用Fiddler+Python抓取微信公众号历史消息:从接口抓包到CSV落地
2026/9/18 2:11:28 网站建设 项目流程

环境准备、接口定位、参数拆解、代码落地,再加上最后"跑久了才会遇到"的坑,一条线走完,你照着操作,从装好Fiddler到跑出第一份CSV,大概就是标题里说的5分钟量级。

1. 历史消息接口是什么,为什么值得专门分析

1.1 从"人肉翻历史消息"到"直接问接口"

微信公众号的历史消息页,就是点进公众号主页后那个"查看历史消息"的入口。就我平时接触过的场景来说,需求大概分两类:一类是给自己做个公众号备份,防止文章被删;另一类是竞品分析、选题调研,想看某个垂直领域公众号过去一年发了什么。手动翻的话,一次翻个几十篇还凑合,要翻几百篇甚至几千篇,手指都酸了。

其实这些数据根本不是"页面"而是"接口返回的JSON"。你在手机上用手滑动一次,微信就向服务器发一次请求,服务器返回一批文章的数据,然后客户端负责渲染成你看到的样子。我们要做的,就是通过Fiddler把这条请求和响应截获下来,搞清楚它带了什么参数、返回了什么结构,再用Python把同样的请求发一遍,拿到结构化的数据,存成CSV或Excel。

这个思路适用于绝大多数App和网页端的数据采集,不只是微信公众号。之所以拿"历史消息接口"开刀,是因为它足够典型——带签名参数、带分页游标、返回嵌套JSON,这几个特征在其它接口里几乎也都会遇到。把这条链路跑通,你分析别的接口基本就是复制粘贴的路子。

1.2 Fiddler + Python 的组合为什么最顺

市面上抓包工具有不少,Charles、Fiddler、Wireshark,还有人直接用浏览器DevTools,但我个人做App接口分析还是优先Fiddler。

  • 轻量,绿色版解压就能跑,不像Charles要先折腾Java环境。
  • HTTPS解密配置很直白,钩子勾上、证书一装就能看到明文,对新手友好。
  • 会话列表清晰,支持按域名、按URL关键字过滤,这个在微信流量里非常重要,因为微信的请求太多了,不过滤基本没法看。
  • FiddlerScript支持改请求和响应,虽然我们这次用不到,但后续做mock返回数据时很香。

Wireshark的定位不一样,它是网卡级抓包,能看到TCP/IP层的东西,但要对HTTP明文内容做还原就得费点劲,不适合快速看接口。Charles在macOS上确实顺手,不过它是付费的,Fiddler对Windows免费,受众更广。加上Python这边用requests库,几十行就能把逻辑写清楚,整套技术栈没有任何冗余。

下面进入正题,按实际操作的顺序走一遍。

2. 动手前的三个准备:HTTPS解密、代理、证书

2.1 打开Fiddler的HTTPS解密开关

默认情况下Fiddler只能看到HTTP明文流量,而微信的所有接口都是HTTPS,所以第一件事是开启解密。

打开Fiddler后进入Tools -> Options -> HTTPS,勾选Capture HTTPS CONNECTsDecrypt HTTPS traffic。勾完它会提示安装根证书,一路确认就行。如果弹出自定义根证书警告,选择“是”信任。

这一步做完,Fiddler会生成一个名为DO_NOT_TRUST_FiddlerRoot的根证书,后续手机/模拟器装的也是它。需要注意的是,某些环境下杀毒软件会对这个根证书报警,因为它们对“中间人证书”天然敏感。我自己在Win11上遇到过Defender拦截,需要在信任区放行一次,否则Fiddler起不来。

2.2 手机和电脑连到同一个局域网

接下来要让手机或模拟器走Fiddler的代理。

  • 手机和电脑连同一个Wi-Fi。
  • 手机Wi-Fi设置里手动配置代理,IP填电脑的局域网IP,端口默认8888。
  • 电脑上如果开了防火墙,记得放行Fiddler的8866/8888端口(默认8888)。

