Claude skill weidu
微读(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 就够);不要用于需要登录态的微信内容(公众号订阅页、付费内容);不要用于公众号视频号/图文卡片(容器结构不同)。From its SKILL.md
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.
2 things 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.
- runs commandsInstructs the agent to run 7 commands, including `Start-Sleep -Seconds 2` and 6 more.
SKILL.md
8.0 KB, ~2.4k tokens by cl100k_base, 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 思路)
What ships with it: 5 files
11.4 KB alongside SKILL.md
examples/
- appso-fable5-pingti.md2.1 KB
- zhoutian-claude-songkou.md3.2 KB
- .gitignore131 B
- LICENSE1.0 KB
- README.md5.0 KB