ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

智能网页内容读取器:Claude Code Skill 实现微信/小红书/头条正文提取

2026/10/7 21:35:38 拓冰建站 浏览量
智能网页内容读取器:Claude Code Skill 实现微信/小红书/头条正文提取 简介面向需要自动化处理中文主流平台网页内容的开发者这套Claude Code Skill以网页读取为核心同时提供小红书自动化能力覆盖微信公众号、小红书、今日头条等平台支持自动发布、自动评论与自动检索可无缝接入OpenClaw、Codex、CC等工具链。资源包共11个文件主体为6个Python脚本分别负责URL识别、微信文章转换、内容保存等任务辅以Markdown使用说明、JSON配置与gitignore文件整体体积仅22KB轻量易部署且模块边界清晰。目前已有73人参与学习适合希望快速构建内容采集与小红书运营流程的开发者或自动化爱好者。包内url-reader-main目录按功能拆分了读取器核心逻辑既可直接调用现成脚本处理公众号链接也能在此基础上扩展其他平台适配器为二次开发提供了完整可参考的样例结构。1. 智能网页内容读取器是什么一个拆开就能用的 Claude Code Skill很多人拿到公众号文章链接时顺手就把它丢给 Claude 让它读结果 Claude 回了一句“这是一篇文章”因为模型根本看不到页面里的正文。智能网页内容读取器这个 Claude Code Skill 解决的就是这个断层它先按域名和 URL 特征识别平台再把微信公众号、小红书、今日头条这类中国主流平台的网页正文抓下来、清洗成干净的 Markdown最后交给模型去总结、改写或提取信息。它不是一个爬虫框架而是一套 SKILL.md 加按站点配置的解析规则。适合批量把链接变成结构化素材、需要持续维护多平台解析的开发者。2. 读取链路拆解Skill 怎么拿到网页并吐出 Markdown2.1 Claude Code 的 Skill 机制靠 description 触发不靠命令Claude Code 的 Skill 本质是一组放在固定目录下的文件核心是SKILL.md。Claude Code 会扫描用户的~/.claude/skills/和项目里的.claude/skills/目录把每个 Skill 的 frontmatter 里的name和description索引进上下文。当用户输入的内容和某个 Skill 的描述匹配时这个 Skill 才会被加载进当前会话。所以写 Skill 最要紧的不是给它起多好听的名字而是 description 里要把触发场景写清楚最好把平台名、动作词、URL 形式都列进去。举个例子如果 description 只写“读取网页”模型会在很多无关场景误触发如果写“当用户提供微信公众号、小红书、今日头条链接要求总结或提取内容时使用”触发就会精准得多。description 就是接口接口写得越具体Skill 的命中率越高。这也是为什么读取器这类工具适合做成 Skill 而不是写进 system prompt——Skill 只在需要时才加载不额外占用上下文。2.2 一条链接的完整旅程识别、抓取、解析、清洗四步我在本地把读取器拆成了四个阶段。第一步识别平台拿到 URL 后先解析 host 和路径再用正则匹配平台特征比如mp.weixin.qq.com/s/是公众号文章xiaohongshu.com/explore/是小红书笔记toutiao.com/article/是头条文章。第二步抓取用配置好的 User-Agent 请求页面三平台基本都能用静态 HTML 拿全部分场景需要额外带 Referer。第三步解析这块差异最大公众号正文藏在#js_content容器里小红书正文在window.__INITIAL_STATE__这个 JSON 里头条则在__NEXT_DATA__或 JSON-LD 里。第四步清洗把所有 script、style、iframe 标签删掉把连续空行压缩再把 HTML 转成 Markdown 输出。这四个阶段里最容易被低估的是第四步。你从页面里拿到的原始 HTML 可能包含导航、推荐位、二维码、评论区这些噪声对模型理解正文没有任何帮助反而会增加 token 消耗、干扰总结质量。清洗不是可选项而是读取器能不能用起来的关键。常见做法是先用选择器锚定正文容器比如#js_content、article、main只在容器内做文本提取容器拿不到时再降级到整页清洗。2.3 requests 够用还是必须上无头浏览器一开始做这类工具容易被“这平台有反爬”吓到第一反应就是上 Playwright 或 Puppeteer。实际做下来我的结论是先用 requests 把静态 HTML 拿回来不行的平台再考虑无头浏览器。公众号、今日头条的正文基本都在服务端渲染的 HTML 里小红书虽然大部分正文在 JS 变量里但那也是静态 script 标签里的字符串requests 就能拿到不需要真跑一遍浏览器。无头浏览器最大的问题不是慢而是容易被风控识别启动 Chrome 的指纹特征本身就比 requests 更像机器人。我一般会把无头浏览器作为二级降级方案而不是主链路。优先走“静态请求 JSON 状态提取”这条链路失败时再用无头浏览器渲染一次。这个设计能让读取器保持轻量也方便在本地快速调试。你只需要在 Python 环境里装 requests、BeautifulSoup 和 lxml 三个库就能跑通全部平台。3. 从零搭建读取器SKILL.md、解析器与站点配置三件套3.1 目录结构与 SKILL.md三行 frontmatter 决定触发时机先建目录。我把这套 Skill 放到~/.claude/skills/web-reader/下里面只放三个文件SKILL.md、reader.py、site_config.json。目录结构保持极简不要堆一堆无关文件Claude Code 在加载 Skill 时会把整个目录的内容都纳入上下文文件越少越省 token。~/.claude/skills/web-reader/ ├── SKILL.md ├── reader.py └── site_config.jsonSKILL.md 是这个 Skill 的入口Claude Code 靠它判断“什么时候该用”。我把触发条件写在 description 里把执行步骤写在正文里让模型按流程调用解析器--- name: web-content-reader description: 当用户提供微信公众号、小红书、今日头条等中国主流平台的网页链接要求读取、总结、改写或提取正文内容时使用。也适用于用户粘贴平台文章 URL 询问“这篇讲了什么”的场景。 --- # 网页内容读取器使用说明 1. 收到用户提供的链接后先调用 python3 reader.py url 抓取并解析正文。 2. 解析结果是 JSON字段包含 title、content、author、publish_time、image_urls。 3. 把 content 字段中的 Markdown 文本交给用户或按用户要求基于它做总结和改写。 4. 如果解析结果中出现 error 字段直接向用户说明读取失败不要尝试用链接内容猜测文章含义。这段 frontmatter 里没有写allowed-tools字段因为新版 Claude Code Skill 的触发主要靠 description 匹配工具权限由父级会话统一管理。如果你用的是旧版本可以在 frontmatter 里补上tools: [bash]或对应允许执行的工具列表。正文里的步骤一定要写清楚“先跑命令再总结”否则模型可能会跳过解析脚本直接凭链接文本瞎猜内容。3.2 核心解析器用 Python 把 HTML 变成可读 Markdownreader.py是整个 Skill 的执行核心。它的职责是接收 URL输出 JSON。我把它拆成四个函数加载配置、匹配平台、抓取页面、解析清洗。代码不长但每一步都要稳。#!/usr/bin/env python3 import json import re import sys from urllib.parse import urlparse import requests from bs4 import BeautifulSoup def load_config(pathsite_config.json): with open(path, r, encodingutf-8) as f: return json.load(f) def match_platform(url, config): 按 URL 特征匹配平台。命中返回平台 key否则返回 default parsed urlparse(url) target (parsed.netloc parsed.path).replace(www., ) for key, conf in config[platforms].items(): for pattern in conf[url_pattern]: if re.search(pattern, target): return key return default def fetch_html(url, conf, default_ua): 抓取页面。UA、超时、重试次数都从配置里读不硬编码 headers {User-Agent: conf.get(user_agent, default_ua)} if conf.get(referer): headers[Referer] conf[referer] resp requests.get(url, headersheaders, timeoutconf.get(timeout, 10)) resp.raise_for_status() resp.encoding resp.apparent_encoding or utf-8 return resp.text def clean_html_fragment(content_html): 把正文里的 HTML 片段清洗成 Markdown 文本 soup BeautifulSoup(content_html, lxml) for tag in soup.find_all([script, style, noscript, iframe]): tag.decompose() text soup.get_text(\n, stripTrue) return re.sub(r\n{3,}, \n\n, text) def extract_by_selector(html_text, selector): 按 CSS 选择器提取文本没有命中时返回空串 if not selector: return soup BeautifulSoup(html_text, lxml) node soup.select_one(selector) return node.get_text(\n, stripTrue) if node else def main(): url sys.argv[1] config load_config() key match_platform(url, config) conf config[platforms].get(key) html_text fetch_html(url, conf, config[default_ua]) result { platform: key, url: url, title: extract_by_selector(html_text, conf[selectors].get(title)), content: clean_html_fragment( extract_by_selector(html_text, conf[selectors].get(content)) ), author: extract_by_selector(html_text, conf[selectors].get(author)), publish_time: extract_by_selector(html_text, conf[selectors].get(publish_time)), } print(json.dumps(result, ensure_asciiFalse, indent2)) if __name__ __main__: main()核心逻辑是先按 URL 命中平台再从配置里读出该平台的 UA 和选择器最后把选中容器里的 HTML 片段清洗成纯文本。fetch_html 里有一行resp.encoding resp.apparent_encoding or utf-8这是处理中文编码的关键。有些老平台页面声明的是 GBK但没有 HTTP 头字段requests 默认会按 ISO-8859-1 解码抓回来就是乱码。apparent_encoding 会根据字节特征猜测真实编码能覆盖大部分情况。3.3 站点配置拆解把平台差异赶出代码解析逻辑里不能写死任何平台选择器否则每适配一个新平台就要改主流程代码。我把所有平台差异收敛到site_config.json里新增平台时只改配置不动 Python。{ default_ua: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, platforms: { wechat: { url_pattern: [mp\\.weixin\\.qq\\.com/s], selectors: { title: h1.rich_media_title, content: #js_content, author: #js_name, publish_time: #publish_time }, user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, referer: https://mp.weixin.qq.com/ }, default: { url_pattern: [], selectors: { title: h1, content: article, main } } } }这个配置文件里url_pattern 是正则列表用来做平台匹配selectors 是 CSS 选择器组分别对应标题、正文、作者、发布时间user_agent 和 referer 用于绕过反爬校验。default 平台是兜底当 URL 不属于任何已知平台时它会尝试拿article或main容器。这样处理后主流程对平台的感知降到了最低新增平台的过程变成了“加一段配置跑一次测试”。4. 微信/小红书/今日头条适配三个平台的选择器与正则细节4.1 微信公众号UA 校验与 #js_content 正文容器公众号文章是三个平台里结构最稳定的服务端渲染完整正文就在#js_content这个 div 里。但它最烦人的地方是风控。直接用默认 requests UA 去抓很容易被微信识别成机器请求返回一个“环境异常”的验证页面。解决办法是带上完整的浏览器 UA并把 Referer 设置成https://mp.weixin.qq.com/。这两项都写进配置里不放在代码中。微信公众号的解析器还需要做一步验证页检测。验证页的特征是标题变成“环境异常”或者页面里出现“访问过于频繁”这类关键词。如果不做检测抓回来的是一个没有正文的空壳后续总结出来的内容全是“该内容无法显示”。在抓取函数里加一个判空逻辑如果正文容器长度小于 50 个字符就认为触发风控返回错误信息提示用户稍后重试或换个入口链接。# 微信公众号解析核心逻辑附加在 reader.py 的 main 流程中 # 微信正文最小阈值设为 50 字低于这个值基本是验证页或空模板 content_text extract_by_selector(html_text, conf[selectors][content]) if len(content_text) 50: result[error] wechat_risk_control result[content] else: result[content] clean_html_fragment(content_text)50 这个阈值是我自己定的。有的纯图片文章正文确实很短但公众号编辑器会生成一堆空段落文本提取后不会少于 50 字。如果抓到的正文是 30 个字大概率是验证页。这个参数你可以按自己的样本调整核心思路是先判断是否命中反爬再决定要不要继续解析避免把验证页当正文喂给模型。4.2 小红书正文在 window.INITIAL_STATE里不在 HTML 里小红书笔记的 HTML 看起来结构完整但你把正文提取出来往往是空的。这是因为正文数据并不渲染在 DOM 里而是以 JSON 字符串的形式藏在 script 标签里。解析小红书的第一步是在 HTML 里找到window.__INITIAL_STATE__或window.__INITIAL_SSR_STATE__然后提取后面的 JSON 对象。# 小红书解析函数放在 reader.py 里main 流程按平台分发调用 def parse_xhs_state(html_text, note_id): # 兼容新旧两种变量名SSR 版是近期改版后的命名 m re.search( rwindow\.__INITIAL_(?:SSR_)?STATE__\s*\s*(\{.*?\})\s*/script, html_text, re.S) if not m: return {error: xhs_state_not_found} data json.loads(m.group(1)) # 常见数据路径note.noteDetailMap[note_id].note note_map data.get(note, {}).get(noteDetailMap, {}) if note_id not in note_map: return {error: xhs_note_missing} note note_map[note_id][note] tag_list [tag[name] for tag in note.get(tagList, [])] # 图片 URL 常带 !nweb 或 x-oss-process 水印参数要去掉才能看原图 image_urls [] for img in note.get(imageList, []): url img.get(urlDefault) or img.get(url) url re.sub(r!.*$, , url) image_urls.append(url) return { title: note.get(title) or note.get(displayTitle, ), content: note.get(desc, ), author: note.get(user, {}).get(nickname, ), publish_time: note.get(time, 0), tags: tag_list, image_urls: image_urls, }这个函数里有几个参数值得细说。兼容新旧变量名用了一个非捕获分组(?:SSR_)?这样无论小红书前端改成__INITIAL_STATE__还是__INITIAL_SSR_STATE__都能命中。图片 URL 的水印参数通常以!开头或在 URL 里带x-oss-process去掉尾部参数能拿到原图地址。还有一点小程序的笔记 ID 可能过长匹配不上 noteDetailMap 的 key需要在外面单独做一次遍历比对noteId字段常见的做法是先按 URL 里的 ID 找找不到就去 noteDetailMap 里遍历所有 key。小红书还有个登录墙问题。在未登录状态下部分热门笔记会返回重定向到登录页的 HTML。这个页面里同样有__INITIAL_STATE__但 noteDetailMap 是空的。遇到这种情况解析结果里会出现xhs_note_missing错误。我没有太好的绕过办法通常是提示用户换一个链接或者用分享到微信后生成的短链作为输入。4.3 今日头条NEXT_DATA与 JSON-LD 双通道降级今日头条的文章页分为两类。PC 端详情页用 Next.js正文数据嵌在script id__NEXT_DATA__ typeapplication/json里另一类页面使用 JSON-LD 结构化数据正文在script typeapplication/ldjson里。我在解析器里做了双通道降级先找__NEXT_DATA__找不到就找 JSON-LD再找不到才回退到 CSS 选择器。# 头条解析函数同样由 main 流程按平台分发 def parse_toutiao(html_text): # 通道一Next.js 数据最新版头条详情页走这里 m re.search( rscript id__NEXT_DATA__ typeapplication/json(.*?)/script, html_text, re.S) if m: data json.loads(m.group(1)) page_props data.get(props, {}).get(pageProps, {}) article page_props.get(articleInfo, {}) return { title: article.get(title, ), content: clean_html_fragment(article.get(content, )), author: article.get(authorName, ), publish_time: article.get(publishTime, ), } # 通道二JSON-LD 结构化数据适合部分旧版文章页 m re.search(rscript typeapplication/ld\json(.*?)/script, html_text, re.S) if m: data json.loads(m.group(1)) return { title: data.get(headline, ), content: clean_html_fragment(data.get(articleBody, )), author: data.get(author, {}).get(name, ), publish_time: data.get(datePublished, ), } return {error: toutiao_structured_data_not_found}头条__NEXT_DATA__里的articleInfo.content本身是 HTML 片段里面是段落、图片、标题标签的混合体。直接把这个 HTML 字符串传给模型不是不行但会夹杂很多没用的标签而且图片的src可能带有尺寸参数。我一般先过一次clean_html_fragment再把图片 URL 单独提取出来放到image_urls字段让模型在总结时只看文字部分。三个平台适配完我把选择和入口整理成一张表方便对照排查。平台URL 特征正文入口主要风险关键参数微信公众号mp.weixin.qq.com/s/#js_content环境异常验证页UA 校验完整 UA、Referer、正文最短长度 50小红书xiaohongshu.com/explore/window.INITIAL_STATEJSON 变量名改版、图片水印、登录墙兼容 SSR 变量名、去 ! 后缀参数今日头条toutiao.com/article/NEXT_DATA/ JSON-LD两种数据格式并存优先 Next.js降级到 ldjson未知平台任意article / main选择器命中率低兜底配置抓 main 容器5. 网页内容读取器避坑与排查五个高频翻车点5.1 抓回来是空字符串HTML 里却明明有正文现象是解析器返回的 content 是空的但用浏览器打开页面正文明明就在那里。原因大概率是正文由 JavaScript 动态渲染requests 抓到的 HTML 里只有一个空壳容器。解决方法是先检查页面里有没有内嵌的 JSON 状态变量比如__INITIAL_STATE__、__NEXT_DATA__、__NUXT__有就优先解析这些变量里的数据而不是找 HTML 标签。这个方法对小红书和头条都适用。如果页面连 JSON 也没有再考虑上 Playwright 渲染但这种平台毕竟是少数。5.2 微信链接第一次能读第二次就返回“环境异常”现象是同一个公众号链接第一次抓取正常过几分钟再抓就返回验证页。原因是微信对单个 IP 的短时间请求频率有限制连续访问多次会触发风控。解决方法是先在代码里检测验证页特征词检测到就立即停止解析、返回错误提示不要返回空模板然后再做一层请求间隔可以把抓取函数包装成带重试和延时。我一般会设置两次请求之间至少间隔 3 秒连续失败两次就不再重试。如果你的场景需要大量抓公众号文章需要准备代理池不然频率问题绕不开。5.3 小红书正文抓回来了图片却全挂现象是输出结果里标题、正文都对但图片 URL 打不开。原因是小红书的图片地址带有水印参数常见的格式是 URL 后面跟着!nweb、!nd_dft或者x-oss-processimage/...这些参数会把图片改造成带水印的压缩图。解决方法是把imageList每个 URL 里!后面的部分去掉取原图地址如果 URL 里是x-oss-process需要用正则把该参数及其值整段删除。另外还要给图片加 Referer 头直链访问小红书图床会返回 403Referer 设为https://www.xiaohongshu.com/能解。5.4 本地脚本跑通挂到 Claude Code 里就超时现象是python3 reader.py在终端执行一切正常但在 Claude Code 里调用时频繁报超时或者返回结果很慢。原因是 Claude Code 调 bash 工具默认有单次执行的时间上限读取器里如果加了重试逻辑一个链接来回请求 3 次很容易把时间拖到超时。解决方法是把重试次数从 3 降到 1把单次请求超时时间缩短到 10 秒以内把队列里第一个请求跑对同时把抓取和解析拆成两个独立步骤先抓取到本地缓存再解析避免超时后全部重新执行。5.5 输出里混进了导航、广告和评论区现象是解析出的 content 不仅有正文还带着页面底部的“相关阅读”和评论区内容。原因是选择器锚定的容器范围太宽了比如直接选了body或整个div而非正文容器。解决方法是把选择器写具体到正文容器本身比如公众号的#js_content找不到容器时可以用article、main这类语义标签兜底但兜底结果一定要检查一遍。更稳妥的做法是在清洗函数里过滤掉 class 名包含comment、recommend、related的节点避免评论区混入正文。提示所有平台的选择器都会随前端改版失效建议每次解析后保存一条含平台、URL、选择器、正文长度的日志改版时比对日志就能快速定位是哪个选择器挂了。6. 投入前最后一步验证输出质量并沉淀成通用技能读取器写完后别急着扔给 Claude 用。我通常准备一个验证集每个平台找两三条代表性链接跑脚本后人工核对几个指标标题是否准确、正文长度是否合理、发布时间是否有值、图片是否能打开。把结果记录成一张表用前后对比的差异来判断解析是否稳定。平台预期标题实际标题正文字数图片数是否通过公众号文章 AXX 产品发布一致48624是公众号文章 B团队访谈环境异常00否小红书笔记 A周末露营一致3209是头条文章 A行业分析一致21003是验证发现失败后先看是反爬还是选择器问题。公众号验证页可能是频率限制休息几分钟重试即可小红书抓不到内容大概率是变量名又变了去 HTML 里搜索__INITIAL确认新命名。把这次排查经验沉淀回配置文件和代码注释下次同类报错能少走一半弯路。通用化改造的方向是让新增平台像填表一样简单。我的做法是在 site_config.json 里维护一个平台清单整个 Skill 的核心不再关心“这个平台是什么”而是把url_pattern、selectors、parser三个字段变成平台的元信息。将来适配知乎、CSDN、掘金只需新增一段配置并在解析器里挂一个针对性的提取函数。这也是我维护这类读取器的一点简化技巧。希望帮到你。本文还有配套的精品资源点击获取