怎么快速查电脑局域网IP?Win+R输入cmd,然后ipconfig,找IPv4 地址那行。我见过新手在这里填了外网IP,折腾半天连不上。记住,一定是内网IP,192.168.x.x或10.x.x.x开头那种。

配置完代理,用手机浏览器访问http://ip:8888,Fiddler的证书下载页面就出来了。也可以直接在Fiddler里用QuickExec输入!cert弹出证书安装向导,但手机访问的方式更通用。

2.3 证书装完还要在系统里"信任",这一步很多人卡住

证书下载后要安装到手机系统里,但安装完成不代表生效。

  • iOS系统:下载证书后,去设置 -> 通用 -> 关于本机 -> 证书信任设置里面,把Fiddler的证书开关打开。如果不做这一步,抓到的HTTPS请求会显示Tunnel to ...:443,看不到明文。
  • Android系统:分两种情况。Android 7.0以下,用户装的证书会被微信信任;Android 7.0及以上,App默认不信任用户证书。微信的场景下,低版本Android还好说,高版本直接抓不到,这在业内是个通用的坎儿。常规思路是让Fiddler同时监听两个端口:8888正常收发流量,8866用于向手机下发证书和配置(Wi-Fi代理时填8866),然后手机通过这种方式安装Fiddler的CA证书到系统证书目录。具体可以参考Fiddler官方文档的移动端抓包章节,根证书方案的适用范围会更好一些。

不过说实话,微信公众号历史消息接口的正常生效场景,在国内手机上通常是在微信内置浏览器里直接访问公众号主页触发的。在这种场景下,如果抓包环境一直显示Tunnel,优先检查证书是否被系统信任,其次是确认微信版本没有启用额外的网络校验。还有一个土办法:如果真机上实在抓不透明文,可以换Android模拟器配合旧版微信做分析,调通接口逻辑后再拿到真机验证。原理和参数都是一样的,Fiddler里看到的请求头、请求体不会因为设备不同而变化。

3. 在Fiddler里找到微信历史消息的appmsg接口

3.1 触发一次有效请求

准备工作做完后,按下面顺序操作:

  1. 手机/模拟器上打开微信。
  2. 进入一个公众号的主页,点击右上角的“...”或直接进历史消息页,再退出,反复几次即可。
  3. 打开Fiddler,你会看到大量来自微信的请求,混在一起非常乱。

如果你使用的Fiddler版本支持分支过滤会话(左侧会话列表上方的Filters页签),可以直接切到Filters,勾选Use Filters,在Host栏填微信API常见的域名后缀,比如mp.weixin.qq.com。这个域名基本承载了绝大部分公众号相关的页面和接口,过滤后会话列表会清爽很多。

3.2 认出关键请求的三个特征

微信历史消息页对应的接口,在Fiddler里看就是一个典型的GET请求,Host为mp.weixin.qq.com,路径是/mp/profile_ext或者/mp/appmsg,具体看微信版本。老一点的微信走/mp/profile_ext,新版大多走/mp/appmsg

URL参数里通常能看到下面几个关键字段:__bizappmsg_tokenpass_ticketuinkey

![这里省略了一张Fiddler会话截图,你本地抓包时按上面说的特征找即可]

认准这几个参数名,基本百发九十九中。要注意的是,__biz是一串Base64编码的内容,内容是公众号的原始ID,看起来像MzA3NDI0NjE4NQ==keyuin在不同请求间会变化,特别是key,它相当于分页游标,第一次请求不带,翻页之后带上。

3.3 快速验证你没找错

选中最像的那个请求,右侧点击Inspectors -> Headers,往下翻到请求头区域,看Referer。历史消息页的请求,Referer一定是https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=...这种格式,里面也有__biz参数。同时请求头里大概率出现User-Agent,内容是微信内置浏览器的UA标识。

再点开JSON页签看响应体,返回的JSON结构通常是:

{ "app_msg_list": [ { "title": "文章标题", "link": "文章链接", "digest": "文章摘要", "cover": "封面图URL", "publish_time": 1710000000 } ], "can_msg_continue": true, "next_offset": 20 }

看到这个结构,基本就实锤了。

