ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python实战:批量抓取微信公众号文章并保存为Markdown/JSON

Python实战:批量抓取微信公众号文章并保存为Markdown/JSON 简介这是一套面向开发者与新媒体运营人员的微信公众号文章批量下载工具源码解决官方接口受限下难以高效采集、归档与分析公众号内容的痛点。资源以TypeScriptVue3构建含34个TS逻辑文件、24个Vue组件及配套样式与配置文件完整覆盖搜索、过滤、导出、缓存、订阅与API服务等核心模块109个文件压缩后仅12.02MB结构清晰支持Docker一键部署与私有化落地。已有717人学习下载适用于内容合规审计、竞品分析、知识库建设等场景。用户可直接运行项目获取HTML格式文章含内嵌图片与CSS样式100%还原原始排版同时导出JSON/Excel/TXT多格式数据并提取阅读量、评论及回复等关键运营指标——所有功能均基于本地化实现无需依赖第三方平台或复杂环境配置。1. 项目概述与核心价值最近在整理一些行业资料发现很多有价值的深度内容都沉淀在微信公众号里。但微信的封闭生态让内容归档变得异常麻烦——一篇篇手动复制粘贴效率低下格式还容易乱想做个本地知识库或者进行文本分析数据获取就是第一道坎。相信很多做内容分析、市场研究或者单纯想备份优质文章的朋友都遇到过这个痛点。正是在这种实际需求驱动下我动手写了一个“Wechat Article Exporter”一个能够批量、自动化下载微信公众号文章并保存为结构化格式的工具。今天就把这个项目的完整思路、技术实现细节以及一路踩过来的坑毫无保留地分享出来。简单来说这个工具能帮你做这几件事首先它可以根据你指定的公众号名称或文章列表链接自动抓取文章内容包括标题、作者、发布日期、正文含图文、阅读数、点赞数等元数据。其次它能将抓取到的内容以多种格式保存比如干净的Markdown、带样式的HTML或者结构化的JSON方便你后续导入笔记软件、建立本地搜索或者进行数据分析。最重要的是它实现了批量化操作设定好任务后就可以挂机运行解放双手。无论是个人知识管理还是团队需要做竞品内容监控这个工具都能成为一个得力的“数字助理”。整个项目基于Python构建技术栈清晰代码完全开源。下文我会详细拆解从抓包分析微信接口、模拟登录与请求、解析页面到数据存储的每一个环节并提供可直接运行的源码。即使你不是专业开发者跟着步骤走也能在自己的电脑上搭建起这套系统。2. 核心思路与技术选型解析2.1 为什么不能直接爬取在动手之前首先要明白微信公众号内容的特殊性。它不像普通网页那样可以直接通过Requests库抓取HTML。微信公众平台有以下几个关键防线动态加载与反爬机制公众号文章列表和内容大多通过Ajax动态加载数据接口返回的是JSON格式而非完整的HTML页面。这些接口通常带有复杂的参数签名如signature、timestamp、nonce和访问令牌access_token直接模仿难度大。登录态与Cookie要获取非公开文章列表或某些详细数据需要维持一个有效的微信登录态Cookie。这个登录态是通过扫描二维码在移动端确认获得的模拟这一过程有一定复杂度。请求频率限制微信服务器对高频请求有严格的限制轻则返回错误码重则封禁IP或临时禁止访问。因此直接写爬虫去硬刚微信服务器是不明智的。我们的核心思路需要转变不是去破解微信的接口而是找到一个合法的“观察窗口”来获取数据。2.2 技术路径选择抓包分析与模拟经过多种方案的对比测试我最终确定了以下技术路径这也是本项目的核心抓包定位数据源不直接攻击微信主站而是利用微信PC客户端、微信网页版或者第三方聚合平台如搜狗微信搜索作为数据来源。通过抓包工具如Fiddler、Charles或Wireshark监听这些客户端与服务器之间的网络请求找到真正返回文章列表和文章内容的API接口。这是最关键的一步找到了正确的接口问题就解决了一大半。请求模拟与参数构造分析抓包得到的HTTP请求包括URL、Headers特别是User-Agent、Referer、Cookie、以及GET/POST参数。然后使用Python的requests库或httpx库完全模拟这些请求从而“伪装”成一个合法的客户端来获取数据。数据解析与清洗获取到的数据可能是JSON也可能是HTML。对于JSON接口直接解析即可对于HTML页面则需要使用BeautifulSoup或lxml这样的解析库来提取标题、正文、图片等元素。这里需要处理富文本中的多种标签和样式。持久化存储将解析后的数据根据用户选择保存为不同格式。Markdown格式最通用便于后续编辑HTML格式能保留原始排版JSON格式则最适合程序化处理和分析。任务调度与容错实现批量下载时必须加入延时控制如time.sleep来规避反爬同时要设计重试机制如使用tenacity库来处理网络波动或临时错误确保长任务稳定运行。这个路径的优势在于它绕开了最复杂的微信核心接口鉴权利用现有客户端已经建立好的通信通道实现了一种“借道”获取数据的方式合法性和稳定性都更高。2.3 工具与依赖库清单工欲善其事必先利其器。以下是实现本项目所需的核心Python库及其作用requests / httpx用于发送HTTP请求模拟浏览器行为。httpx支持HTTP/2和异步性能更好但requests更简单稳定。BeautifulSoup4 / lxml / parselHTML/XML解析器用于从抓取的页面中提取所需的数据。BeautifulSoup写起来简单lxml解析速度更快。json / demjson处理JSON数据。Python标准库的json通常够用demjson能处理一些不太规范的JSON字符串。PyExecJS / node.js有些接口的参数加密使用了JavaScript代码需要在Python环境中执行JS来生成正确的参数。PyExecJS是一个桥梁。Pillow (PIL)如果需要下载和处理文章中的图片会用到这个图像处理库。tenacity一个强大的重试库可以优雅地处理请求失败后的重试逻辑。loguru让日志记录变得非常简单美观便于调试和监控运行状态。python-dotenv管理配置信息如代理设置、保存路径将敏感信息从代码中分离。除了Python库你还需要一个抓包工具。我强烈推荐Fiddler ClassicWindows或Charles跨平台它们对于HTTPS流量的解密和抓取非常方便。当然浏览器自带的开发者工具F12中的Network面板对于初步分析也足够了。3. 实操步骤详解从抓包到数据落地3.1 第一步环境准备与抓包配置首先确保你的电脑上安装了Python 3.7及以上版本。创建一个新的项目目录并使用虚拟环境来管理依赖。mkdir wechat-article-exporter cd wechat-article-exporter python -m venv venv # Windows: venv\Scripts\activate # Mac/Linux: source venv/bin/activate然后将前面提到的核心库安装进来。我习惯用一个requirements.txt文件来管理。requests2.28.0 beautifulsoup44.11.0 lxml4.9.0 httpx0.23.0 tenacity8.0.0 loguru0.6.0 python-dotenv0.20.0 pillow9.0.0使用pip install -r requirements.txt进行安装。接下来是抓包工具配置。以Fiddler为例你需要开启HTTPS解密功能Tools - Options - HTTPS并安装Fiddler的根证书到受信任的根证书颁发机构。这样你才能看到微信客户端与服务器之间加密通信的内容。配置完成后打开微信PC版在Fiddler中就能看到所有的网络请求了。注意抓包过程涉及拦截和查看网络流量请仅用于学习和个人合法的数据备份目的勿用于侵犯他人权益或违反微信用户协议的行为。3.2 第二步定位关键API接口启动Fiddler和微信PC版。在微信中找到你想要导出文章的公众号点击进入其主页查看历史消息列表。此时Fiddler的会话列表会刷出大量请求。我们的目标是找到加载文章列表的那个请求。你可以通过以下特征来筛选URL关键词寻找包含mp.weixin.qq.com域名的请求特别是路径中带有profile、home、getappmsgext等字样的。响应格式在Fiddler中查看响应体Response Body如果是JSON格式且内容包含文章标题、链接等那很可能就是目标接口。请求方法通常是GET或POST。例如我通过抓包发现公众号历史消息列表的一个关键接口模式类似于https://mp.weixin.qq.com/mp/profile_ext?actionhome__biz...scene......这个接口会返回一个包含文章列表数据的JSON。其中__biz参数是公众号的唯一标识至关重要。另一个关键接口是获取单篇文章详细内容的可能形如https://mp.weixin.qq.com/s?__biz...mid...idx...sn...或者通过一个getappmsgext的接口来获取阅读数、点赞数等。你需要耐心地多翻几页历史消息观察请求参数的变化规律特别是offset偏移量、count每页数量等分页参数。把这些接口的URL、Headers和参数规律记录下来。3.3 第三步编写请求模拟代码拿到接口信息后就可以开始用Python模拟请求了。核心是还原抓包看到的HTTP请求。import requests import json import time from loguru import logger from tenacity import retry, stop_after_attempt, wait_exponential class WechatArticleCrawler: def __init__(self): self.session requests.Session() # 从抓包中复制来的关键Headers特别是Cookie和User-Agent self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://mp.weixin.qq.com/, # Cookie: 你的抓包获取的Cookie字符串这是维持登录态的关键 } self.session.headers.update(self.headers) retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) def fetch_article_list(self, biz_id, offset0, count10): 获取公众号文章列表 :param biz_id: 公众号的__biz参数 :param offset: 偏移量用于分页 :param count: 每页数量 :return: 文章列表数据 url https://mp.weixin.qq.com/mp/profile_ext params { action: home, __biz: biz_id, offset: offset, count: count, is_ok: 1, scene: 124, uin: 777, key: 777, pass_ticket: 你的pass_ticket, # 可能需要从Cookie或其他接口获取 appmsg_token: 你的appmsg_token, # 同上 f: json, } try: resp self.session.get(url, paramsparams, timeout10) resp.raise_for_status() # 检查HTTP错误 data resp.json() if data.get(ret) 0: return data.get(general_msg_list, {}).get(list, []) else: logger.error(f获取列表失败: {data}) return [] except requests.exceptions.RequestException as e: logger.error(f网络请求异常: {e}) raise except json.JSONDecodeError as e: logger.error(fJSON解析失败: {e}, 响应文本: {resp.text[:200]}) raise def parse_article_item(self, item): 解析单篇文章列表项提取标题、链接、发布时间等 app_msg_ext_info item.get(app_msg_ext_info, {}) title app_msg_ext_info.get(title) content_url app_msg_ext_info.get(content_url) if content_url: # 微信返回的链接是http需要替换为https content_url content_url.replace(http://, https://) publish_time item.get(comm_msg_info, {}).get(datetime) return { title: title, url: content_url, publish_time: publish_time }这段代码定义了一个爬虫类初始化了会话和请求头。fetch_article_list方法模拟了抓取文章列表的请求并使用了tenacity装饰器来实现自动重试。parse_article_item方法则用于从返回的复杂JSON中提取出我们关心的字段。实操心得Cookie和appmsg_token等参数是动态变化的且有有效期。最稳妥的方式不是写死在代码里而是设计一个“登录模块”通过扫描二维码等方式实时获取这些凭证。对于个人偶尔使用的场景可以从抓包工具中手动复制一次Cookie短时间内有效。对于自动化要求高的场景则需要实现完整的二维码登录流程这涉及到与微信服务器的更多交互复杂度会上升一个等级。3.4 第四步解析文章详情与数据存储获取到文章链接列表后下一步就是逐个访问这些链接抓取完整的文章内容。def fetch_article_detail(self, article_url): 抓取单篇文章的详细内容 try: resp self.session.get(article_url, timeout10) resp.raise_for_status() # 假设返回的是HTML return resp.text except Exception as e: logger.error(f抓取文章详情失败 {article_url}: {e}) return None def parse_html_content(self, html): 从HTML中解析出正文、作者等信息 from bs4 import BeautifulSoup soup BeautifulSoup(html, lxml) # 微信文章的正文通常在id为js_content的div里 content_div soup.find(div, idjs_content) if not content_div: logger.warning(未找到正文内容) return None # 提取纯文本简单示例实际需要更精细处理 text_content content_div.get_text(stripTrue, separator\n) # 提取标题可能在h1标签或meta标签里 title soup.find(h1, class_rich_media_title) if title: title title.get_text(stripTrue) else: # 从meta标签获取 meta_title soup.find(meta, propertyog:title) title meta_title[content] if meta_title else 未知标题 # 提取作者、发布时间等位置可能不固定 # 这里需要根据实际HTML结构调整CSS选择器 author_tag soup.find(span, class_rich_media_meta rich_media_meta_text) author author_tag.get_text(stripTrue) if author_tag else 未知作者 return { title: title, author: author, content_text: text_content, content_html: str(content_div) # 保留原始HTML }解析出内容后我们需要将其保存下来。这里提供保存为Markdown和JSON的示例import os from datetime import datetime def save_as_markdown(article_info, save_diroutput): 保存为Markdown文件 os.makedirs(save_dir, exist_okTrue) # 用标题作为文件名过滤掉非法字符 safe_title .join([c for c in article_info[title] if c.isalnum() or c in ( , -, _)]).rstrip() filename f{safe_title[:50]}.md # 限制文件名长度 filepath os.path.join(save_dir, filename) with open(filepath, w, encodingutf-8) as f: f.write(f# {article_info[title]}\n\n) f.write(f**作者**: {article_info[author]}\n) f.write(f**发布时间**: {article_info.get(publish_time_str, )}\n) f.write(f**原文链接**: {article_info.get(url, )}\n\n) f.write(---\n\n) f.write(article_info[content_text]) logger.info(f文章已保存为Markdown: {filepath}) def save_as_json(articles_list, save_diroutput): 将多篇文章信息保存为单个JSON文件 os.makedirs(save_dir, exist_okTrue) timestamp datetime.now().strftime(%Y%m%d_%H%M%S) filepath os.path.join(save_dir, fwechat_articles_{timestamp}.json) with open(filepath, w, encodingutf-8) as f: json.dump(articles_list, f, ensure_asciiFalse, indent2) logger.info(f文章列表已保存为JSON: {filepath})3.5 第五步组装与批量执行最后我们将上述模块组合起来实现批量下载的主逻辑。def run(self, biz_id, start_page0, max_pages5, delay2): 主运行函数 :param biz_id: 公众号biz :param start_page: 起始页偏移量计算 :param max_pages: 最多抓取多少页 :param delay: 每次请求之间的延迟秒数防止被封 all_articles [] for page in range(start_page, max_pages): offset page * 10 # 假设每页10条 logger.info(f正在抓取第{page1}页偏移量{offset}) article_items self.fetch_article_list(biz_id, offsetoffset, count10) if not article_items: logger.warning(f第{page1}页未获取到数据可能已到底部) break for item in article_items: parsed_item self.parse_article_item(item) if not parsed_item or not parsed_item.get(url): continue logger.info(f处理文章: {parsed_item[title]}) html self.fetch_article_detail(parsed_item[url]) if html: detail self.parse_html_content(html) if detail: full_info {**parsed_item, **detail} all_articles.append(full_info) # 保存单篇Markdown self.save_as_markdown(full_info) # 礼貌性延迟非常重要 time.sleep(delay) else: logger.error(f无法获取文章详情: {parsed_item[title]}) # 每抓完一页列表也加一个延迟 time.sleep(delay * 2) # 所有文章抓取完毕后再保存一个汇总的JSON self.save_as_json(all_articles) logger.success(f批量抓取完成共获取{len(all_articles)}篇文章。) if __name__ __main__: crawler WechatArticleCrawler() # 需要替换成目标公众号的biz参数 target_biz MzAwNTc5NjI0MQ # 示例并非真实 crawler.run(biz_idtarget_biz, max_pages3, delay3)4. 常见问题、避坑指南与进阶优化在实际开发和运行过程中你几乎一定会遇到下面这些问题。这里我把解决方案和思考过程记录下来希望能帮你节省大量时间。4.1 高频问题排查表问题现象可能原因排查步骤与解决方案获取列表返回ret不为0例如ret: -11. Cookie或关键令牌如appmsg_token,pass_ticket已过期。2. 请求参数不正确或缺失。3. 请求频率过高被临时限制。1.首要检查从抓包工具中获取最新的Cookie和请求参数替换到代码中。确认__biz参数是否正确。2.模拟对比用抓包工具捕获一个成功的请求将代码生成的请求URL、Headers和参数与之逐字对比确保完全一致。3.增加延迟大幅增加time.sleep的间隔时间例如从2秒增加到5-10秒并尝试更换IP地址。能获取列表但文章详情页返回空白或4031. 文章链接需要额外的参数或特定的Referer。2. 详情页有JavaScript渲染直接请求获取不到完整HTML。3. IP或Cookie被针对文章页做了限制。1.检查链接确保文章链接已从http替换为https。尝试在请求详情页时带上正确的Referer头通常是公众号主页或列表页URL。2.使用渲染引擎如果页面是JS动态加载的考虑使用Selenium、Playwright或Pyppeteer这类能控制真实浏览器的工具来获取渲染后的HTML。3.会话保持确保requests.Session()被正确使用这样能自动管理Cookie。解析HTML时找不到js_content等元素1. HTML结构发生变化。2. 获取到的HTML不是文章正文页可能是登录跳转页或错误页。1.打印检查将获取到的HTML前1000字符保存到文件或打印出来确认是否是预期的文章页面。2.更新选择器使用浏览器开发者工具重新检查目标元素最新的CSS选择器或XPath路径。3.备用方案尝试通过meta标签如og:description或其他通用标签来获取内容。运行一段时间后突然全部失败1. IP地址被微信服务器封禁。2. 使用的Cookie被踢下线。1.使用代理IP池这是长期稳定运行的关键。可以购买付费代理服务或者使用ADSL拨号换IP家庭网络环境下。在代码中集成代理设置并定期更换。2.实现自动登录设计一个监控机制当检测到请求连续失败如返回登录页时自动触发二维码扫描登录流程更新Cookie。这部分的实现较复杂需要处理图片显示、登录状态轮询等。下载的图片链接失效或无法显示文章中的图片链接可能是防盗链的或者需要特定的请求头才能访问。1.模拟Referer下载图片时在请求头中设置Referer为文章原地址。2.替换图床有些工具会选择将图片下载到本地并在生成的Markdown/HTML中替换为本地路径。这需要额外的图片下载和处理逻辑。4.2 核心避坑经验延迟是美德不是缺点这是最重要的原则。无论你的网络多快代码多高效都必须在请求之间加入随机延时例如time.sleep(random.uniform(2, 5))。过于频繁的请求是触发反爬机制最快的方式。把抓取速度放慢稳定性会极大提升。Cookie管理是生命线手动复制Cookie是权宜之计。对于严肃项目必须实现Cookie的持久化存储如保存到文件或数据库和过期更新机制。每次运行前检查Cookie是否有效无效则重新登录。异常处理要周全网络超时、连接重置、JSON解析错误、HTML结构异常……这些情况一定会发生。务必用try...except包裹所有网络请求和数据处理代码并记录详细的错误日志便于事后排查。使用tenacity等重试库可以优雅地处理临时性错误。尊重robots.txt与法律边界虽然技术上可行但在大规模抓取前请务必查看目标网站的robots.txt文件并遵守其规定。同时确保你的数据用途符合相关法律法规和平台用户协议仅用于个人学习、研究或备份不进行商业性转载、贩卖或恶意传播。4.3 项目进阶优化方向如果你已经实现了基础功能并希望工具更强大、更稳定可以考虑以下优化异步并发抓取使用asyncioaiohttp或httpx的异步客户端可以大幅提高I/O密集型网络请求的效率。但要注意并发数必须严格控制否则会立刻被封。集成可视化界面使用PyQt、Tkinter或Streamlit为工具制作一个图形界面方便非技术用户输入公众号信息、选择保存路径、设置抓取参数等。内容增强与处理图片本地化自动下载文章中的所有图片并替换Markdown/HTML中的链接为本地路径。格式清洗优化正文提取算法更好地处理代码块、表格、公式等特殊格式生成更干净的Markdown。元数据补充除了阅读点赞数还可以尝试抓取评论如果接口开放、文章标签等。任务调度与监控将工具改造为常驻服务支持定时抓取指定公众号的新文章实现“订阅”功能。可以通过邮件、钉钉机器人等方式发送抓取完成通知。多数据源支持不局限于PC端接口。可以研究手机端接口通过抓包安卓模拟器、或者第三方平台如搜狗微信搜索、清博大数据等作为备选数据源提高工具的鲁棒性。这个项目的完整源码我会整理后放在GitHub上。它不仅仅是一个脚本更是一个理解网络爬虫与反爬对抗、模拟登录、数据清洗的完整案例。技术细节可能会随着微信前端的改版而变化但解决问题的思路和框架是通用的。希望这份详细的拆解能帮你构建起自己的信息获取工具高效地管理数字时代的知识碎片。本文还有配套的精品资源点击获取
返回列表