Claude Code Skill:轻松读取微信、小红书、头条网页正文,输出干净Markdown 简介面向微信公众号、小红书、今日头条等主流平台的智能网页内容读取器是适配Claude Code生态的Skill工具包主要服务于需要自动化抓取、解析网页正文并集成到工作流的开发者或内容运营者。基于资源描述这套Skill还覆盖小红书平台的自动发布、自动评论与自动检索场景并兼容OpenClaw、Codex、CC等运行环境。压缩包共11个文件以6个Python脚本和2个Markdown文档为主另有JSON元数据、txt说明及gitignore配置整体体积约22KB结构轻量便于部署。脚本模块分工明确包含URL识别、微信文章链接转换与读取、内容保存等功能配合skill.md与README.md可快速理解调用逻辑适合作为自定义Skill开发范本或直接嵌入内容采集管线。目前已有73人学习下载对于希望扩展Claude、OpenClaw等工具中文内容读取能力的技术读者具有参考价值。1. 智能网页内容读取器这个 Claude Code Skill解决的是终端里链接读不干净的老问题把一篇微信公众号文章的 URL 丢给 Claude Code让它总结要点它却说“无法访问链接”。换个小红书笔记链接它读回来的只有一堆 emoji 和推荐语。这不是 Claude 的能力问题而是通用网页读取器没适配这些平台的页面结构。智能网页内容读取器这个 Claude Code Skill就是为了在终端里把这类链接变成干净的 markdown 正文——保留标题、作者、发布时间、正文层级去掉广告和推荐位。它的价值不止于省掉复制粘贴当你需要批量处理几十篇文章、做内容聚合或语料清洗时手动复制完全不现实而这个 skill 能直接串进 Claude Code 的自动化流程里。适合三类人用 Claude Code 做内容运营分析的人、给 RAG 应用喂网页数据的人、以及想把日常阅读变成结构化数据的信息从业者。这篇从它怎么读网页讲起一路到安装、平台适配和踩坑。2. 先看懂它怎么读从 URL 到干净正文的抽取链路与平台差异直接对这三类平台跑requests.get再抽article是最常见的失败方式。它们的页面结构有一个共同点正文不在你第一眼看到的 HTML 里或者被注释、被转义、被字体替换。这一章先把 skill 的抽取链路拆开讲清楚它为什么能读出来以及为什么通用工具做不到。2.1 为什么通用抓取器在微信、小红书、头条上集体失效先说微信公众号。它的文章页是服务端渲染的curl能看到标题但正文区js_content里的 HTML 在首次请求时经常是空的或者被注释符包裹需要脚本触发滚动加载后才填充完整。简单正则去抓抓到手的只是一堆空的div壳。这里有个容易被忽略的细节微信对非移动端 UA 的响应也不太稳定同一个链接用桌面 UA 访问可能拿到“环境异常”的验证页用微信内置浏览器 UA 就正常。凡是直接用默认 Python UA 去抓的大概率翻车。小红书则是另一个极端。它的笔记详情页是标准的前端渲染单页应用正文数据根本不在静态 HTML 里而是埋在页面底部的一串script标签里的window.__INITIAL_STATE__变量中。你以为自己在抓网页其实是在解析一个 JSON。而且这个 JSON 里的结构非常深笔记标题、正文、话题标签各在不同层级拿通用抽取器去识别段落密度自然是空手而归。今日头条介于两者之间正文在 HTML 里确实存在但页面里混着大量“相关推荐”“热门评论”“广告位”且正文末尾经常跟一堆推荐流卡片同在一个父节点下。更麻烦的是部分场景下正文里的数字会被字体文件替换成自定义字形渲染后是乱码静态 HTML 里根本对不上号。Mozilla 的 Readability 算法对英文博客效果好对中文平台的这些怪象基本无能为力——它按段落文本密度和标点密度算分遇到字体反爬乱码时算出来的“正文”全是垃圾字符。所以这类 skill 在架构上几乎不会只用通用算法而是一套“按域名路由、按平台定制”的混合方案。2.2 抽取方案选型正则模板、可读性算法和 DOM 特征各有边界常见做法是三层方案叠加。第一层是正则模板针对每个平台写一组提取规则比如正文容器的 id、class、包裹正文的 script 变量名。正则的好处是快、可控、不依赖浏览器内核坏处是脆——平台一改版就碎需要手工维护。第二层是通用可读性算法比如 readability 或 trafilatura用作兜底。当 URL 域名不在平台路由表里、或者平台模板失效时就走通用算法能读到多少算多少。第三层是 DOM 特征匹配通过类似document.querySelector的方式定位正文容器适用于页面结构稳定但模板规则太复杂的场景。实际 skill 里这三层是串起来的先按 URL 特征路由到平台专属提取器专属提取器失败后自动降级到通用算法。看一下这个路由逻辑的核心import re URL_PATTERNS { wechat: rmp\.weixin\.qq\.com, xiaohongshu: rxiaohongshu\.com, toutiao: r(www|m)\.toutiao\.com, } def extract(url: str, html: str) - str: for name, pattern in URL_PATTERNS.items(): if re.search(pattern, url): extractor EXTRACTORS[name] # 平台专属提取器 content extractor(url, html) if len(content) 200: # 有效性阈值 return content break # 提取失败跳出路由 return readability_dom_extract(html) # 兜底通用算法逻辑说明URL_PATTERNS里写了三个平台的域名正则extract先按 URL 匹配平台找到后调用对应的提取器。len(content) 200是有效性阈值——正文少于 200 字通常意味着提取失败这时候不能再硬撑直接降级到通用算法。这个阈值很关键拿掉它skill 会把“提取失败”的残缺结果当成正文输出下游的 Claude 就会基于垃圾内容给你编一篇总结。参数说明里最值得调的是有效性阈值。200 字适合公众号和头条文章小红书短笔记可能正文不到一百字这时要单独给小红书设一个更低的阈值否则短笔记永远走不到正文提取逻辑。我一般会把阈值放在配置文件里而不是写死在代码中方便按平台单独调。2.3 skill 的典型工作链路预检、抓取、渲染、清洗、格式化这套 skill 内部通常是一个五段管道。第一段预检确认 URL 可访问、判断域名是否在平台路由表里。第二段抓取用配置好的 headers 发请求拿到原始 HTML。第三段渲染这一步不是每次都执行只有初版 HTML 里找不到正文时才交给 Playwright 之类的无头浏览器模拟滚动到底触发懒加载。第四段清洗把正文里的script、style、推荐位卡片、广告 iframe 全部剥掉只留段落节点。第五段格式化把清洗后的 DOM 转成 markdown 或纯文本附带标题、作者、发布时间等元信息。def run_pipeline(url: str) - dict: page fetch_html(url, headersbuild_headers(url)) if not has_content(page): page render_with_browser(url, scrollTrue) # 渲染兜底 content extract(url, page) cleaned strip_noise(content) # 去脚本、去推荐位 return {url: url, meta: parse_meta(page), content: cleaned}逻辑说明fetch_html用的是 requests 加自定义 headershas_content检查 HTML 里是否存在已填充的正文容器。微信和头条的首页请求约有三到四成概率正文区是空的这时render_with_browser会启动无头浏览器用真实渲染拿最终 DOM。strip_noise以容器边界为基准做修剪这一步对头条尤其关键下面第四章会细说。参数说明scrollTrue解决的是懒加载问题公众号正文分页加载时不触发滚动事件就拿不到后几段这个参数在配置里可以改成scroll_times3控制滚动次数。render_with_browser的超时一般给 8 到 10 秒太长会让单次读取慢到没法用太短在弱网下会渲染不完整。这里有个取舍渲染模式是最后的后悔药能用静态提取解决的尽量别走渲染因为浏览器渲染的耗时和 CPU 占用都高出两个数量级。3. 把 zip 装进 Claude Code安装、确认加载与首次调用拿到那个 zip 压缩包之后最直接的诉求是“装完就能用”。Claude Code 的 skill 机制并不复杂把解压出来的文件夹放进指定的 skills 目录重开会话就能被扫描到。这章按步骤走中间会标注两个容易卡住的细节。3.1 解压到 skills 目录SKILL.md 与文件夹结构先说目录归属。Claude Code 会在启动时扫描两个位置项目级目录.claude/skills/和用户级目录~/.claude/skills/。项目级只对当前项目生效适合团队协作时把 skill 跟着仓库走用户级对所有项目生效适合个人常用技能。这个网页读取器属于后者因为无论在哪写代码都可能需要读链接。解压命令如下# 建好用户级 skills 目录然后解压 mkdir -p ~/.claude/skills unzip 智能网页内容读取器*.zip -d ~/.claude/skills/ # 看一眼解压后的结构确认 SKILL.md 存在 ls -la ~/.claude/skills/智能网页内容读取器/逻辑说明unzip的-d参数指定了解压目标目录解压后应该看到 SKILL.md 和一个脚本目录。SKILL.md 是 skill 的唯一入口Claude Code 通过它识别技能名称、描述和触发条件。ls这一步是确认用如果目录里没有 SKILL.md 而是多包了一层嵌套文件夹skill 会加载失败。这里有个中文 zip 常见坑压缩包内的文件名在解压后显示为乱码原因是 zip 内部的编码标记不一致。遇到这种情况用unzip -O gbk重新解压即可。目录名乱码不影响 Claude Code 加载但你自己维护时会很痛苦建议解压后顺手把文件夹重命名为英文比如web-content-reader。3.2 验证 skill 被加载/skills 命令确认状态安装完并不需要重启机器但需要重开一个 Claude Code 会话让扫描器重新加载目录。在 Claude Code 的交互界面上输入斜杠命令来确认/skills当前会话可用的 skill 会列出来找到其中的网页内容读取器条目说明加载成功。如果列表里没有优先排查路径确认刚才解压到的目录确实在~/.claude/skills/下而不是~/Downloads/里留了一份没挪过去。其次是 SKILL.md 的格式问题打开文件看开头几行确认 frontmatter 是否完整。一个典型的 SKILL.md 长这样--- name: web-content-reader description: Read webpage content and return clean markdown. Use when the user asks to read articles from WeChat, Xiaohongshu, Toutiao, or any news URL. ---逻辑说明name是 skill 的唯一标识description是模型判断“什么时候该用这个技能”的依据。description 里写的触发词越具体模型越容易在正确的场景里调用它。上面这段描述里刻意写进了 WeChat、Xiaohongshu、Toutiao 三个平台名因为 Claude Code 的工具选择机制依赖文本匹配描述里没有平台关键词模型很可能把请求路由到别的地方。参数说明description 不要写太长两到三句话为佳。Claude Code 在每次会话启动时会把所有 skill 的 description 塞进上下文几十个 skill 就是几百行文本太长的描述会拖慢响应且挤占 token。写清楚“做什么 何时用”就够具体怎么实现是 SKILL.md 正文的事不影响工具选择。3.3 首次调用从公众号文章开始的最小验证加载完成后用一个最简单的指令验证整条链路是否通。公众号文章是三个平台里结构最规整的适合当第一个测试目标用 网页内容读取器 读取这条链接的正文输出 markdown https://mp.weixin.qq.com/s/xxxxx预期结果有三层。第一层是输出里出现标题和作者第二层是正文段落完整第三层是正文末尾没有被截断、也没有混入推荐位内容。如果只返回了标题和一段摘要多半是提取阈值判断出现了偏差去检查配置文件里的有效性阈值。如果完全没有输出回上一步用/skills确认 skill 是否在列表中。一个要留意的细节Claude Code 在读到完整正文后可能会顺手追加自己的分析和总结这不是 skill 的问题而是模型的行为习惯。如果你只想要干净的正文可以把指令改成“只输出读取结果不要总结”。第一次跑通之后再依次换小红书和头条的链接做平台适配测试每个平台的坑各不相同下一章专门拆开讲。4. 微信、小红书、头条三平台的差异化配置这一章是实际使用中最花时间的部分。三个平台的页面结构差异太大不可能用同一个提取器通吃所以 skill 的配置层面必须支持按平台覆盖参数。我从每个平台的正文读取方式、最容易失败的环节、以及建议的配置项三个角度来写。4.1 微信公众号js_content 容器与非移动 UA 的坑公众号文章页从 2021 年后就稳定使用div idjs_content作为正文容器标题在h1 classrich_media_title。这套结构很适合写死模板规则。但有个前置条件请求必须携带正确的 User-Agent。实测用微信内置浏览器 UA 访问正文完整返回的概率远高于桌面 Chrome UA桌面 UA 经常触发验证中间页返回的 HTML 里全是安全校验脚本。skill 配置里对应的是 headers 段通常长这样platforms: wechat: match: mp\\.weixin\\.qq\\.com body_container: div#js_content title_selector: h1.rich_media_title headers: user_agent: Mozilla/5.0 (Linux; Android 13; ... Mobile) AppleWebKit/537.36 ... MicroMessenger/8.0.38 dirty_threshold: 200参数说明body_container是正文定位用的 CSS 选择器title_selector取标题。dirty_threshold指的是正文 HTML 长度低于多少字算提取失败200 意味着如果 js_content 里扒出来的纯文本不足 200 字就转入渲染兜底分支。这个值在公众号场景下可以稍微调高到 300因为公众号文章动辄两三千字如果提取器只拿到 200 字大概率是正文被注释符包裹了不值得信任。user_agent字段是最容易踩坑的微信内置浏览器 UA 会定期更新版本号skill 维护者需要跟着更新。你自己拿到手之后如果发现微信链接读出来一大段“当前环境异常”之类的提示先去把这个 UA 换成当前微信版本对应的新 UA。另外微信文章的正文里偶尔有section嵌套过深的问题清洗阶段只剥script和style是不够的我一般会额外把空白的p和section全部过滤掉保证输出 markdown 里没有连续空行。4.2 小红书笔记从INITIAL_STATE里取 JSON 数据小红书笔记详情页的正文藏在页面底部脚本变量window.__INITIAL_STATE__里面是一个深度嵌套的 JSON 对象。静态抓取时整个 HTML 里唯一有价值的信息就是这个变量其他部分全是框架代码和预加载逻辑。正确做法是用正则找到变量起始位置再用括号配对算法截取完整 JSON最后按结构取正文。import re, json, html as html_mod def extract_xiaohongshu(html_text: str) - str: # 定位 __INITIAL_STATE__ 的起始位置 marker window.__INITIAL_STATE__ start html_text.find(marker) if start -1: return # 从等号后的第一个 { 开始做括号配对找到完整 JSON 结束位置 depth 0 i html_text.find({, start) for j in range(i, len(html_text)): if html_text[j] {: depth 1 elif html_text[j] }: depth - 1 if depth 0: state json.loads(html_text[i:j1]) break note_data state[note][noteDataMap] first_key list(note_data.keys())[0] note note_data[first_key][note] title note.get(title, ) desc note.get(desc, ) # desc 是纯文本段落直接即可 return f# {title}\n\n{desc}逻辑说明这代码的核心是括号配对它比正则更可靠。小红书页面的 JSON 里会出现引号、冒号、嵌套对象用json.loads直接解析整个脚本变量往往会因为页面里夹杂的其他内容报错而括号配对能精确找到 JSON 对象的边界把这段字符串交给json.loads就稳定了。读取成功后note.noteDataMap下面才是真正的笔记数据标题和正文分别取title和desc字段。有个容易被忽略的点desc里是纯文本没有段落标签小红书笔记的换行靠\n分隔清洗时不要试图拿 HTML 解析器去处理它转 markdown 时保留换行即可。另外小红书对请求频率非常敏感同一个 IP 连续请求十几个笔记链接后页面里会不再返回__INITIAL_STATE__而是换成验证脚本。skill 配置里应当给小红书单独加一个请求间隔参数我一般设为 3 到 5 秒随机。4.3 头条文章字体反爬的乱码与推荐位混排头条文章页的正文容器是article标签看起来结构清晰但实际有两大坑。第一个是部分场景下正文里的数字和特定汉字显示为乱码字体肉眼在浏览器里看着正常抓下来的静态 HTML 里却是自定义字体编码后的字符。第二个是正文末尾混着一大堆相关推荐卡片它们与正文处于相近的 DOM 层级直接按article提取会把推荐位内容一并带进来。处理乱码的思路不是去还原字体映射——那是无底洞——而是把乱码当作“正文异常”的信号交给渲染分支。无头浏览器加载页面后直接读取渲染后的可见文本因为浏览器自己完成了字体替换拿到的就是正常字符。代价是性能差、耗时高所以这个分支只在样本文本乱码比例超过阈值时触发。推荐位混排的问题用容器边界解决正文容器通常在article的第一个子节点链条里而推荐位在article之外的独立div中。清洗时先定位正文容器再按文本节点顺序拼接遇到“相关推荐”“热门评论”这类关键词就截断。配置上可以给头条设一个cut_markers列表platforms: toutiao: match: (www|m)\\.toutiao\\.com body_container: article div cut_markers: [相关推荐, 热门评论, 点击加载更多] font_garbled_threshold: 0.05参数说明cut_markers是截断关键词拼接正文时一旦遇到这些词就认为正文结束。font_garbled_threshold是乱码比例阈值0.05 表示抽取结果中有超过 5% 的字符落在自定义字体区时触发渲染兜底。这个参数不能设太大头条正文里偶尔出现几个乱码字符是常态但超过 5% 说明整个页面字体映射已生效静态提取的结果不可信。4.4 输出参数markdown 保真、纯文本与 token 控制读取结果最终要喂给 Claude所以输出格式直接影响后续处理质量。四个参数最常用format决定输出 markdown 还是纯文本with_meta控制是否附带标题作者等元信息max_chars限制正文长度strip_links决定是否删除正文里的外链。参数可选值默认值适用场景formatmarkdown / plainmarkdown需要标题层级时用 markdown只做语义提取用 plainwith_metatrue / falsetrue做引用记录时开做纯语料清洗时关max_chars1000 - 5000020000公众号长篇可开大小红书短笔记可调小strip_linkstrue / falsetrue保留链接会显著增加 token 消耗参数的取舍逻辑很简单token 预算紧张就开strip_links和plain公众号一篇文章转 markdown 加链接大概会消耗 4000 到 6000 token而纯文本不含链接可以压到一半。with_meta看起来只加了几行元信息但批量处理几十篇文章时每篇多出的 token 累加起来很可观。我一般建议在批量清洗场景里关掉 meta在单篇精读场景里保留。5. 避坑清单平台读取器最常见的 5 个翻车现场运行这类 skill问题往往不在“读不到”而在“读出来的东西是错的”。以下五个坑是按出现频率排的每条都按现象、原因、解决的顺序写建议直接对照排查。5.1 返回空正文微信懒加载与注释干扰现象读取公众号文章输出的 markdown 里只有标题和摘要正文字段为空。原因微信文章正文区受懒加载机制控制首包 HTML 里js_content节点下的内容可能是空节点或以注释形式存在。此外未触发任何滚动事件时部分分页文章只加载了第一屏内容。解决在 skill 配置里把微信的render_fallback设为true让提取器在检测到正文为空时自动走渲染分支。如果不想启动浏览器另一个缓解手段是用正则从 HTML 注释里捞出被注释的正文片段但这个方法在微信改版后已不太可靠建议优先用渲染。5.2 skill 没被触发Claude Code 的工具选择机制像个黑匣子现象你在终端里丢给 Claude 一串链接它没调用任何读取工具而是直接凭训练记忆编了一段文章概要。原因Claude Code 对 skill 的调用由模型自主判断它依据的是 SKILL.md 里的 description 是否与当前请求匹配。如果 description 写得过于宽泛比如只写“read web page”模型会认为读取网页不是必要步骤直接用自己的知识回答。解决把 description 改具体明确点出平台名和动作比如“read and extract article content from WeChat, Xiaohongshu, Toutiao URLs”。同时在提问时主动写明工具名“用网页内容读取器读这个链接”显式触发远比让模型自己猜可靠。这不是玄学是工具选择的文本匹配机制决定的。5.3 JSON 提取崩溃小红书页面的特殊字符与转义陷阱现象读小红书笔记skill 报 JSON parse error或者输出到一半截断。原因__INITIAL_STATE__里的 JSON 字符串包含未转义的单引号、换行符和 HTML 实体直接按行读取或正则取小块都会踩中边界。另一个原因是这个 JSON 对象里某些值可能未定义标准 JSON 解析器遇到undefined直接抛出异常。解决第一层用括号配对算法取完整 JSON而不是截取到分号。第二层在json.loads之前做一次清理把undefined替换为null把单引号替换为双引号。第三层如果仍然解析失败就降级到渲染模式用浏览器执行页面自身的逻辑绕过手动解析。5.4 触发了安全验证页请求频率、指纹与随机延迟现象读取结果里出现“安全验证”“滑块”等字样或者标题正常但正文全是验证脚本残留。原因多个环节叠加的产物——同一个 IP 在短时间内请求次数过多或者请求头里 UA 太新、缺少平台期望的 Cookie 字段。小红书和头条对请求频率的敏感度高于微信高频请求几乎必然触发风控。解决在 skill 配置里为每个平台设置独立的请求间隔小红书建议 3 到 5 秒随机头条建议 2 到 4 秒随机。批量读取时还要加一个全局速率限制比如每分钟最多 12 个请求。这里的原则是降低请求特征而不是对抗验证逻辑。被验证页拦截后的处理只有一条路——停一会儿再请求没有捷径可走。5.5 正文尾部混入推荐位内容容器边界与关键词截断现象读取头条文章正文前半部分完整后半部分突然出现“相关推荐”“点击加载更多”“热门评论”等字样甚至整段内容跑题。原因头条的推荐位与正文在同一父容器下的不同分支里结构化提取如果只按父容器裁剪会把这些分支一并收入。公众号文章末尾偶尔也会出现“阅读原文”跳转卡片同样会污染输出。解决清洗阶段按两个信号截断。第一个是容器边界精确定位到正文段落节点而不是整个父容器。第二个是关键词信号配置cut_markers列表在拼接文本时遇到列表里的词就终止。两个方法同时启用时以先触发的信号为准。还有一个额外的兜底策略如果正文提取结果超过 3 万字而页面本身是普通文章时大概率是混入了推荐位内容按长度经验值直接截断。6. 进阶给读取器加缓存与文本指纹把 token 花在刀刃上skill 跑通之后下一个要解决的是效率和成本问题。无论你是给 RAG 应用喂数据还是做定期的内容监控缓存都应该是标配。我的做法是把缓存做成 skill 的默认行为而不是可选功能因为重复抓取同一篇页面不仅浪费 API 额度更容易触发平台风控。import hashlib, json, os, time CACHE_DIR os.path.expanduser(~/.cache/web_content_reader) def get_cache_key(url: str) - str: # 用 URL 的前 128 个字符做哈希避免超长链接当文件名 return hashlib.sha256(url.encode(utf-8)).hexdigest()[:16] def read_with_cache(url: str, ttl_hours: int 24) - dict: key get_cache_key(url) cache_path os.path.join(CACHE_DIR, f{key}.json) if os.path.exists(cache_path): data json.load(open(cache_path, encodingutf-8)) if time.time() - data[cached_at] ttl_hours * 3600: return data result run_pipeline(url) # 走完整的抓取链路 result[cached_at] time.time() os.makedirs(CACHE_DIR, exist_okTrue) json.dump(result, open(cache_path, w, encodingutf-8), ensure_asciiFalse) return result逻辑说明缓存键用 URL 哈希的前 16 位避免长 URL 带来的文件名问题同时也避免把原始链接当文件名导致的信息泄露。ttl_hours默认 24 小时公众号文章和头条新闻在这一天内变更概率较低小红书笔记则建议把 TTL 拉到 72 小时。过期后重新抓取过期前直接读缓存。这套逻辑能省掉大部分重复请求。验证读取质量我一般看三个指标。第一个是正文覆盖率提取出的正文字符数除以页面可见文本总字符数目标在 0.8 以上——低于 0.6 说明清洗过度把正文当噪声剥掉了。第二个是信噪比正文里有效文字与标签残留、广告词的比率头条文章最容易在这一项翻车。第三个是字段完整度标题、作者、发布时间、正文四项是否齐全微博类短内容可以放宽公众号文章要求全中。这三个指标可以写进 skill 的返回结果里每次读取时自动计算并附带上下文。最后说一个我的血泪经验有一次批量清洗 200 篇头条文章时没有开缓存脚本中途断了重跑等于同一批页面抓了两遍下午整个 IP 就被平台限制了。那之后我把缓存和限速写成了 skill 的默认行为宁可多占一点磁盘空间也不在同一个坑里翻两次车。文本指纹去重也可以顺势做——读取结果时计算正文前 128 字的哈希如果和已有缓存指纹相同就直接复用旧的读取结果连 URL 哈希都不必比对。这套逻辑自己维护成本不高但对批量场景的提升是实打实的。希望帮到你。本文还有配套的精品资源点击获取