weidu
New微读(weidu)——读 mp.weixin.qq.com 公众号文章的稳定姿势。绕开层层叠叠的反爬:Jina/WebFetch/curl 全被拦或返回验证码页。 名字寓意:「微」=微信,「读」=读得到;两个字直说"让你能读到微信公众号文章"。 方法论:PowerShell HttpWebRequest 拉原始 UTF-8 字节(避开 Invoke-WebRequest 的 UTF-8→GBK 编码污染)+ Python 双容器兜底(js_content 旧版 + content_noencode 新版沉浸式)。 当用户给出 mp.weixin.qq.com 链接、要读 / 解读 / 摘要 / 提取一手数据时使用。 触发词包括但不限于:mp.weixin.qq.com/s/ 链接(任何形式)、读一下这篇微信文章、看下这个公众号、读这个链接、解读这篇文章(链接是公众号时)、提取这个公众号文章的关键信息、摘要一下这篇微信文章、微读看一下、用微读读。 即使用户只是丢一个公众号链接没说话,也应该触发——丢链接 = 想读。 不要用于非 mp.weixin.qq.com 域名(用 WebFetch / Jina 就够);不要用于需要登录态的微信内容(公众号订阅页、付费内容);不要用于公众号视频号/图文卡片(容器结构不同)。
Summary
com)文章的稳定工具。它通过PowerShell原始字节下载和Python双容器解析,绕过微信的反爬机制,确保获取到真实的文章内容,避免验证码页、乱码或空壳问题。适用于需要解读、摘要或提取公众号文章信息的场景。
Overview
微读 · weidu | 公众号文章读取工坊
工坊规矩
微信反爬不是一面墙,是一堆陷阱。每一层(Jina、WebFetch、Invoke-WebRequest)都会"看起来工作"——返回 200、给你点东西——但内容是错的(验证码页、乱码、空壳)。只信原始字节 + 自己解码,别信任何中间层的"它帮你解码好了"。
接活:用户给的输入
- •链接形如
https://mp.weixin.qq.com/s/<id>—— 直接读 - •链接 + 任务:「读这个 + 摘要 / 提取观点 / 翻译 / 对比另一篇」—— 先读再做任务
- •多个链接:批量处理,但每两次 fetch 之间
Start-Sleep -Seconds 2防风控
班规总纲
- •先抓原始字节,再解码。任何"看起来工作但内容错"的中间层(PowerShell
Invoke-WebRequest、Jina)都不能信。 - •双容器都试,不假设。
js_content在就用,不在就找content_noencode——不要根据公众号名字猜。 - •元数据缺失就说缺失,不编。新版沉浸式文章
nickname/ct大概率拿不到,老实写"作者未知"。 - •chars=0 不是成功。如果两个容器都没匹配上,提取脚本必须报错退出,不能静默返回空 → 给用户假象。
- •抓下来的 HTML 留 sha8。同一篇文章不同时间抓可能不同(小幅 A/B 测试),sha8 让你后期对比能定位差异。
Step 1:原始字节下载(PowerShell)
$url = "<wx-url>"
$out = "$env:TEMP\wx_raw.html"
$req = [System.Net.HttpWebRequest]::Create($url)
$req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
$req.Headers.Add("Accept-Language","zh-CN,zh;q=0.9")
$req.Timeout = 30000
$resp = $req.GetResponse()
$ms = New-Object System.IO.MemoryStream
$resp.GetResponseStream().CopyTo($ms)
[System.IO.File]::WriteAllBytes($out, $ms.ToArray())
$resp.Close()
"len=" + $ms.Length
"sha8=" + (Get-FileHash $out -Algorithm SHA256).Hash.Substring(0,8)为什么必须这样:Invoke-WebRequest 内部对 response body 做编码推断时,会把 UTF-8 字节按系统码页(中文 Windows = GBK / cp936)解码——所有中文字符变乱码。HttpWebRequest + WriteAllBytes 走原始字节路径,由后续 Python 按 UTF-8 解码,干净。
Step 2:判断容器类型
抓下来先看是旧版还是新版:
$h = [System.IO.File]::ReadAllText("$env:TEMP\wx_raw.html", [System.Text.Encoding]::UTF8)
"js_content present? " + $h.Contains('id="js_content"')
"content_noencode present? " + $h.Contains('content_noencode:')| 类型 | 容器 | 典型公众号 |
|---|---|---|
| 旧版 | <div id="js_content">...</div> | APPSO、知名科技号、传统排版 |
| 新版"沉浸式"流式文章 | inline JS 变量 content_noencode: '<html-string>' | 短内容创作者、AI 圈个人号 |
两个都可能同时存在——以 js_content 优先(更结构化)。
Step 3:双容器提取(Python)
import re, html, sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
with open(r"<path-to-html>", encoding="utf-8") as f:
h = f.read()
def find_meta(name):
m = re.search(r'<meta[^>]+(?:property|name)="' + name + r'"[^>]+content="([^"]*)"', h)
return html.unescape(m.group(1)) if m else None
title = find_meta("og:title")
# 路径 A:旧版 js_content(优先)
m = re.search(r'<div[^>]*id="js_content"[^>]*>(.*?)</div>\s*<script', h, re.S)
body_html = ""
if m:
body_html = m.group(1)
else:
# 路径 B:新版 content_noencode JS string
i = h.find("content_noencode:")
if i > 0:
i = h.find("'", i)
j = i + 1
buf = []
while j < len(h):
c = h[j]
if c == "\\" and j + 1 < len(h):
nx = h[j+1]
if nx == "x" and j + 3 < len(h):
buf.append(chr(int(h[j+2:j+4], 16))); j += 4; continue
if nx == "u" and j + 5 < len(h):
buf.append(chr(int(h[j+2:j+6], 16))); j += 6; continue
buf.append({'n':'\n','t':'\t','r':'\r',"'":"'",'"':'"','\\':'\\','/':'/'}.get(nx, nx))
j += 2; continue
if c == "'":
break
buf.append(c); j += 1
body_html = "".join(buf)
# 班规:chars=0 不是成功
assert body_html, "FATAL: 两个容器都没匹配上——可能是新版变体或反爬命中"
# 去标签
body = re.sub(r'<br[^>]*>', '\n', body_html)
body = re.sub(r'</p>', '\n\n', body)
body = re.sub(r'</(h[1-6]|li|blockquote|section|div)>', '\n', body)
body = re.sub(r'<style[^>]*>.*?</style>', '', body, flags=re.S)
body = re.sub(r'<script[^>]*>.*?</script>', '', body, flags=re.S)
body = re.sub(r'<[^>]+>', '', body)
body = html.unescape(body)
body = re.sub(r'\n{3,}', '\n\n', body).strip()
print(f"TITLE: {title}")
print(f"chars: {len(body)}")元数据提取
| 字段 | 出处 |
|---|---|
| 标题 | <meta property="og:title" content="..."> |
| 公众号名 | var nickname = htmlDecode("...")(旧版有,新版常缺) |
| 发布时间 | var ct = "<unix-timestamp>"(同上) |
| 原文 URL | <meta property="og:url" content="..."> |
新版沉浸式文章 nickname / ct 大概率拿不到——老实写"作者未知 / 时间未知",不要编。
已知陷阱
- •❌ 只看
js_content容器 → 新版沉浸式文章一律 0 字符正文 - •❌ Python
urllib.request直请 → 微信检测后返回反爬页(无浏览器 UA + Accept-Language) - •❌
Invoke-WebRequest -OutFile→ 同样存在编码推断问题 - •❌ 假设
content_noencode是 JSON 编码 → 实际是 JS string 转义(\x0a不是\n) - •❌ 静默返回空正文 → 给用户假象,下游任务全错
- •⚠️ 连续抓多篇 IP 易被风控 →
Start-Sleep -Seconds 2间隔 - •⚠️ 部分文章正文段落是图片 →
<img>标签里只有图片 URL,需单独提data-src走 OCR 或 vision - •⚠️ 个别公众号会做"会员可见"折叠 → 抓到的只是开头部分,要在班规里向用户报告
实测案例
完整案例见 `examples/` 目录:
- •appso-fable5-pingti.md — APPSO(旧版
js_content) - •zhoutian-claude-songkou.md — 周天财经(新版
content_noencode)
两个版本一份脚本兜底,跑通。
一句话出师证书
微信公众号文章读取的核心不是"用什么工具",是"信哪一层"。只信原始字节 + 你自己写的解码逻辑——
Invoke-WebRequest给你 GBK 乱码、Jina 给你验证码页、js_content在新版文章里压根不存在。
发现日期:2026-06-16 · 打磨日期:2026-06-17(按鲁班 Skill v1.0 思路)
Install & Usage
mkdir -p .claude/skillsAdd the configuration to .claude/skills/weidu.md
/weiduUse Cases
Usage Examples
/weidu https://mp.weixin.qq.com/s/example123
用微读读一下这篇微信文章,然后给我摘要:https://mp.weixin.qq.com/s/example456
提取这个公众号文章的关键信息:https://mp.weixin.qq.com/s/example789
Security Audits
Frequently Asked Questions
What is weidu?
微读(weidu)是专门用于读取微信公众号(mp.weixin.qq.com)文章的稳定工具。它通过PowerShell原始字节下载和Python双容器解析,绕过微信的反爬机制,确保获取到真实的文章内容,避免验证码页、乱码或空壳问题。适用于需要解读、摘要或提取公众号文章信息的场景。
How to install weidu?
To install weidu: create the skills directory (mkdir -p .claude/skills), then add the config to .claude/skills/weidu.md. Finally, /weidu in Claude Code.
What is weidu best for?
weidu is a other categorized under General. It is designed for: python. Created by DQT-bit.
What can I use weidu for?
weidu is useful for: 读取一篇微信公众号文章,获取完整正文内容。; 对给定的公众号文章链接进行摘要,提取核心观点。; 批量处理多个公众号文章链接,分别提取关键信息。; 解读公众号文章中的复杂观点或技术内容。; 提取公众号文章中的关键数据或引用。; 对比两篇公众号文章的内容差异。.