简介这份资源通过搜狗微信公众平台接口帮助爬虫开发者与数据分析师便捷获取公众号基本资料及历史文章。它围绕公众号信息查询与文章抓取两个典型场景展开两个Python脚本分别完成公众号ID匹配和文章内容采集并搭配Markdown文档说明接口参数、请求头构造及常见反爬应对思路适合有一定Python基础、想做公众号内容聚合或舆情监控的读者参考。压缩包为7z格式共3个文件含2个Python源文件和1个说明文档整体仅2KB代码精炼、无多余依赖易于阅读和二次改造。目前已有1719人学习/下载。通过阅读这套脚本读者能掌握搜狗微信搜索接口的调用逻辑、翻页与字段解析细节还可借鉴其中的延时、重试等基础反爬策略为后续批量采集或定时更新公众号数据提供直接可用的起点。1. 搜狗微信接口这条路不接官方不碰逆向公众号信息的中立入口微信官方没有开放面向第三方的公众号内容检索 API做过公众号开发的人都清楚你能管的是自己的号想看别人的历史发文只能一篇篇翻。搜狗微信公众号接口——也就是 weixin.sogou.com 这套 Web 搜索接口——成了业内事实上的中立通道它按「公众号」和「文章」两条路径收录微信生态内容市面上绝大多数公众号采集工具都是围着它封装的。这篇笔记把我自己跑通这条链路的完整过程拆给你从请求构造、Cookie 处理、公众号列表页解析到文章正文抓取与落库。新手照着第 3 章代码就能跑通第一轮采集熟手可以直接跳到第 5 章对照排查表。适合做公众号监控、舆情归档、知识库素材沉淀的开发者。2. 摸清搜狗微信接口的家底两类搜索、Cookie 体系与返回结构搜狗微信接口不是一个严格意义上的开放平台 API它本质上是一个带反爬的 Web 搜索页。既然是网页接口就得按网页的规矩来先维护会话再带参数请求最后解析 HTML。动手抓数据前我建议先把它的请求模型和返回结构搞明白否则代码写出来也是碰运气。2.1 接口的第一课type1 搜公众号type2 搜文章搜狗微信搜索的入口 URL 是https://weixin.sogou.com/weixin所有查询都通过这个地址的 URL 参数控制。最关键的参数是typetype1表示按公众号搜索返回的是公众号名片列表type2表示按文章搜索返回的是单篇图文结果。这两个路径服务的需求完全不同——做竞品监控先按公众号找号做热点追踪按文章找内容源。import requests base_url https://weixin.sogou.com/weixin params { query: 机器学习, type: 1, # 1 搜公众号2 搜文章 ie: utf8, s_from: input, _sug_: n, _sug_type_: } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://weixin.sogou.com/ } resp requests.get(base_url, paramsparams, headersheaders) print(resp.status_code, len(resp.text))这段代码是探测性的先确认自己的请求能拿到 200 和完整的页面文本。ieutf8保证中文关键词正确编码s_frominput表示模拟从搜索框提交_sug_和_sug_type_是搜索联想相关的参数保持默认值即可。Referer指向搜狗自身页面第一次请求缺它有可能被拦加上更稳。参数说明里有个容易被忽略的点query不需要手动 URL 编码requests的params字典会自动处理。但要注意type参数必须放在请求里缺省时搜狗会把它当成综合搜索返回的页面结构跟上面两种都不一样。2.2 Cookie 与请求头没有 SNUID 连搜索页都进不去搜狗会给每个新访客下发一个SNUID标识后续翻页、反爬验证都跟它绑定。如果你直接发裸请求不带 Cookie大概率拿到的是一个安全验证页而不是搜索结果。处理方式是用requests.Session()先访问一次搜狗首页让服务器把 Cookie 种下来之后的所有搜索都复用同一个会话。import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }) # 先访问一次首页让服务端种下 SNUID 等基础 Cookie session.get(https://weixin.sogou.com/, timeout10) # 带着会话里的 Cookie 再发起搜索 resp session.get( https://weixin.sogou.com/weixin, params{query: Python, type: 1, ie: utf8}, timeout15 ) print(session.cookies.get_dict()) print(resp.status_code)Session对象会自动管理 Cookie 的存储与回传不用手动维护Cookie请求头。打印get_dict()能看到至少包含SNUID和SUV两个键其中SNUID是后续翻页参数的基础。这里有个实际经验如果哪天搜索突然全部返回验证码页先去看 Cookie 是不是被重置了重置后重新走一遍「先访问首页再搜索」的流程通常能恢复。2.3 返回页面里的有效信息列表结构、跳转链接与反爬特征搜狗微信的搜索结果页是标准的 HTML 列表解析前先判断页面里有没有正常的列表节点这能帮你快速区分「正常结果」和「验证码页」。正常页面里公众号搜索结果和文章搜索结果的字段结构差异明显我用一张表把它们列出来type主要信息链接形式1公众号公众号昵称、微信号、认证信息、简介、最近文章标题站内/gzh?openidxxx页面2文章文章标题、摘要、公众号名、发布时间站内跳转链接落地到mp.weixin.qq.com抓取时我一般只信字段本身不轻信页面上的时间文字——搜狗显示的时间经常是「3天前」「昨天」这类相对时间落库时要么接受这种粗粒度要么进文章页取og:release_date的绝对时间。反爬特征也要提前写进代码验证码页面通常没有news-list节点而是出现一个包含「请输入验证码」的 iframe检查到这个特征就立即熔断不要继续发请求。2.4 使用边界抓回来不是到你手里就完了搜狗微信接口是公开网页不等于可以无限抓。它的反爬策略是渐进式的低频请求基本没人管高频了就开始弹验证码、封 IP。我自己的使用习惯是单 IP 抓取频率控制在 1 QPS 以下每次会话抓完主动歇几秒每天总量设个上限。另一个边界是内容版权。公众号文章版权归原作者抓下来的内容用于个人学习、内部调研、归档检索是常见且合规的但不要拿去做洗稿、内容农场或者二次转售。这条线自己心里要有数技术能解决的问题不能解决的只有分寸。3. 跑通第一轮采集用 Python 搜索公众号并解析列表页这一章解决「公众号列表页怎么获得」这个核心问题。从请求到解析每一步都会给出能直接跑的代码并说明为什么这么写。搜狗页面的 class 命名会随改版变化但只要把解析逻辑收敛到一个函数里页面变动时改一处即可。3.1 最小可用的公众号搜索代码公众号搜索的页面里每个结果是一个li节点里面包含公众号名称、微信号、简介和近期文章入口。用BeautifulSoup做结构化解析比手写正则稳得多——正则遇到标签嵌套和属性顺序变化就翻车选择器只要类名没变就能撑过大多数改版。import requests from bs4 import BeautifulSoup def search_gzh(session, keyword): 按关键词搜索公众号返回公众号基本信息列表 resp session.get( https://weixin.sogou.com/weixin, params{ query: keyword, type: 1, # 固定为公众号搜索 ie: utf8, s_from: input, _sug_: n }, timeout15 ) soup BeautifulSoup(resp.text, lxml) if not soup.select(ul.news-list li): print(未找到列表节点可能触发了验证码) return [] results [] for li in soup.select(ul.news-list li): name_node li.select_one(.txt-box .tit) wxid_node li.select_one(.info span) desc_node li.select_one(.txt-box p) results.append({ name: name_node.text.strip() if name_node else , wxid: wxid_node.text.strip() if wxid_node else , desc: desc_node.text.strip() if desc_node else , }) return results这段代码的核心在解析部分。先通过ul.news-list li定位所有结果条目再从每条里取三个字段。.info span对应的是微信号有时候搜狗不展示完整微信号只显示一串脱敏字符这个字段可能会是空或带星号入库时不要把它当主键。参数说明lxml解析速度比html.parser快得多但需要额外安装。解析前先检查列表节点是否存在这个检查逻辑能拦截掉大部分验证码页和页面改版导致的空结果。我见过有人在这个检查上偷懒结果把验证码页的空白内容全写进了数据库回头清洗数据时非常痛苦。3.2 翻页参数页码与 SNUID 的绑定关系搜狗微信搜索结果通常有多页翻页有两个关键参数page页码和b由 SNUID 与页码派生出的校验值。page1时可以不携带b但从第二页开始搜狗前端会用一个 JS 函数基于SNUID计算b参数。公开社区里流传着多种 Python 实现核心思路都是把SNUID和页码拼起来做摘要运算。import hashlib def gen_b(snuid: str, page: int) - str: 生成搜狗翻页参数 b 的常见占位实现。 搜狗前端算法会随改版更新实际使用时应以抓包得到的真实逻辑为准。 base snuid str(page) digest hashlib.md5(base.encode()).hexdigest() # 页码映射表用于让不同页码生成不同的尾部 mapping 0123456789abcdef return digest mapping[page % 16]这个函数是一个占位实现。真实生产环境里b参数的算法会随搜狗前端改版而变化长期依赖单一实现是有风险的。我的习惯是每次采集前先手动从浏览器里复制一个真实请求的b值反推当前算法特征再决定要不要复用旧代码。参数说明里要特别提醒翻页时页面里的page参数从 1 开始递增但b参数必须和同一个SNUID配套使用。如果你在会话中途清理了 Cookie新的SNUID会让旧的b参数全部失效表现为「第二页返回的还是第一页内容」。这种情况下不要硬试先重新走一遍首页种 Cookie 的流程。3.3 从公众号搜索到文章索引拿到 openid 与近期文章搜狗公众号结果里点「最近文章」会跳到一个站内页面/gzh?openidxxx这里的openid是公众号在搜狗侧的标识。拿到这个标识后就能抓取该公众号被搜狗收录的近期文章列表。注意这个列表不是完整历史通常只包含最近几篇到十几篇。def get_articles(session, openid): 通过 openid 抓取公众号近期文章索引 url fhttps://weixin.sogou.com/gzh?openid{openid} resp session.get(url, timeout15) soup BeautifulSoup(resp.text, lxml) articles [] for li in soup.select(ul.news-list li): title_node li.select_one(h3 a) if not title_node: continue time_node li.select_one(.s-p a) articles.append({ title: title_node.text.strip(), link: title_node.get(href, ), time: time_node.text.strip() if time_node else , }) return articlesgzh页面的列表结构跟搜索结果类似但每个条目里多了一个跳转链接。这里的link通常还是搜狗站内地址需要再跳一次才能得到mp.weixin.qq.com的最终链接——这个跳转逻辑放在第 4 章讲。字段time是搜狗显示的相对时间真要按时间排序得进文章页解析og:release_date。参数说明openid是 URL 路径参数而不是查询参数拼接时不要用params字典。另外搜狗对/gzh页面的反爬比搜索页更敏感连续抓取同一个公众号的多个页面时建议每次间隔 2-3 秒。3.4 落库设计两张表搞定公众号与文章的关系采集到的数据要及时落库否则进程一断全部白跑。我的表结构设计很直接一张表存公众号账号信息一张表存文章索引两张表通过openid关联。文章表用链接做唯一键天然支持去重。CREATE TABLE IF NOT EXISTS gzh_account ( openid TEXT PRIMARY KEY, name TEXT, wxid TEXT, description TEXT, updated_at DATETIME ); CREATE TABLE IF NOT EXISTS gzh_article ( url TEXT PRIMARY KEY, openid TEXT, title TEXT, summary TEXT, publish_time DATETIME, grabbed_at DATETIME );账号表里openid是搜狗侧标识wxid是微信号两个都别设唯一约束以外的东西——搜狗有时候不返回完整wxid拿它当主键会插入失败。文章表里url用最终的文章链接做主键这样重复采集时直接插入失败或跳过不需要额外的查询去重逻辑。grabbed_at这个字段容易被忽略它记录的是抓取时间不是发布时间。排查数据时效问题时有没有这个字段差别很大。我遇到过抓回来的文章列表里混着几个月前的旧文没有grabbed_at根本判断不了是增量失败还是搜狗收录延迟。4. 文章正文采集从临时链接到可读全文的完整链路第 3 章解决的是「有哪些号、有哪些文章」这一章解决「文章正文怎么安全落盘」。公众号文章正文本身在mp.weixin.qq.com但搜狗给的文章链接是带时效的跳转地址直接把搜狗链接入库明显不靠谱。正文抓取链路里最常翻车的三个点链接跳转、正文提取、图片防盗链我会逐个给出可复现的写法。4.1 先解决链接跳转搜狗为什么不肯给最终地址搜狗搜索结果里的文章链接点击后要先经过一个站内跳转再由服务端带到mp.weixin.qq.com。这个跳转地址通常携带签名参数时效很短隔天再访问大概率 404。常见做法是抓取时用requests自动跟随跳转直接拿最终 URL 入库。def resolve_link(session, sogou_link): 把搜狗站内跳转链接解析为 mp.weixin.qq.com 最终地址 resp session.get(sogou_link, allow_redirectsTrue, timeout15) return resp.url这个函数只有两行但作用很关键。allow_redirectsTrue让requests自动跟随 302 跳转最终resp.url就是落地后的mp.weixin.qq.com地址。解析完成后建议立刻拼接正文抓取不要只存链接等以后再用——签名的时效性决定了这个链接随时可能作废。参数说明如果resolve_link返回的 URL 还是weixin.sogou.com开头说明跳转没走完大概率是触发了安全验证。这时候返回的页面里没有mp.weixin.qq.com地址不要硬存。我一般在解析函数里加一道校验URL 里不含mp.weixin.qq.com就丢弃这条记录宁缺毋滥。4.2 正文提取readability 比手写正则稳定得多微信公众号文章页有固定的内容容器div#js_content但里面会混入推广卡片、引导关注文案、小程序跳转组件。手写正则清洗这些内容是个无底洞——今天过滤了「点击上方蓝字关注」明天又冒出「分享到朋友圈」。常见做法是交给通用正文提取库处理readability-lxml是一个稳定选择。from readability import Document def extract_content(html): 从微信公众号文章 HTML 中提取正文 doc Document(html) content_html doc.summary(html_partialTrue) return content_htmlDocument会基于页面结构和文本密度自动识别正文区域比手写规则健壮得多。summary(html_partialTrue)返回的是正文 HTML 片段而不是完整文档方便后续清洗和转换。参数说明readability偶尔会把「公众号名称 关注引导」这类页首信息带进正文因为它识别的是「文本最密集的区域」有时候页首也满足条件。我的处理方式是提取后再按行清洗维护一个过滤词列表把包含「点击关注」「长按识别二维码」「本文来源」等特征的行剔除。别指望一版清洗规则能用一年公众号的引导文案变着花样更新定期看样本调词表才是常态。4.3 图片与防盗链为什么图片在自己的页面里裂了公众号正文里的图片域名是mmbiz.qpic.cn这组 CDN 有防盗链校验。直接把抓下来的 HTML 原样放到自己的网站或笔记系统里图片大概率裂掉——浏览器请求图片时带的是你站点的RefererCDN 不认。解决办法是下载图片到本地下载时把Referer伪装成https://mp.weixin.qq.com/。import re from pathlib import Path def download_images(session, html, save_dir): 下载正文 HTML 中的微信图片到本地目录返回新旧路径映射 save_dir Path(save_dir) save_dir.mkdir(parentsTrue, exist_okTrue) mapping {} srcs re.findall(rimg[^]src([^]*mmbiz\.qpic\.cn[^]*), html) for idx, src in enumerate(srcs): try: resp session.get( src, headers{Referer: https://mp.weixin.qq.com/}, timeout15 ) if resp.status_code ! 200: continue ext .jpg local_path save_dir / f{idx:04d}{ext} local_path.write_bytes(resp.content) mapping[src] str(local_path) except Exception as exc: print(f图片下载失败: {src}, {exc}) return mapping这段代码的正则只匹配mmbiz.qpic.cn域名的图片地址避免误下载其他外部图床。带上Referer请求头就能正常通过防盗链校验。文件名用四位序号简单可靠映射关系返回到调用方由上层把 HTML 里的图片地址替换成本地路径。参数说明图片格式统一存成.jpg问题不大微信图片绝大多数是 JPEG少量 PNG 也能用 jpg 扩展名打开格式由内容决定扩展名不影响浏览器渲染。如果想做得更规范可以用resp.headers.get(Content-Type)判断真实格式。另外图片下载失败不要影响正文保存先记录日志后续再补拉。4.4 增量更新用 URL 指纹和时间戳做去重正文抓回来之后还要解决「下次采集怎么不重复」。我的做法是双保险文章链接做主键去重发布时间参与内容新鲜度判断。入库前先查一下库已存在的直接跳过省下的请求配额留给新内容。def is_new_article(conn, url): 检查文章 URL 是否已入库 cur conn.execute(SELECT 1 FROM gzh_article WHERE url ?, (url,)) return cur.fetchone() is Noneis_new_article在每次插入前调用用 SQL 主键约束兜底。这个函数看起来简单但能省下大量无效请求——搜狗的文章搜索结果里同一个链接会反复出现尤其关键词是热门领域时。参数说明发布时间统一用北京时间UTC8处理。微信公众号文章页里有og:release_date格式类似2025-11-20T08:30:0008:00解析时注意带时区偏移量不要直接按本地时间读否则会跟服务器时区不一致。5. 搜狗微信接口的五个常见坑从验证码到封 IP 的排查手册这一章是我跑了快两年采集链路后整理出来的踩坑清单。每一条都是先描述现象再讲根因最后给解决动作。如果你在抓取时遇到类似问题直接按解决步骤操作能省下不少排查时间。5.1 页面突然只剩一个验证码 iframe触发了安全验证现象解析出来的列表是空的打印 HTML 发现里面没有news-list只有一个包含验证码 iframe 的页面。请求状态码依然是 200但页面内容已经完全不是搜索结果了。原因同一 IP 短时间内的请求频率超过了搜狗的安全阈值服务端把你判定为脚本流量。这个阈值不是固定的跟 IP 历史行为、请求间隔、是否带完整请求头都有关。解决立刻停止当前任务等 5 到 10 分钟让惩罚期过去清理 Session 里的 Cookie 重新种一遍把请求频率降到 1 QPS 以下。如果还需要更高的吞吐准备多个账号 Cookie 轮换或者走代理池分摊请求。验证码页的识别逻辑要写进采集框架里一旦检测到就自动熔断不要无脑重试。5.2 同一 IP 连续抓取被限流302 跳转到反爬页现象请求返回 302落地地址是weixin.sogou.com/antispider/相关路径另一种情况是状态码 200 但页面内容空白或只有框架代码。原因这是 IP 维度的限流换新 Cookie 也没用因为限制的是出口 IP 而不是会话标识。通常在集中抓取半小时到一小时后出现触发条件跟请求总量和频率都有关。解决加代理池让请求分散到多个 IP每个 IP 设置每日请求量上限超过就摘除监控请求的失败率一旦连续失败比例超过 20%主动降低全局抓取速率。还有一个经验搜狗对同一 IP 的公众号搜索和文章搜索是分开计量的必要时可以错峰使用两种模式。5.3 翻页时 b 参数失效第一页正常第二页必挂现象page1请求正常返回page2带上了b参数后返回的仍然是第一页内容或者直接弹验证码。原因b参数与SNUID强绑定会话中途 Cookie 被重置或者b参数的生成算法跟当前页面的前端版本不匹配。旧代码里写死的算法在新版本上线后自然失效。解决翻页前检查 Session 里的SNUID是否还在抓包看当前真实请求里的b参数长什么样跟自己的生成结果对比在新算法稳定前用浏览器 Cookie 兜底让每次翻页都复用同一个带完整 Cookie 的会话。别把这个参数当成永久契约它是前端代码的一部分前端改了它就得跟着改。5.4 文章链接 24 小时过期存了链接等于白存现象入库的搜狗文章链接第二天打开提示页面不存在或者跳转到搜狗首页。文章本身还在但通过旧链接访问不了了。原因搜狗给出的跳转链接携带临时签名设计上就不支持长期保存。这是预期行为不是 bug只是很多人第一次抓的时候没意识到。解决抓取时立即解析跳转、立即抓正文、立即落库不要存链接等以后再处理。整套流程要在一次会话里完成拿到搜狗链接后马上resolve_link然后抓正文再把最终 URL 和正文一起入库。如果确实需要长期维护文章快照定期用公众号的/gzh页面重新拉取最新列表增量更新。5.5 关键词查不到公众号不是 bug是收录范围现象用公众号全名去搜返回结果里却没有目标公众号或者排名非常靠后。换另一家正常收录的号对比结果正常。原因搜狗对公众号的收录本来就不是全量的。部分新注册账号、低频更新账号、或特定领域的账号可能长期不被收录另外查询词太宽泛时搜索结果会被高权重账号挤占。解决换type2按文章搜索通过目标公众号的文章反查账号信息用更精确的关键词比如「公众号名 认证主体」的组合也可以试试搜狗微信的小程序入口收录范围跟 Web 版不完全一致。如果某个号始终搜不到先确认它是不是真的被收录了别在代码里反复重试消耗配额。6. 采集之后把公众号文章整理成可检索的知识库数据抓回来只是第一步。我平时最主要的用途是把公众号内容沉淀成团队内部可检索的知识库——把正文转成干净的 Markdown图片本地化目录按「公众号名/日期-标题」组织再整体导入知识库系统。下面是正文 HTML 转 Markdown 的简化逻辑。from bs4 import BeautifulSoup def to_markdown(content_html, img_mapping): 把正文 HTML 转换为简化 Markdown图片替换为本地路径 soup BeautifulSoup(content_html, lxml) for tag in soup([script, style]): tag.decompose() # 替换图片地址为本地路径 for img in soup.find_all(img): src img.get(src, ) if src in img_mapping: img[src] img_mapping[src] lines [] for child in soup.descendants: if child.name in (p, h1, h2, pre): text child.get_text(stripTrue) if text: prefix {h1: # , h2: ## , pre: \n}.get(child.name, ) lines.append(prefix text) return \n\n.join(lines)这段代码先删除脚本和样式标签再替换图片路径最后按段落和标题层级输出 Markdown 文本。它并不追求覆盖所有排版格式——公众号正文的排版本来也不复杂把标题和段落保住代码块原样输出就已经足够支撑绝大多数知识库检索场景。图片映射来自第 4 章的下载结果替换后 Markdown 里的图片路径指向本地文件整体迁移时不会裂图。我自己跑这条链路快两年最深的教训是「先把正文存下来再想怎么用」。搜狗侧的链接会过期、页面会改版、频率限制会收紧但已经落到数据库和磁盘上的内容才是你自己的资产。接口参数可以慢慢调数据没了就真的没了。希望帮到你。本文还有配套的精品资源点击获取