Claude skill weidu
Read mp.weixin.qq.com Official Account articles from Claude Code without getting blocked. PowerShell HttpWebRequest raw bytes + dual-container (js_content / content_noencode) fallback. 绕反爬读公众号文章的稳定姿势。
npx -y skills add DQT-bit/claude-skill-weiduAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
微读(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 就够);不要用于需要登录态的微信内容(公众号订阅页、付费内容);不要用于公众号视频号/图文卡片(容器结构不同)。
SKILL.md
8.0 KB, as published. Nobody here has run it
微读 · 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 思路)