4. 接口参数拆解:从"能用"到"清楚为什么能用"

拿到一个接口后,啪啪复制参数能跑通是一回事,搞清楚每个参数干嘛的是另一回事。后者决定了当你遇到“请求失败”时能不能自己排查。

4.1 参数表与含义

参数名含义是否必填备注
__biz公众号唯一ID(Base64编码)只要换公众号,这个值就变
appmsg_token访问历史消息的临时令牌有时效性,过期后需要重新进入历史消息页获取
pass_ticket会话凭证和用户登录态绑定
uin用户唯一标识最后一个参数,通常带?,后面接key
key分页游标翻页时必填第一页不传,最后一页传空
fjson固定值
count每页数量10到20左右

appmsg_tokenpass_ticket怎么来的?它们是微信服务器在页面加载时下发的加密参数,你直接复制URL里的即可带走,也可以在请求头里的Cookie中找到对应字段。实际开发中,我们可以每次运行脚本前手动用Fiddler抓一次这两个值填进代码里,也可以做一个前置请求去自动获取,但自动获取需要先模拟页面跳转,复杂度高不少。我个人的建议是先用硬编码跑通,后续再考虑自动化。

4.2 翻页逻辑和next_offset的推进

第一页请求,URL尾部是这样的:

...&f=json&count=10&action=home&next_offset=0

响应中返回can_msg_continue=truenext_offset=10,把next_offset的值填到下一次请求的next_offset参数中,同时带上上一页返回的key,就能翻到下一页。

实际情况是,有的接口第一页不返回key,第二页才返回,所以你需要在循环里判断:如果key还没拿到,就等下一轮响应再取。

翻页终止条件有两个:can_msg_continue变为false,或者app_msg_list为空数组。两个条件满足任意一个就停。

4.3 请求头里面少了什么会失败

请求头里这几个字段,少一个都可能让你得到-3-6之类的错误码:

  • User-Agent:必须是微信内置浏览器UA,不能是普通Chrome的UA。很多人在这翻车,因为直接用Python默认UA会触发拦截。
  • Referer:必须是公众号主页URL,且带__biz参数。
  • Cookie:包含pgv_pvipgv_sinews_comm_info等字段雪崩组合,其中部分可以留空,但uinpass_ticket对应的Cookie必须有。

建立了完整参数的认知,下面就可以写代码了。

5. Python代码实战:完整脚本与逐行说明

5.1 代码总览

下面的脚本适用于Python 3.8+,依赖requests。运行前先装库:

pip install requests
import requests import json import csv import time from urllib.parse import urlencode, parse_qsl, urlparse # ============ 你需要替换的部分 ============ BIZ = "MzA3NDI0NjE4NQ==" # 公众号 __biz 参数 APPMSG_TOKEN = "你的_appmsg_token" # 从Fiddler里抓到的token PASS_TICKET = "你的_pass_ticket" # 从Fiddler里抓到的ticket UIN = "你的_uin" # 用户标识 COOKIE = "你的完整Cookie,从Fiddler请求头里复制" # ========================================== HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36 NetType/WIFI Language/zh_CN", "Referer": f"https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz={BIZ}&scene=124", "Cookie": COOKIE, } BASE_URL = "https://mp.weixin.qq.com/mp/appmsg" def build_params(biz, token, pass_ticket, uin, key=None, offset=0, count=10): params = { "action": "list_ex", "begin": offset, "count": count, "f": "json", "from": "msg", "__biz": biz, "appmsg_token": token, "pass_ticket": pass_ticket, } # key 是游标,第一页不带,翻页带上 if key: params["key"] = key if uin: params["uin"] = uin return params def fetch_history(max_pages=50): all_articles = [] cursor_key = None offset = 0 page = 0 while page < max_pages: params = build_params( biz=BIZ, token=APPMSG_TOKEN, pass_ticket=PASS_TICKET, uin=UIN, key=cursor_key, offset=offset, count=10, ) resp = requests.get(BASE_URL, params=params, headers=HEADERS, timeout=10) try: data = resp.json() except json.JSONDecodeError: print(f"[第{page+1}页] 响应不是JSON,可能被拦截,打印文本前500字:") print(resp.text[:500]) break # 业务错误码处理 if data.get("ret") != 0: err_msg = data.get("err_msg") or data.get("msg") or data.get("ret") print(f"[第{page+1}页] 接口返回错误: {err_msg}") break article_list = data.get("app_msg_list", []) if not article_list: print("[停止] app_msg_list 为空,已抓完。") break all_articles.extend(article_list) # 翻页游标更新 can_continue = data.get("can_msg_continue") offset = data.get("next_offset", offset + len(article_list)) if "app_msg_list" in data: # 大部分情况下 key 从响应中取,如果响应没有就沿用上一次的 new_key = data.get("key") if new_key: cursor_key = new_key print(f"[第{page+1}页] 抓取到 {len(article_list)} 篇,累计 {len(all_articles)} 篇") if not can_continue: print("[停止] can_msg_continue 为 false,已到最后一页。") break page += 1 time.sleep(1) # 温和策略,避免请求过密 return all_articles def save_to_csv(articles, filename="wechat_history.csv"): if not articles: print("没有数据可保存。") return with open(filename, "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["title", "link", "digest", "cover", "publish_time"]) for item in articles: writer.writerow([ item.get("title", "").strip(), item.get("link", "").strip(), item.get("digest", "").strip(), item.get("cover", "").strip(), item.get("publish_time", ""), ]) print(f"已保存到 {filename}") if __name__ == "__main__": articles = fetch_history() save_to_csv(articles)

