Python爬虫实战:Fiddler抓包解析微信公众号历史文章数据 1. 项目缘起一个被“历史数据”困扰的日常需求做内容运营或者自媒体分析的朋友估计都遇到过这个头疼事想回顾一下自己公众号过去几年的数据表现看看哪篇文章爆了哪个话题凉了用户增长曲线是怎么走的。你兴致勃勃地打开公众号后台准备大干一场结果发现后台的数据导出功能要么限制时间范围要么导出的字段不全格式还乱七八糟想做个跨年度的趋势分析得手动一页一页地翻一篇一篇地复制粘贴。这效率简直能把人逼疯。更别提如果你想分析竞品或者某个垂类大号的运营策略了。公开渠道能看到的数据非常有限阅读数、点赞数、在看数这些最核心的互动指标除了单篇文章页面上显示的那个数字几乎没有批量获取的途径。市面上倒是有一些第三方数据平台但要么收费昂贵要么数据更新不及时要么就是担心数据安全和合规问题。于是自己动手写一个工具就成了很多技术背景的运营者或数据分析师心里冒出来的念头。我这个脚本就是在这种背景下诞生的。它不是什么高深莫测的黑科技核心目标就一个自动化、批量化地获取指定公众号的历史文章数据。这里的“历史数据”我主要聚焦在文章层面包括但不限于文章标题、永久链接、发布时间、阅读数、点赞数好看数、在看数、文章摘要如果有、封面图链接等。把这些数据规整地抓下来存到本地数据库或者Excel里后续你想做内容分析、用户画像、传播效果评估就有了第一手、干净的数据原料。这个需求听起来简单但真动手做你会发现微信公众平台的反爬机制和接口限制就像一道道关卡。它不像爬取一个普通的静态网页直接requests加BeautifulSoup就能搞定。你需要模拟登录、处理Cookie、理解它前端渲染和数据加载的逻辑甚至要应对频繁请求带来的封禁风险。接下来我就把自己趟过的路、踩过的坑以及最终跑通的方案拆开揉碎了跟大家分享。这不是一个“万能脚本”而是一个基于特定技术思路的实战记录希望能给有同样需求的朋友提供一个可行的参考框架。2. 核心思路与工具选型为什么是“Fiddler 公众号接口”这条路最开始我也尝试过几种常见思路但都遇到了不小的障碍。第一种是纯前端模拟。用Selenium或者Puppeteer这类浏览器自动化工具模拟用户操作打开公众号主页滚动加载然后从页面HTML里解析数据。这个方法直观但问题很大。首先效率极低加载和渲染页面需要时间其次公众号文章列表页是动态加载的需要不断滚动触发稳定性差最重要的是阅读数、点赞数这些关键数据在列表页是不显示的你必须点进每一篇文章的详情页才能看到这意味着你要发起海量的页面请求被封IP的风险极高几乎不可行。第二种是寻找公开的RSS源。早些年微信公众号是支持RSS的但现在官方早已关闭。虽然有一些第三方服务如WeRSS能提供部分公众号的RSS但覆盖不全、数据延迟、且有中断风险无法作为稳定可靠的数据源。所以经过一番摸索和测试我最终确定的路线是通过抓包工具Fiddler/Charles分析微信公众号网页端或手机端的网络请求找到其获取文章列表和文章详情的真实后端接口API然后使用Python脚本模拟这些请求直接获取结构化的JSON数据。这个方案的优势很明显高效直接调用数据接口绕过页面渲染速度极快。精准获取的是源头结构化的数据JSON字段清晰无需复杂的HTML解析。稳定只要微信不彻底改变其接口鉴权方式和参数逻辑脚本就能持续工作。工具选型如下抓包分析工具Fiddler Everywhere或Charles。我主要用Fiddler因为它对Windows友好且能方便地解密HTTPS流量需要安装证书。这一步的目的是“侦察”找到我们需要的API地址、请求头、参数和返回数据结构。开发语言Python 3.8。生态丰富requests库处理HTTP请求简单强大json库解析数据pandas和openpyxl处理数据存储sqlite3或pymysql操作数据库一套下来非常顺畅。关键Python库requests: 发送HTTP请求的核心。json: 解析接口返回的JSON数据。pandas: 数据清洗、处理和导出为Excel。sqlalchemy/pymysql/sqlite3: 可选用于将数据持久化到数据库。time/datetime: 处理时间、添加请求间隔防止被封。logging: 记录脚本运行日志方便排查问题。注意任何爬虫行为都应遵守网站的robots.txt协议并尊重数据版权。本脚本及思路仅用于个人学习、分析自有公众号数据或获取已公开的、无明确禁止抓取声明的数据。严禁用于商业爬取、侵犯他人权益或对目标服务器造成恶意负载。3. 关键步骤拆解从抓包到数据落地的完整链路整个流程可以分解为几个关键阶段每个阶段都有需要注意的细节。3.1 第一步侦察——用Fiddler捕获关键API这是最核心的一步决定了脚本的可行性。你需要在自己电脑或手机上配置好Fiddler的代理并安装好CA证书以解密HTTPS流量。打开目标公众号在微信PC客户端或浏览器中打开你想要抓取数据的公众号主页。例如在浏览器中访问https://mp.weixin.qq.com/并登录后进入目标公众号的“图文消息”列表页。开始抓包在Fiddler中清空当前会话然后在公众号页面里进行“滚动加载”操作。随着你不断向下滚动新的文章列表会被加载出来。寻找目标请求在Fiddler捕获到的一大堆请求中你需要筛选出那个携带文章列表数据的请求。通常这类请求的URL会包含明显的关键字比如appmsgpublish已发布文章、appmsg文章、list列表等。返回格式是application/json。分析请求详情找到疑似目标请求后重点查看Request Headers请求头特别是Cookie、User-Agent、Referer、X-Requested-With等。Cookie是维持登录状态的关键脚本需要复用这个。Query String Parameters查询参数URL问号后面的参数。通常会有action动作如list_ex、begin开始位置、count每次获取数量通常是5或10、fakeid公众号的唯一ID、token一个动态令牌等。fakeid是公众号的身份证至关重要。Response响应查看返回的JSON数据结构。里面应该有一个数组包含了文章的基本信息如标题(title)、链接(link)、发布时间(create_time)、封面图(cover)等。但注意阅读数(read_num)和点赞数(like_num)通常不在这里需要另一个接口。通过反复滚动和观察你就能确定获取列表的API URL和参数规律。例如可能是一个类似https://mp.weixin.qq.com/cgi-bin/appmsgpublish?actionlist_ex...的地址。3.2 第二步解析——获取公众号唯一标识fakeidfakeid是公众号在微信后台系统里的唯一ID不是我们常见的微信号或公众号名称。如何获取它从公众号主页URL获取在浏览器中打开公众号的图文消息列表页其URL通常格式为https://mp.weixin.qq.com/mp/profile_ext?actionhome__bizXXXXXXscene124#wechat_redirect。其中__biz参数后面的XXXXXXBase64编码经过解码后往往就是fakeid或其相关标识。但这种方式有时不稳定。从抓包请求中提取推荐在Fiddler捕获的列表请求参数里直接找到fakeid参数的值。这是最准确的方式。把这个值记录下来它就是脚本中需要使用的目标公众号ID。3.3 第三步构建——Python脚本的核心逻辑有了API地址、参数和fakeid就可以开始编写脚本了。脚本主要分为几个函数模块import requests import json import time import pandas as pd from datetime import datetime import logging # 配置日志 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) class WeChatCrawler: def __init__(self, cookie, token, fakeid): 初始化爬虫设置关键参数 :param cookie: 从浏览器/Fiddler复制的完整Cookie字符串 :param token: 从请求参数中获取的token注意token有时效性 :param fakeid: 目标公众号的唯一ID self.session requests.Session() self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., # 使用真实的UA Cookie: cookie, Referer: https://mp.weixin.qq.com/, } self.token token self.fakeid fakeid self.base_url https://mp.weixin.qq.com/cgi-bin/appmsgpublish self.article_list [] # 存储所有文章基本信息 def get_article_list(self, begin0, count10): 获取一页文章列表 :param begin: 起始偏移量 :param count: 每页数量通常最大为10微信限制 :return: 文章列表数据 和 是否还有下一页 params { action: list_ex, begin: begin, count: count, fakeid: self.fakeid, token: self.token, lang: zh_CN, f: json, ajax: 1 } try: resp self.session.get(self.base_url, headersself.headers, paramsparams, timeout30) resp.raise_for_status() # 检查HTTP错误 data resp.json() if data.get(base_resp, {}).get(ret) 200001: logger.error(Token可能已过期或Cookie失效请更新。) return None, False app_msg_list data.get(app_msg_list, []) has_next data.get(has_next, 0) 1 for item in app_msg_list: article_info { aid: item.get(aid), title: item.get(title), link: item.get(link), create_time: datetime.fromtimestamp(item.get(create_time, 0)), cover: item.get(cover), digest: item.get(digest, ), # 注意列表接口通常没有阅读数/点赞数 } self.article_list.append(article_info) logger.info(f获取到文章: {article_info[title][:30]}...) return app_msg_list, has_next except requests.exceptions.RequestException as e: logger.error(f请求列表失败: {e}) return None, False except json.JSONDecodeError as e: logger.error(f解析JSON失败: {e}, 响应文本: {resp.text[:200]}) return None, False def get_article_detail(self, article_url): 获取单篇文章的详细数据特别是阅读数、点赞数。 这里需要找到获取详情的接口可能需要解析文章页面或调用另一个API。 注意微信对阅读数等敏感数据接口保护严密直接通过文章链接可能拿不到。 一种常见方法是文章列表接口返回的 app_msg_list 里每个item可能包含 appmsgid 和 itemidx。 另一个详情接口可能需要组合这些参数。 由于微信策略经常变动此部分为难点下文会详细讨论。 # 此处为难点暂不实现具体代码 pass def crawl_all(self, max_pages50): 爬取所有历史文章列表 :param max_pages: 最大爬取页数防止无限循环 begin 0 count 10 current_page 0 while current_page max_pages: logger.info(f正在爬取第 {current_page 1} 页起始位置 {begin}) _, has_next self.get_article_list(begin, count) if not has_next: logger.info(已爬取所有文章列表。) break begin count current_page 1 time.sleep(2 random.random()) # 重要添加随机延迟避免请求过快 logger.info(f列表爬取结束共获取 {len(self.article_list)} 篇文章。) def save_to_excel(self, filenamewechat_articles.xlsx): 将文章列表保存到Excel if not self.article_list: logger.warning(文章列表为空无法保存。) return df pd.DataFrame(self.article_list) df.to_excel(filename, indexFalse) logger.info(f数据已保存至 {filename}) # 使用示例 (需要替换为你的真实数据) if __name__ __main__: # 这些信息需要从Fiddler捕获的请求中提取 YOUR_COOKIE 你的Cookie字符串很长 YOUR_TOKEN 你的Token YOUR_FAKEID 公众号的Fakeid crawler WeChatCrawler(cookieYOUR_COOKIE, tokenYOUR_TOKEN, fakeidYOUR_FAKEID) crawler.crawl_all(max_pages100) # 尝试爬100页 crawler.save_to_excel()这个框架完成了最基础的文章列表抓取。但最关键、也最棘手的阅读数、点赞数并不在列表接口中。3.4 第四步攻坚——如何获取阅读数与点赞数这是整个项目最大的挑战。微信将这些数据保护得很好。经过我的测试和研究目前请注意时效性有几种思路但都不完美通过文章永久链接页面抓取不稳定直接请求文章链接如https://mp.weixin.qq.com/s/XXXXXX从返回的HTML中解析。阅读数和点赞数可能藏在某个script标签的变量里或者通过后续的AJAX请求加载。这种方法需要解析HTML且微信可能随时改变前端代码结构稳定性差。此外频繁请求文章页面极易触发风控。寻找内部数据接口较优但复杂在公众号管理后台mp.weixin.qq.com操作时Fiddler可能会捕获到获取文章统计数据的专用接口。例如在“图文分析”或“单篇图文”页面可能会触发请求某个包含getappmsgext获取文章扩展信息字样的API。这个接口可能需要appmsgid文章ID和itemidx多图文中的索引等参数并且对Cookie和Token的校验极其严格。这个接口是获取阅读点赞数据最“正统”的途径但参数构造和鉴权逻辑非常复杂且属于微信内部接口使用需格外谨慎。模拟“在看”数据请求仅供参考点赞好看数有时可以通过模拟点击“在看”的请求来间接获取这个思路更偏向于逆向工程难度和风险都极高不推荐普通用户尝试。实操建议对于个人学习可以尝试方法1但要做好解析规则经常失效的心理准备。如果只是为了分析自己公众号的数据公众号后台本身提供了数据导出功能虽然不好用或者使用微信官方提供的API需认证的服务号且有严格权限和频率限制这才是最合规的途径。在我的脚本中我暂时将get_article_detail函数留空并添加了日志提示。在实际应用中如果你通过抓包找到了稳定的详情数据接口可以在此函数中实现对应的请求和解析逻辑。4. 核心难点与避坑指南我踩过的那些“坑”写这个脚本的过程就是不断踩坑和填坑的过程。下面这些经验希望能帮你节省大量时间。4.1 Cookie与Token的时效性与管理坑1Cookie过期从浏览器或Fiddler复制出来的Cookie是有生命周期的。可能几小时也可能几天后就失效了。脚本跑着跑着就返回“未登录”错误。应对脚本里要有重试和异常处理机制。一旦检测到ret值为200001等登录失效的标识就记录日志并停止运行提示用户需要更新Cookie。可以考虑将Cookie持久化到文件并编写一个简单的“更新Cookie”的辅助流程。坑2Token的动态性token参数在很多请求中都需要而且它似乎是动态变化的和当前会话有关。直接从某一次抓包的请求里复制一个token可能很快失效。应对观察发现在同一个登录会话中token在一定时间内是有效的。我们的脚本应在一次执行周期内使用最初获取的那个token。如果脚本需要长期定时运行可能需要模拟一个“保持会话活跃”的定时任务或者研究token的生成/刷新机制这很难。4.2 请求频率控制与反爬策略坑3请求太快被封如果你不加任何延迟连续快速请求接口很快就会被微信服务器限制返回错误或直接封禁IP一段时间。应对必须添加延迟在每次循环请求后使用time.sleep()添加一个随机延迟比如time.sleep(2 random.random())。这样模拟人类操作能大大降低被封风险。对于大量历史数据慢就是快。坑4User-Agent被识别使用默认的python-requests的UA很容易被识别为爬虫。应对使用真实的浏览器User-Agent字符串。可以从Fiddler抓到的请求头里复制。4.3 数据解析与字段缺失坑5JSON结构变化微信后台的接口返回格式并非一成不变。今天app_msg_list在data里明天可能就换了个名字。应对解析JSON时多用.get(‘key’, default)的方式避免直接[‘key’]导致KeyError崩溃。做好日志记录把异常的响应内容片段记录下来方便调整解析逻辑。坑6关键数据不在列表接口如前所述阅读数、点赞数缺失是常态。应对调整心理预期。本脚本的首要目标是高效获取文章元数据标题、链接、时间。对于阅读点赞数据要么接受缺失要么投入更多精力去攻破详情接口并承担更高风险和不稳定性。4.4 关于fakeid的获取稳定性坑7fakeid获取不到或错误从公众号主页URL解码__biz参数不一定100%准确特别是对于某些类型的公众号。应对最可靠的方法永远是从实际成功的数据请求参数中提取。确保你从Fiddler里找到的那个能返回文章列表的请求其参数里的fakeid就是你要用的那个。5. 数据存储、清洗与简单分析示例抓取到的数据是原始、杂乱的需要经过处理才能用于分析。5.1 存储方案选择JSON文件简单适合数据量小、临时存储。使用json.dump保存列表。CSV/Excel文件最常用方便用Excel或Pandas打开查看。使用pandas.DataFrame.to_csv/to_excel。数据库SQLite/MySQL适合数据量大、需要复杂查询或长期积累。可以设计表结构如articles(id, title, link, publish_time, read_num, like_num, ...)。我的脚本示例中用了Excel因为它对大多数人最友好。在实际项目中我更喜欢用SQLite轻量且功能强大。import sqlite3 def save_to_sqlite(self, db_pathwechat_data.db): conn sqlite3.connect(db_path) cursor conn.cursor() # 创建表如果不存在 cursor.execute(‘’‘ CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, link TEXT UNIQUE, -- 链接唯一防止重复插入 publish_time TIMESTAMP, cover_url TEXT, digest TEXT, read_count INTEGER DEFAULT 0, like_count INTEGER DEFAULT 0, crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ‘’‘) # 插入或更新数据 for article in self.article_list: cursor.execute(‘’‘ INSERT OR REPLACE INTO articles (title, link, publish_time, cover_url, digest) VALUES (?, ?, ?, ?, ?) ‘’‘, (article[‘title‘], article[‘link‘], article[‘create_time‘], article[‘cover‘], article[‘digest‘])) conn.commit() conn.close() logger.info(f“数据已保存至SQLite数据库: {db_path}”)5.2 简单数据清洗与分析有了数据就可以用Pandas进行一些简单的分析了。import pandas as pd import matplotlib.pyplot as plt # 读取数据 df pd.read_excel(‘wechat_articles.xlsx‘) df[‘publish_time‘] pd.to_datetime(df[‘create_time‘]) # 确保时间是datetime类型 # 1. 发布频率分析按年-月统计文章数量 df[‘year_month‘] df[‘publish_time‘].dt.to_period(‘M‘) monthly_count df.groupby(‘year_month‘).size() print(“月度发文数量“) print(monthly_count) # 2. 标题长度与阅读数关系假设有阅读数字段‘read_num‘ if ‘read_num‘ in df.columns: df[‘title_length‘] df[‘title‘].str.len() plt.scatter(df[‘title_length‘], df[‘read_num‘]) plt.xlabel(‘标题长度‘) plt.ylabel(‘阅读数‘) plt.title(‘标题长度与阅读数关系‘) plt.show() # 3. 找出最受欢迎的文章按阅读数或点赞数 if ‘read_num‘ in df.columns: top_10_read df.nlargest(10, ‘read_num‘)[[‘title‘, ‘publish_time‘, ‘read_num‘, ‘like_num‘]] print(“阅读数TOP10文章“) print(top_10_read)这些分析虽然基础但能快速给你一个内容表现的整体印象。6. 脚本的优化与扩展方向一个能跑起来的脚本只是开始要让它更健壮、更实用还有很多可以优化的地方。配置化将Cookie、Token、Fakeid、请求头、数据库连接信息等写入一个配置文件如config.yaml或config.ini避免硬编码。日志与监控使用logging模块记录不同级别的日志INFO, WARNING, ERROR并输出到文件方便后续排查问题。可以记录每次请求的URL、状态码、耗时。断点续爬如果脚本中途因网络或封禁中断应该能从断点恢复而不是重头开始。可以在本地记录一个offset.json文件保存当前已爬取的页码或最后一条文章的ID。异常重试与代理使用requests的适配器或tenacity库为请求添加重试机制。如果IP被封可以考虑集成代理IP池但这会大大增加复杂度。分布式与调度如果需要监控成百上千个公众号可以考虑使用Scrapy框架并结合Celery和Redis进行分布式任务调度。但这属于工业级方案了。数据更新策略对于已抓取过的公众号后续运行脚本时应该只抓取新发布的文章。可以通过对比数据库中已存在的最新文章发布时间或唯一ID如aid来实现增量抓取。最后必须再次强调技术是把双刃剑。这个脚本分享的是在技术层面解决一个具体问题的思路和方法。在实际使用中请务必遵守相关法律法规和平台规则将之用于正当的学习和研究目的控制请求频率避免对他人服务器造成不必要的负担。对于核心的阅读点赞数据获取如果找不到稳定合规的接口或许接受它的缺失转而更深入地分析已有的标题、摘要、发布时间等数据也能获得许多有价值的洞察。毕竟内容策略的优化不仅仅只看那几个数字。