1. 为什么微信文章链接丢给 AI 总是读不出正文
你大概遇到过这种场景:把一条mp.weixin.qq.com的文章链接发给 AI,让它帮忙总结,结果它回你一句“继续滑动看下一个”,或者干脆说抓不到内容。这不是模型笨,而是微信文章的正文压根不在初始 HTML 里。
普通网页抓取工具拿到的是 HTML 骨架,微信文章的正文由 JavaScript 动态请求接口后再插入 DOM,同时请求还要带上有效的wap_sid2Cookie,请求头也得像真实浏览器。三层机制叠在一起,纯接口抓取基本没戏。所以wechat-article-viewer这个 Skill 的思路很直接:不跟接口硬碰,改用真实浏览器渲染页面,等 DOM 里出现.rich_media_content再提取。
这篇要拆的是这个 Skill 在 OpenClaw 里的配置落地,重点不是讲它多巧妙,而是把目录结构、入口声明、config.toml骨架一项项写清楚,并且用 TaoToken 统一 Key 和 API 通道,让本地能真正跑通、能验证调用链路。适合已经在用 OpenClaw、想接一个真实 Skill 练手的人,也适合想搞明白 Skill 配置到底长什么样的新手。
2. 前置准备:TaoToken 的 Key 与通道怎么接
在动config.toml之前,先把 Key 和 API 通道准备好。TaoToken 在这里扮演的角色是统一入口:Skill 里需要调用模型能力时,不用到处散落各家 Key,而是走同一个 API 地址和同一把 Key。
先去控制台创建 API Key,地址是https://taotoken.net/api-keys,登录后新建一个,复制出来保存好。API 基础地址用https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 base_url 填进配置。
如果你还没注册,从官网进就行:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。注册完顺手把 Key 建了,后面配置里要用。
这里有个容易踩的点:很多人把 Key 直接写死在SKILL.md或者脚本里,结果一提交就泄露。正确做法是 Key 只出现在config.toml或环境变量里,Skill 脚本通过读取配置拿到,不硬编码。下面第三节的骨架就是按这个原则写的。
3. 可复制配置:wechat-article-viewer 的 config.toml 骨架
先把目录结构摆出来,OpenClaw 加载 Skill 时是按目录识别的,结构错了后面全白搭。
wechat-article-viewer/ ├── SKILL.md # 入口声明与触发条件 ├── config.toml # 本篇重点:Key 与通道配置 ├── scripts/ │ ├── check_browser.js # 浏览器连接分层诊断 │ └── fetch_article.js # 主执行逻辑 └── references/ └── browser_setup.md # 浏览器启动说明SKILL.md的入口声明负责告诉 OpenClaw 什么时候该加载这个 Skill,frontmatter 里写清楚name和description,并且一定要有NOT for,防止用户丢个知乎链接也触发它。
--- name: wechat-article-viewer description: > 微信公众号文章完整阅读器。 Use when: 用户提供 mp.weixin.qq.com 链接,需要阅读或总结文章内容。 NOT for: 非微信链接的普通网页、需要登录的付费文章。 ---接下来是核心的config.toml骨架。这里把 TaoToken 的 API 地址和 Key 集中管理,脚本只读配置,不碰明文。
# config.toml —— wechat-article-viewer 的通道配置 [provider] # TaoToken 统一 API 通道,不带任何查询参数 base_url = "https://taotoken.net/api" # Key 从环境变量读取,避免明文入库 api_key_env = "TAOTOKEN_API_KEY" # 默认走对话模型,用于文章总结 default_model = "claude-sonnet" [skill] name = "wechat-article-viewer" enabled = true # 触发关键词,命中才加载完整逻辑 trigger_domains = ["mp.weixin.qq.com"] [browser] # 调试端口,与启动命令保持一致 debug_port = 9222 # 等待正文元素出现的最长时间(毫秒) render_timeout = 15000 content_selector = ".rich_media_content" [output] # 结构化输出开关 structured = true include_source_url = true配置里几个参数值得单独说。api_key_env指向环境变量名而不是 Key 本身,这样config.toml可以安全地放进版本库。render_timeout给 15 秒是实测下来比较稳的值,微信文章正文加载偶尔慢,给太短会误判失败。content_selector就是那个关键选择器,等它出现才说明渲染完成。
环境变量在启动 OpenClaw 前设置好:
export TAOTOKEN_API_KEY="你的Key"Windows 下用set TAOTOKEN_API_KEY=你的Key,或者写进系统环境变量。设完可以用echo $TAOTOKEN_API_KEY确认一下有没有生效。
4. 验证请求:跑通一次真实抓取并确认链路
配置写完不算完,得真跑一次。先启动带调试端口的 Chrome,macOS 下命令是这样:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \ --remote-debugging-port=9222 \ --user-data-dir=/tmp/chrome-debug-profile启动后浏览器里先登录一次微信网页版,让 Cookie 落地。这一步很多人跳过,结果脚本一直报“未登录”。
然后写一个最小验证脚本,确认 TaoToken 通道和浏览器两条链路都通:
// scripts/verify.js import { readFileSync } from "fs"; import { parse } from "toml"; const config = parse(readFileSync("./config.toml", "utf-8")); const apiKey = process.env[config.provider.api_key_env]; // 1. 验证 TaoToken 通道 async function verifyProvider() { const res = await fetch(`${config.provider.base_url}/v1/models`, { headers: { Authorization: `Bearer ${apiKey}` }, }); if (!res.ok) throw new Error(`通道异常: ${res.status}`); console.log("TaoToken 通道正常"); } // 2. 验证浏览器调试端口 async function verifyBrowser() { const res = await fetch(`http://localhost:${config.browser.debug_port}/json/version`); if (!res.ok) throw new Error("浏览器调试端口未就绪"); console.log("浏览器调试端口正常"); } await verifyProvider(); await verifyBrowser();跑node scripts/verify.js,两条都打印正常,说明 Key、通道、浏览器环境都就位了。接着用真实链接跑主流程:
// scripts/fetch_article.js 核心片段 export default async function run(url) { if (!url.includes("mp.weixin.qq.com")) { return { success: false, message: "请提供有效的微信公众号文章链接" }; } const browser = await connectToBrowser(9222); const page = await browser.newPage(); await page.goto(url, { waitUntil: "networkidle2" }); await page.waitForSelector(".rich_media_content", { timeout: 15000 }); const article = await page.evaluate(() => ({ title: document.querySelector(".rich_media_title")?.innerText, author: document.querySelector(".profile_nickname")?.innerText, content: document.querySelector(".rich_media_content")?.innerText, })); return { success: true, data: article }; }成功时你会拿到标题、作者、正文三段结构化数据,正文不再是“继续滑动看下一个”。到这一步,Skill 加载和调用链路就算真正跑通了。
5. 本篇常见错排查
报错一:ECONNREFUSED localhost:9222。说明 Chrome 没带调试端口启动,或者端口被别的进程占了。先lsof -i :9222看占用,没有就按第 4 节的命令重新启动。注意--user-data-dir要指向一个独立目录,别用默认配置目录,否则调试端口可能起不来。
报错二:waitForSelector超时。正文元素一直没出现,通常是没登录微信网页版,Cookie 缺失导致服务端返回的是提示页。在调试浏览器里手动打开https://wx.qq.com登录一次,再重跑。如果登录了还超时,把render_timeout调到 20000 试试。
报错三:TaoToken 返回 401。基本是TAOTOKEN_API_KEY没设或设错。确认环境变量名和config.toml里的api_key_env完全一致,大小写敏感。Key 本身去https://taotoken.net/api-keys重新复制一次,注意别带多余空格。
报错四:Skill 不触发。用户发了微信链接但 OpenClaw 没加载这个 Skill,检查SKILL.md的 frontmatter 里trigger_domains是否包含mp.weixin.qq.com,以及enabled是不是true。目录名和name字段不一致也会导致加载失败。
报错五:正文抓到了但全是乱码或空行。这是提取时没做分段处理。.rich_media_content的innerText会保留换行,但连续空行需要自己压缩。在返回前加一句content.replace(/\n{3,}/g, "\n\n")就能清爽很多。
6. 把通道固定下来,后面接别的 Skill 也省事
这个 Skill 跑通之后,你会发现真正值钱的不是抓取逻辑本身,而是那套“Key 集中管理 + 通道统一”的配置方式。config.toml里provider那一段,换个 Skill 照样能用,改的只是skill和browser部分。
如果你后面要接更多需要模型能力的 Skill,或者想让 OpenClaw 长期跑编码、Agent 类任务,可以看下 Coding Plan,通道和 Key 复用同一套,不用每个 Skill 重新配一遍:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。想先单独验证模型对话是否正常,用模型对话页面发一条消息就行:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,里面把 base_url 和鉴权头写得很清楚,照着填不会错。
最后留一个我踩过的坑:config.toml改完记得重启 OpenClaw,它不会热加载配置。有次我改完render_timeout死活不生效,折腾半天才发现是没重启。