5.2 代码关键点解释

上面脚本里的几个地方,不是随便写的,每行都有讲究。

User-Agent。我写的是模拟手机端微信内置浏览器的UA。你完全可以直接从Fiddler里面复制完整请求的UA字段来填,比任何网上的模板都准。有些情况下服务端会校验UA里的MicroMessenger字段是否出现,没出现直接拒绝。

params 构建逻辑。列表页的offset并不是页面索引,而是偏移量。如果每页10篇,第一页offset=0,第二页offset=10,第三页offset=20,以此类推。有的版本接口用begin字段代替offset,两者本质相同,区别不大。我在脚本里用了begin,因为你Fiddler里看到的实际请求很可能就是begin

错误码判断。微信接口比较坑的一点是,HTTP状态码基本上是200,错误信息放在JSON的ret字段里。常见的ret=200003表示token过期,ret=200005表示频率限制或签名错误。所以代码里判断ret != 0时要打印出来,方便快速定位。

sleep(1)。每抓一页停1秒,这个节奏是我的一个平衡点。太快容易触发风控,太慢翻几百篇得等到天荒地老。实测每秒一页比较稳。

csv编码。保存CSV时用了utf-8-sig,而不是utf-8。这个细节很重要,因为Excel打开UTF-8编码的CSV会中文乱码,utf-8-sig会在文件头加上BOM标记,Excel就能正确识别。

5.3 一次运行结果示例

跑完脚本,控制台输出大概这样:

[第1页] 抓取到 10 篇,累计 10 篇 [第2页] 抓取到 10 篇,累计 20 篇 [第3页] 抓取到 10 篇,累计 30 篇 ... [第23页] 停止,can_msg_continue 为 false,已到最后一页。 已保存到 wechat_history.csv

打开CSV,每一行是一篇文章,标题、摘要、封面、链接、发布时间都在列。到这里,你已经从"打开公众号手动翻历史"变成"跑一条命令拿到全部历史记录"了。

6. 从抓包到落地:那些文档里不会写的坑和心得

6.1 接口返回空data并不一定是代码问题

我第一次用类似脚本的时候就遇到过:第一页app_msg_list有数据,第二页开始返回空,但can_msg_continue还是true。折腾很久,最后发现是key在翻页时丢失了。还有的情况是,页面刚要翻页时必须回传上一次响应中携带的一个continue标志,我在脚本里直接没带上,导致的报错。

排查这类问题时,推荐一个笨但稳妥的办法:用Fiddler抓两个真实请求——第一页和第二页——然后对比这两个请求之间哪个参数变了。所有变化点,就是你需要自己实现的状态流。

