1. 项目背景与需求分析
微信公众号作为国内最大的内容创作平台之一,积累了海量的优质文章资源。许多运营者、研究者和内容创作者经常需要将这些文章整理成系统化的知识库或离线文档。传统的复制粘贴方式效率低下,且无法保留原文的排版结构和多媒体内容。
这个项目的核心价值在于解决了三个痛点:
- 批量处理能力:能够一次性爬取公众号合集内的所有文章,而非单篇操作
- 格式标准化:将微信特有的富文本格式转换为通用的Markdown格式
- 内容完整性:保留原文中的图片、代码块、表格等特殊元素
提示:微信公众号的爬取需遵守平台规则,建议仅用于个人收藏和合法合规的研究用途
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术方案选型与对比
2.1 主流爬取方案评估
目前实现微信公众号爬取主要有三种技术路线:
| 方案类型 | 代表工具 | 优点 | 缺点 |
|---|---|---|---|
| 模拟操作 | Selenium/Puppeteer | 绕过接口限制 | 性能低、易被检测 |
| 接口调用 | 微信开放API | 官方合规 | 权限申请复杂 |
| 中间件解析 | WeChatSogou/DrissionPage | 折中方案 | 需要维护解析规则 |
本项目选择基于DrissionPage的混合方案,原因在于:
- 相比纯模拟操作,减少了不必要的页面渲染开销
- 能够处理微信的动态加载机制
- 支持处理合集页面的特殊结构
2.2 Markdown转换方案
原始微信文章是HTML格式,转换为Markdown需要考虑:
- 复杂排版(如多级列表、嵌套引用)的准确转换
- 图片资源的本地化存储
- 表格结构的无损转换
经过实测对比,选用html2text作为基础转换器,并针对微信特性进行了以下定制:
python复制class WeChatHtmlConverter(html2text.HTML2Text):
def __init__(self):
super().__init__()
self.unicode_snob = True # 保留Unicode字符
self.mark_code = True # 特殊处理代码块
self.body_width = 0 # 禁用自动换行
3. 完整实现流程
3.1 环境准备
需要安装的核心依赖:
bash复制pip install drissionpage html2text requests beautifulsoup4
注意:DrissionPage需要Chromium内核,建议使用Docker环境保证一致性
3.2 爬取核心逻辑
3.2.1 合集页面解析
微信公众号合集页面的特殊结构:
javascript复制// 典型合集页面结构
<div class="album__list">
<div class="album__item" data-link="文章1链接">...</div>
<div class="album__item" data-link="文章2链接">...</div>
...
</div>
对应的爬取代码实现:
python复制def get_article_links(collection_url):
page = MixPage('s')
page.get(collection_url)
# 等待动态加载完成
page.wait.ele_loaded('.album__list', timeout=20)
articles = []
for item in page.eles('.album__item'):
link = item.attr('data-link')
title = item.ele('tag:h4').text
articles.append((title, link))
return articles
3.2.2 单篇文章内容提取
微信文章正文位于#js_content节点,但需要注意:
- 图片可能是延迟加载的
- 视频需要特殊处理
- 部分样式类需要清理
优化后的内容提取方案:
python复制def get_article_content(page):
# 滚动到底部触发图片加载
page.scroll.to_bottom()
time.sleep(2)
content = page.ele('#js_content').html
# 清理微信特有样式
content = re.sub(r'<svg.*?</svg>', '', content)
return content
3.3 Markdown转换优化
针对微信内容的特殊处理:
- 图片处理:
python复制def process_images(html):
soup = BeautifulSoup(html, 'lxml')
for img in soup.find_all('img'):
if 'data-src' in img.attrs: # 微信懒加载图片
img['src'] = img['data-src']
return str(soup)
- 表格增强:
python复制converter.handle_td = lambda self, td: td + ' |'
converter.handle_th = lambda self, th: th + ' |'
- 代码块保留:
python复制def wrap_code_blocks(text):
return re.sub(r'```(\w+)?\n(.*?)\n```',
r'```\1\n\2\n```',
text, flags=re.DOTALL)
4. 实战案例与异常处理
4.1 完整工作流示例
python复制def wechat_collection_to_markdown(collection_url, output_dir):
# 1. 获取合集内所有文章链接
articles = get_article_links(collection_url)
# 2. 逐篇处理
for title, link in articles:
page.get(link)
content = get_article_content(page)
# 3. 转换Markdown
html = process_images(content)
markdown = converter.handle(html)
markdown = wrap_code_blocks(markdown)
# 4. 保存文件
filename = f"{sanitize_title(title)}.md"
with open(os.path.join(output_dir, filename), 'w', encoding='utf-8') as f:
f.write(f"# {title}\n\n{markdown}")
4.2 常见问题排查
- 反爬机制触发:
- 症状:获取到空内容或验证页面
- 解决方案:
python复制page = MixPage('s', timeout=30) page.set.user_agent('Mozilla/5.0...')
- 图片下载失败:
- 原因:微信图片链接有时效性
- 应对策略:
python复制def download_image(url): headers = {'Referer': 'https://mp.weixin.qq.com/'} return requests.get(url, headers=headers, timeout=10)
- 编码问题:
- 现象:转换后出现乱码
- 修复方法:
python复制import locale locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
5. 进阶优化方向
5.1 性能优化技巧
- 并发处理:
python复制from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(process_article, link) for link in article_links]
- 缓存机制:
python复制import diskcache
cache = diskcache.Cache('./cache')
@cache.memoize(expire=86400)
def get_article(link):
# 获取文章内容
5.2 内容增强
- 自动生成目录:
python复制import markdown2
markdowner = markdown2.Markdown(extras=['toc'])
- 元信息提取:
python复制def extract_metadata(html):
soup = BeautifulSoup(html, 'lxml')
return {
'author': soup.find('meta', attrs={'property':'og:author'})['content'],
'publish_date': soup.find('em', class_='rich_media_meta').text
}
5.3 企业级部署方案
对于需要大规模采集的场景,建议采用:
- 分布式任务队列(Celery + Redis)
- 代理IP轮换机制
- 自动化监控告警系统
python复制# 代理配置示例
page = MixPage('s', proxies={
'http': 'http://proxy_ip:port',
'https': 'http://proxy_ip:port'
})
6. 法律合规与道德考量
- 版权尊重:
- 禁止将爬取内容用于商业用途
- 建议在本地存储后仅作个人研究使用
- 保留原始出处信息
- 访问频率控制:
python复制import time
import random
def polite_delay():
time.sleep(random.uniform(1, 3)) # 随机延迟
- 用户协议遵守:
- 严格遵守微信公众平台的服务条款
- 不绕过任何技术保护措施
- 不采集用户隐私数据
在实际项目中,我通常会添加明显的版权声明和用途说明:
markdown复制> 本文档由微信公众号文章转换生成,仅供个人学习使用。
> 原始链接:{article_url}
> 转换时间:{datetime.now()}
