尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于PC微信的公众号历史文章数据采集方案

基于PC微信的公众号历史文章数据采集方案 简介wechat_spider 是一款基于 Node.js 的微信爬虫工具核心通过中间人代理原理批量获取微信公众号文章数据包括阅读量、点赞量、在看数、评论和正文内容面向需要采集公众号数据的开发者、运营或数据分析人员可在个人电脑或服务器运行支持 Docker 部署。压缩包共 80 个文件包含 40 个 JavaScript 源码文件、9 个 JSX 界面文件、Dockerfile 与 docker-compose 配置、证书文件、前端静态资源及说明文档整体约 1.13MB主要 JS 代码实现代理解析、数据存储和 API 服务多个模型文件对应文章、评论、公众号等数据结构目前已有 2619 人学习查看。解压后可见清晰的项目结构涵盖客户端构建、服务端接口、代理规则、数据处理与导出脚本还附带根证书生成文件和 Docker 部署配置适合学习 AnyProxy 中间人代理、HTTPS 抓包解析与爬虫工程化实践。通过阅读与运行本项目可以快速掌握微信文章批量采集的实现思路并在此基础上二次开发。 做公众号内容分析和竞品调研最折磨人的不是分析本身而是数据从哪来。官方后台只能看自己账号的数据第三方数据平台对历史文章、阅读量、点赞量、评论这些字段经常缺胳膊少腿手动一篇篇复制又反人类。折腾一圈之后我照着 wechat_spider 的思路从 PC 微信进程入手把公众号历史文章链接批量采下来再逐个抓正文、阅读量、点赞量和评论最后落到 SQLite 里慢慢分析。这篇文章就把我完整的落地方案、核心代码片段、表结构设计和踩过的坑写清楚想自建公众号数据采集工具的内容运营、数据分析和后端开发都可以直接参考。我先说明一个前提这套方案是在自己的电脑上、用自己的微信号做本地调试数据量控制在正常阅读速度以内不要突破微信的正常交互逻辑去搞大规模批量任务。下面进入正题。1. 为什么绕开公众号后台和第三方平台从 PC 微信进程入手1.1 官方后台的天然边界如果只运营自己的公众号后台自带的“内容分析”完全够用。但你想看竞品公众号、看同行的爆款规律、追踪某篇文章的传播数据官方后台一点办法都没有。它只开放自己账号的数据别人的历史文章、阅读量、点赞量都拿不到。1.2 第三方数据平台的硬伤市面上做公众号数据监测的第三方平台不少我用过之后发现几个问题一是数据更新有延迟爆文出现当天往往查不到完整数据二是字段不完整有些平台只有阅读量没有评论内容有些只有文章标题没有正文三是收费和配额限制免费版只能看最近几篇付费版单价不低。对个人开发者来说这种投入不太划算。1.3 搜狗微信的限制搜狗微信是一个看似可行的入口但它有搜索上限通常只能搜到公众号最近十篇左右的内容历史文章覆盖不到。而且搜狗微信的搜索结果是去重后的页面拿到的数据格式也不适合做统计分析。1.4 PC 微信进程采集的核心优势最后回到 PC 微信进程这条路。公众号文章在 PC 微信里被打开时客户端会向微信服务器发起请求历史消息列表也是一页一页加载出来的。这些请求和响应都在本地我们可以通过本地流量采集的方式把这些会话内容捕获下来。只要你在 PC 微信里正常浏览一个公众号的历史消息采集端就能拿到完整的文章列表和链接。这相当于把 PC 微信当成一个“数据浏览器”我们只是在一旁记录它产生的数据流。相比抓包服务器、逆向 App这个方案成本更低字段更全也不依赖第三方接口。适合做竞品选题分析、历史爆款规律挖掘、作者原创内容追踪这一类偏分析型的场景。2. 核心链路历史消息翻页、文章列表、文章统计是怎么串起来的2.1 公众号“历史消息”背后的接口在 PC 微信里点开一个公众号的聊天窗口再点右上角的历史消息入口微信会请求一个主页接口大致长这样https://mp.weixin.qq.com/mp/profile_ext?actionhome__bizMzA3...key...这个接口返回的是公众号主页信息真正的内容还在后面。当页面往下滚动时微信会继续请求加载更多的接口里面带着actiongetmsg这样的参数返回的 JSON 里才包含文章列表。2.2 翻页机制的规律每次加载更多响应里都会带两个关键字段next_offset和can_msg_continue。can_msg_continue表示是否还能继续翻页next_offset是下一次请求要带的偏移量。这跟朋友圈“下拉加载更多”是同一个逻辑只是把手势换成了参数。2.3 从响应中提取文章链接加载更多的响应里有一个general_msg_list字段它是一个 JSON 字符串里面的list数组就是文章消息。每一条消息里有一个appmsg对象包含title标题、digest摘要、cover封面图、url文章链接等字段。这里要注意说好的“公众号所有历史文章链接”实际操作时并不是一次接口就能拿完的而是跟着偏移量一页一页滚出来的直到can_msg_continue为 0。2.4 文章统计数据的来源拿到文章链接之后阅读量、点赞量、评论数据需要单独请求一个统计接口。PC 微信打开一篇文章时会并行请求类似这样的地址https://mp.weixin.qq.com/mp/getappmsgext?__biz...appmsg_type9mid...idx...sn...key...这个接口返回的 JSON 里有一个appmsgstat对象里面有read_num阅读量、like_num点赞量、old_like_num在看量还有一个comment对象里面是精选评论和评论总数。2.5 正文内容的获取方式正文反而是最简单的一步。文章链接拿到之后直接用 HTTP 客户端请求响应是一段 HTML正文主体在idjs_content的节点里。要注意的是PC 微信打开文章时会带特定的 Referer 和 User-Agent我们请求时最好也保持一个合理的浏览器头否则部分图片和样式会被拦截。整个数据链路串起来就是本地采集服务捕获 PC 微信的会话识别公众号主页和历史消息接口提取文章链接列表再对每篇文章请求统计接口和正文页面最后把数据落库。3. 环境准备与项目骨架3.1 技术选型wechat_spider 这个方向最成熟的方案是 C# 加本地流量采集库我沿用了这个思路。核心采集用 C# 写因为捕获本地微信会话的组件是 .NET 生态的接入最简单JSON 解析用 Newtonsoft.Json数据存储用 SQLite单文件、零配置分析阶段非常方便。为什么不全用 PythonPython 做数据分析确实顺手但 PC 微信会话捕获这一层绕不开 C# 的现成组件。我的做法是 C# 负责采集和入库后面分析阶段把 SQLite 文件丢给 Python 或直接写 SQL 都行分工明确。3.2 依赖清单在 Visual Studio 里创建一个控制台项目然后通过 NuGet 安装以下包FiddlerCore本地流量采集组件核心依赖Newtonsoft.JsonJSON 解析System.Data.SQLite 或 Microsoft.Data.SqliteSQLite 访问3.3 微信版本与运行环境PC 微信要固定在一个版本上关闭自动更新。原因很直接微信客户端一旦升级请求参数、接口路径、加签规则都可能有变化采集代码就会失效。我目前固定在一个比较常见的 PC 版本上采集链路跑得没问题。运行环境只建议在本地开发机不要部署到多台机器或者云服务器上批量跑。本地单账号、低频次是这套方案最安全的使用姿势。启动采集服务之前确保 PC 微信已经登录并且先手动打开一次目标公众号的历史消息页让微信完成主页加载。这样后续采集时key等动态参数才处于可用的状态。4. 动手写代码从拦截历史消息到入库4.1 捕获本地会话FiddlerCore 的核心用法是注册两个事件请求前和响应后。我们关心的是响应内容所以主要处理BeforeResponse回调。using System; using Fiddler; class WeChatSpider { static void Main(string[] args) { FiddlerApplication.BeforeResponse OnBeforeResponse; FiddlerApplication.Start(); Console.WriteLine(正在捕获本地微信会话按回车退出...); Console.ReadLine(); FiddlerApplication.Shutdown(); } static void OnBeforeResponse(Session session) { if (session.fullUrl.Contains(mp.weixin.qq.com/mp/profile_ext) session.fullUrl.Contains(actiongetmsg)) { HandleHistoryMessage(session); } } }这段代码的作用是PC 微信每次加载公众号历史消息列表时响应都会经过这个回调函数我们在这里做文章链接提取。4.2 解析历史消息列表HandleHistoryMessage方法的重点是从响应 JSON 里取出general_msg_list再遍历里面的文章。static void HandleHistoryMessage(Session session) { var body session.GetResponseBodyAsString(); var json JObject.Parse(body); var canContinue json[can_msg_continue]?.Valueint() ?? 0; var nextOffset json[next_offset]?.Valuestring(); if (canContinue 1 !string.IsNullOrEmpty(nextOffset)) { var nextKey json[key]?.Valuestring() ?? ; // 这里可以记录下一次翻页要用的 key 和 offset按需继续请求 } var listJson json[general_msg_list]?.ToString(); if (string.IsNullOrEmpty(listJson)) return; var list JObject.Parse(listJson)[list]; foreach (var msg in list) { var appmsg msg[appmsg]; if (appmsg null) continue; var title appmsg[title]?.ToString(); var link appmsg[url]?.ToString(); var digest appmsg[digest]?.ToString(); var cover appmsg[cover]?.ToString(); var publishTime appmsg[comm_msg_info]?[datetime]?.Valuelong() ?? 0; if (string.IsNullOrEmpty(link)) continue; // 这里把文章基础信息写入数据库 SaveArticle(link, title, digest, cover, publishTime); } }注意publishTime是 Unix 时间戳入库时可以直接存整数分析时再转换。4.3 抓取文章统计信息文章基础信息落库之后还需要读取量、点赞量、评论。这个请求的关键参数是__biz、mid、idx、sn、key它们要么在历史消息主页接口的 URL 里要么在文章链接里可以解析出来。static void FetchArticleStats(string articleUrl, string biz, string key) { var mid ExtractParamFromUrl(articleUrl, mid); var idx ExtractParamFromUrl(articleUrl, idx); var sn ExtractParamFromUrl(articleUrl, sn); var apiUrl $https://mp.weixin.qq.com/mp/getappmsgext? $__biz{biz}appmsg_type9mid{mid}idx{idx}sn{sn}key{key} $is_need_hot_comment1; using var http new HttpClient(); http.DefaultRequestHeaders.UserAgent.ParseAdd(Mozilla/5.0); http.DefaultRequestHeaders.Referrer new Uri(https://mp.weixin.qq.com/); var resp http.GetStringAsync(apiUrl).Result; var json JObject.Parse(resp); var stat json[appmsgstat]; if (stat null) return; var readNum stat[read_num]?.Valueint() ?? 0; var likeNum stat[like_num]?.Valueint() ?? 0; var oldLikeNum stat[old_like_num]?.Valueint() ?? 0; // 评论信息在 json[comment] 里这里只统计个数 var commentCount json[comment]?[elected_comment]?.Count() ?? 0; SaveArticleStats(articleUrl, readNum, likeNum, oldLikeNum, commentCount); }key是动态变化的如果长时间不刷新统计接口会失效。我的处理方式是在历史消息主页响应里维护一个内存中的 key 缓存每个公众号对应一个 key过期就重新打开一次历史消息主页刷新。4.4 正文内容抓取正文抓取相对简单直接请求文章链接然后用正则或 HTML 解析库把idjs_content节点扣出来。static string FetchArticleContent(string articleUrl) { using var http new HttpClient(); http.DefaultRequestHeaders.UserAgent.ParseAdd(Mozilla/5.0); http.DefaultRequestHeaders.Referrer new Uri(https://mp.weixin.qq.com/); var html http.GetStringAsync(articleUrl).Result; var match Regex.Match(html, id\js_content\[^]*(.*?)/div, RegexOptions.Singleline); return match.Success ? match.Groups[1].Value : ; }这里有一个我在实际中发现的细节同样的文章链接如果 Referer 设置不正确正文里的图片可能裂掉。保持 Referer 为https://mp.weixin.qq.com/基本能解决。5. SQLite 表结构设计与增量更新5.1 三张表的设计我设计了三张表文章基础信息表、统计数据表、评论表。分开存的好处是历史文章只存一遍阅读量、点赞量可以多次抓取更新不会污染基础信息。CREATE TABLE IF NOT EXISTS tb_article ( id INTEGER PRIMARY KEY AUTOINCREMENT, biz TEXT NOT NULL, title TEXT, digest TEXT, cover TEXT, link TEXT UNIQUE, mid INTEGER, idx INTEGER, sn TEXT, publish_time INTEGER, content_html TEXT, created_at INTEGER DEFAULT (strftime(%s, now)) ); CREATE TABLE IF NOT EXISTS tb_article_stats ( id INTEGER PRIMARY KEY AUTOINCREMENT, link TEXT NOT NULL, read_num INTEGER, like_num INTEGER, old_like_num INTEGER, comment_count INTEGER, update_time INTEGER ); CREATE TABLE IF NOT EXISTS tb_comment ( id INTEGER PRIMARY KEY AUTOINCREMENT, link TEXT NOT NULL, nickname TEXT, content TEXT, like_count INTEGER, create_time INTEGER );5.2 增量更新的关键点文章表里的link字段设了唯一索引入库时用INSERT OR REPLACE或者先查询再插入避免重复数据。统计表不设唯一索引因为同一篇文章在不同时间点的数据是正常的保留多条可以画出阅读量的增长曲线。翻页时以publish_time作为停止条件。比如我只关心最近一年的文章当某页最早的文章发布时间早于一年前就可以停止继续翻页节省大量请求。5.3 重新采集策略对已入库的文章默认跳过正文抓取只更新统计表。因为正文基本不会变但阅读量、点赞量会一直变。这个策略在追踪爆文增长趋势时特别有用。6. 实测中的三个大坑与完整排查过程6.1 阅读量和点赞量全部为 0第一次跑通链路后我满心期待地去看统计数据结果发现所有文章的阅读量、点赞量全是 0但接口返回的 JSON 里appmsgstat对象是存在的只是里面的值都是 0。排查链路先打印完整返回 JSON确认appmsgstat字段存在说明接口通了。对比浏览器里手动打开文章时的统计接口请求发现我构造的 URL 少了appmsg_type9和data_type1这类参数。补上参数后仍然为 0。再把请求里的key和历史的key对比发现 PC 微信的key是随会话变化的而且有时效。过期之后接口不会报错而是静默返回 0。最终方案每次抓统计信息前先解析最新一次历史消息主页响应里的key并且即时刷新。这个坑最大的迷惑点在于接口不报错数据却是坏的。排查时一定要先抓完整返回体再逐个参数和浏览器里的正常请求做对比。6.2 文章链接提示“链接内容不属于当前公众号”跑了一段时间后我发现从某几个公众号的历史消息里抓下来的链接手动点开时提示“链接内容不属于当前公众号”但当时确实是从这个公众号的历史列表里拿到的。排查链路对比文章 URL 里的__biz和当前公众号主页 URL 里的__biz发现两者不一致。进一步看公众号历史消息列表里偶尔会混入推荐位的内容推荐的是其他公众号的文章因此__biz不同。结论历史消息列表里不是 100% 属于当前公众号的文章。处理方案入库前校验__biz不一致的直接跳过不写入文章表。这个热词对应的现象其实很常见。如果你在做全量历史文章采集一定要在解析层就把__biz过滤条件加上否则后面分析时会出现大量脏数据。6.3 翻页高频触发验证码连续翻页采集几十个公众号之后微信开始弹验证码接口返回类似“当前环境异常请完成验证”的内容。这是频率被限制的典型表现。排查链路先确认不是 IP 层面的问题因为本地单机、单账号很少会触发 ip 限制。统计每个公众号的请求间隔发现过快时就会触发慢一些就正常。最终方案每个公众号处理完之后随机睡眠 3 到 8 秒同一个公众号内串行翻页不做并发触发验证码后停止该公众号 5 分钟再指数退避重试。我建议把采集频率控制在“人工浏览”的节奏附近。你想想一个人翻历史消息是不可能几秒钟翻几十页的程序也一样。6.4 微信自动升级导致解析规则失效还有一次是 PC 微信自动升级后历史消息列表的响应结构变了general_msg_list解析出来为空。排查时才发现版本变了。处理方式是固定 PC 微信版本并且在系统层面停掉微信的自动更新服务避免意外升级。7. 这套方案我用在哪边界怎么划现在这套采集流程已经被我固定成了每周一次的定时任务专门用来做公众号内容选题库。采集范围是我自己关注的几十个垂直领域公众号数据落到 SQLite 之后我会按阅读量排序看本周的爆文标题按点赞量看用户真正认可的内容方向偶尔也会对某篇文章做持续的阅读量追踪观察它的传播周期。边界方面我自己给自己定了三条只采集自己有权浏览的公众号内容不做个人用户隐私数据的采集和公开请求频率保持在人工操作范围内不追求极限速度数据只用于个人分析和内容研究不对外售卖、不批量导出公开传播。其实只要守住这三点这个技术方向就是一个非常顺手的公众号内容分析工具而不是什么黑产手段。最后再分享一个小技巧如果你只是想快速验证某个公众号值不值得深入分析不需要上来就全量采集先跑通历史消息主页接口抓最近十篇文章的链接和阅读量就够了。数据量小、耗时长也不容易触发各种限制。等分析框架搭好、确认这个公众号值得长期跟踪再放开翻页深度去拿完整历史数据这样整个项目推进起来稳妥很多。本文还有配套的精品资源点击获取
返回列表