6.2 签名参数过期时间比你想象中短

appmsg_tokenpass_ticket不是永久的。实测下来,短则十几分钟,长则数小时,具体微信服务端动态判断。运行脚本如果中途出现大量错误码,最有效的处理不是改代码,而是回到微信里重新进一次公众号历史消息页,再回Fiddler取一次新token和ticket,换进代码重跑。

这也是"插入手动步骤"的典型场景:整个脚本可以做得很自动化,但在token更换这一环,手工介入反而是最省时间的。

6.3 频率控制与账号保护

微信对历史消息接口有风控逻辑,如果短时间请求过多,轻则返回频率提示,重则暂时限制部分功能。我在实际使用中一般遵循:单公众号采集总量几百篇以内,用上面脚本次序问题不大;要采集上千篇,就得考虑间歇暂停,比如每10页停5-10秒,再随机化sleep区间。

还需要注意的是,如果同时跑多个公众号的采集任务,建议逐个来,不要并发。并发请求等于主动把账号暴露在风控之下。

6.4 数据使用的合规边界

接口能拿到文章,不代表可以随便用。这里额外多说一句,个人备份、学习研究、小范围数据分析这些场景,影响一般可控;但把文章内容打包公开传播、甚至做成商业产品卖给别人,就有很大风险。我不建议对接口做绕过签名、突破访问频率限制之类的操作,脚本里也刻意没写任何绕过逻辑。技术是无罪的,但使用技术前先想想数据归属和用户隐私,这条底线别碰。

6.5 为什么有时候Fiddler抓不到微信公众号的TLS请求

这个问题在群里被问过很多次。现象是:手机代理配了,证书也装了,Fiddler能看到Tunnel to mp.weixin.qq.com:443,但点进去没有明文。常见原因有三个:

第一,证书没在系统层信任,微信作为App默认不信任用户证书。第二,微信部分版本启用了自带的安全网络框架,普通中间人方案会被忽略。第三,你用的是iOS高版本系统,用户证书和系统证书权限是隔离的。

对应解决办法分别是:换Android 7.0以下环境、用带系统证书安装能力的抓包机、或者按前面提到的双端口方案+系统证书安装处理。核心思路都是"让App信任你的根证书",而这一步没有Win10那种一键勾选,需要针对目标环境做适配。

我个人的建议是:正式做微信公众号采集分析时,不要在"绕过微信安全框架"这件事上花太多时间,因为微信的对抗策略更新很快。用合法的公开接口加上合理的频率控制,对高价值的小批量数据做分析,效率远比研究破解方案高得多。

7. 从单篇文章接口到"编辑推荐"接口:你会踩的第二个同构坑

你可能会说,历史消息搞定了,那公众号主页的自定义菜单、合集、专辑页呢?真巧,这些接口的抓取逻辑和上面几乎一模一样。小程序里公众号主页的appmsg_commentappmsg_likegetappmsgext,都属于同一批以appmsg为前缀的接口,差异主要是参数组合和返回字段。

我这里多说一个最常碰到的:getappmsgext是文章底部的阅读数、点赞数、评论列表接口。很多做数据分析的朋友想抓这个,但它的触发时机是"打开文章页之后",所以抓包时要先把文章链接打开、再在Fiddler里按getappmsgext过滤才能看到。它的请求参数里同样有__bizappmsg_tokenkey,另外多了mididx,分别代表消息ID和文章在消息内的序号。

理解了历史消息接口之后,再去看这些周边接口,你会有一种"万物都是换皮"的既视感——不是说你不用动脑,而是说核心分析思路已经成型了:先触发,再抓包,筛域名,找特征参数,看返回结构,最后写代码复现。

我现在做公众号数据采集,流程基本固定在10分钟以内完成一个新接口的对接,靠的就是上面这套打法。Fiddler本身没什么神秘的,它只是一个"让你看得见HTTP明文"的镜子,真正值钱的是你看懂参数之间关系的能力。这篇文章能帮你把第一面镜子擦亮,剩下的路,多抓两个接口自然就会了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询