尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
微信公众号文章抓取与HTML转Markdown:MCP工具接入AI编码助手
先把结论摆在这儿Claude Code、Codex 这类跑在终端里的编码助手处理本地代码早就是熟手了但一碰到微信公众号文章就抓瞎——链接贴进去多半给你一句无法访问该页面或者干脆读出一堆残缺的样式标签。这事卡了很久直到最近我把抓取、清洗、转换这一整套流程封装成一个能被调用的工具挂上去才算真正打通。接下来写的东西就是我在反复折腾里踩出来的完整路径。这套东西解决的核心问题其实就一句话把公众号页面里那坨被内联样式裹得严严实实的 HTML变成模型能直接消化、结构清晰的 Markdown再通过工具调用的方式交给 AI 编码助手。写完之后你在 Claude Code 里丢一句帮我读一下这篇文章把里面的实现思路整理成任务清单它能真的读进去、理解、然后落成代码。适合谁看写过一点脚本、会用命令行、想让 AI 助手把技术文章当参考资料用起来的人。完全没有编程基础也能看懂设计思路代码部分照着改改就能跑。1. 需求背后到底卡在哪先把痛点掰开1.1 公众号是个内容孤岛AI 工具天生够不着先说清楚为什么会出现读不了这件事。微信公众号的文章页面不是普通的静态网页它把正文、图片、样式全部塞在一个高度嵌套的 DOM 结构里几乎所有视觉呈现都靠内联style属性硬写而不是用外部 CSS 类。这种设计的初衷是让页面在客户端里渲染效果一致但对自动化工具极不友好。更麻烦的是访问层。直接发一个裸的 HTTP 请求过去服务器会看你的User-Agent、Referer、Accept这些请求头稍微不像正常浏览器访问就可能返回环境异常请稍后在微信客户端打开这类提示页。而且页面里的图片资源有防盗链校验Referer不对直接 403。你就算把 HTML 抓下来了图片全是裂的。AI 编码助手的工作方式决定了它不可能自己去点浏览器。它的能力边界就在文件系统和它被授权的工具上。你给它一个 URL它顶多调用一次普通的网页抓取拿回来的内容往往是被裁剪过的摘要或者干脆是报错。所以问题的本质不是模型不够聪明而是它手里没有一把能开这把锁的钥匙。我最初的几次尝试都很朴素手动打开文章全选复制粘贴到一个.md文件里然后让助手去读。这个土办法能用但代价太大——一篇文章从打开到整理成可读文本五分钟起步而且粘贴进编辑器的时候代码块的语言标识全丢了行号错乱图片变成一串没有任何意义的占位符。做得多了就明白这个环节必须自动化否则让 AI 读文章就是个伪需求。1.2 就算能打开正文也不是干净的文本假设你已经绕过了访问层真正难的才开始。我见过太多人以为抓公众号就是requests.get()加一个 CSS 选择器搞定结果拿到的 Markdown 惨不忍睹。第一个坑是正文节点。文章主体的容器是一个固定 id 的元素但它的父级链条上挂着一堆装饰性节点底部的二维码、点赞引导、作者名片、相关推荐、广告位。这些内容如果不清理转换出来就是一坨噪音白白消耗上下文长度还会干扰模型对正文结构的判断。第二个坑是懒加载图片。页面里的img标签src属性指向的往往是一张 1×1 的透明占位图真实地址藏在>python -m venv .venv source .venv/bin/activate pip install requests beautifulsoup4 lxml markdownify mcp浏览器降级方案另外装pip install playwright python -m playwright install chromium注意最后这条要单独跑一次它才是真正下载浏览器内核的步骤光装 Python 包是不够的。这一步下载体积不小第一次装会等一会儿。如果你更习惯 JavaScript 生态等价组合是undici或node-fetch做请求、cheerio做解析、turndown做转换、playwright做降级。逻辑完全一样我就不重复写了下面全部以 Python 版本为例。4.2 核心脚本抓取、清洗、转换一条龙先写请求部分。请求头这块别偷懒该带的都带上import requests UA ( Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 ) def build_session(): s requests.Session() s.headers.update({ User-Agent: UA, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://mp.weixin.qq.com/, }) return s def fetch_html(url, sessionNone, timeout15): s session or build_session() resp s.get(url, timeouttimeout) resp.raise_for_status() resp.encoding utf-8 return resp.textresp.encoding utf-8这行一定要写原因前面讲过。别看它不起眼漏了之后中文全乱你还得回头重来。接下来是核心的提取函数。顺序很重要先取图片真实地址再清属性最后转换。from bs4 import BeautifulSoup from markdownify import markdownify DROP_SELECTORS [ script, style, noscript, .qr_code_pc, #js_pc_qr_code, .rich_media_tool, .reward_area, .weui-msg, mp-common-profile, mp-miniprogram, qqmusic, ] IMG_ATTRS (data-src, data-lazy-src, data-original, data-backsrc) def extract_article(html, keep_images_linkTrue): soup BeautifulSoup(html, lxml) # 标题 title meta soup.find(meta, attrs{property: og:title}) if meta and meta.get(content): title meta[content].strip() if not title: node soup.find(idactivity-name) if node: title node.get_text(stripTrue) body soup.find(idjs_content) if body is None: raise ValueError(未找到正文节点可能是验证页或文章不可见) for sel in DROP_SELECTORS: for tag in body.select(sel): tag.decompose() # 先处理图片真实地址 for img in body.find_all(img): real for attr in IMG_ATTRS: if img.get(attr): real img[attr] break if not real: real img.get(src, ) if real.startswith(//): real https: real img[src] real img[alt] img.get(alt, ).strip() for comment in body.find_all(stringlambda t: isinstance(t, type(soup.Comment))): comment.extract() # 清属性保留必要的 for tag in body.find_all(True): keep {} for attr in (src, alt, href): if tag.has_attr(attr): keep[attr] tag[attr] tag.attrs keep md markdownify( str(body), heading_styleATX, bullets-, strip[span], ) # 折叠多余空行 lines [ln.rstrip() for ln in md.splitlines()] out, blank [], 0 for ln in lines: if not ln: blank 1 if blank 1: continue else: blank 0 out.append(ln) return title or untitled, \n.join(out).strip()几个点值得单独说。strip[span]这个参数挺关键。公众号正文里 span 用得极其泛滥几乎每个字都被包一层。转换库默认会把 span 当行内元素处理结果输出里到处是零碎的换行。指定剥离之后文本会重新连成整句。删除名单里的mp-common-profile和mp-miniprogram是自定义标签名删它们需要解析器支持自定义标签lxml没问题。如果你用的是纯 Python 解析器可能得换一种匹配方式。标签属性的白名单只留src、alt、href三个其他全扔。这样链接和图片能保留样式和事件相关的属性一律不进入输出。4.3 失败识别与浏览器降级判断第一层是否真的成功了不能只看有没有抛异常。有些情况是成功返回了 HTML但内容是验证页。我的做法是检查两个信号正文节点是否存在正文文本长度是否超过一个阈值。def looks_like_article(html, min_len200): if idjs_content not in html: return False soup BeautifulSoup(html, lxml) body soup.find(idjs_content) if body is None: return False return len(body.get_text(stripTrue)) min_len降级路径用 Playwrightfrom playwright.sync_api import sync_playwright def fetch_with_browser(url, wait_ms1500): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) ctx browser.new_context( user_agentUA, localezh-CN, viewport{width: 1280, height: 900}, ) page ctx.new_page() page.goto(url, wait_untildomcontentloaded, timeout30000) try: page.wait_for_load_state(networkidle, timeoutwait_ms) except Exception: pass html page.content() browser.close() return htmlwait_for_load_state外面包了 try因为有些页面永远等不到网络空闲比如挂着长轮询不能让它把整个请求拖死。超时就直接取当前 DOM够用了。4.4 封装成工具接口挂到助手侧这部分是我觉得最有价值的一步。脚本跑通只是自娱自乐挂成工具才是真正接进工作流。用官方 SDK 写一个最小服务import hashlib, os, pathlib from mcp.server.fastmcp import FastMCP mcp FastMCP(wechat-reader) CACHE pathlib.Path.home() / .cache / wechat-reader CACHE.mkdir(parentsTrue, exist_okTrue) mcp.tool() def read_wechat_article(url: str, force_browser: bool False) - str: 读取微信公众号文章正文返回 Markdown 文本。 Args: url: 文章链接支持 /s/ 短链和带 sn 参数的完整链接 force_browser: 为 True 时直接用浏览器渲染跳过 HTTP 尝试 key hashlib.sha1(url.encode()).hexdigest()[:16] cache_file CACHE / f{key}.md if cache_file.exists(): return cache_file.read_text(encodingutf-8) html fetch_html(url) if force_browser or not looks_like_article(html): html fetch_with_browser(url) title, md extract_article(html) result f# {title}\n\n{md} cache_file.write_text(result, encodingutf-8) return result if __name__ __main__: mcp.run()工具的描述文字docstring不要敷衍。模型就是靠这段文字判断什么时候该调用、参数怎么填。写得含糊它要么不敢调要么乱填参数。注册到 Claude Code 里项目根目录放一个.mcp.json{ mcpServers: { wechat-reader: { command: python, args: [-m, wechat_reader.server], env: { PYTHONPATH: /absolute/path/to/project } } } }Codex 侧用的是 TOML 风格的配置[mcp_servers.wechat-reader] command python args [-m, wechat_reader.server]路径用绝对路径别用相对路径我在这上面栽过——助手进程的工作目录跟你终端里的不一样相对路径必然找不到。4.5 参数与容量估算几个数值我实测过直接给参考。请求超时设 15 秒。低于 8 秒遇到稍慢的页面就开始误判失败高于 20 秒网络真出问题时你等得心焦。批量场景下建议并发控制在 2 到 3请求间隔随机 1 到 3 秒。按这个节奏一百篇文章大概十几分钟跑完期间基本不会被判成异常流量。缓存键我用的是链接的哈希。更好的做法是优先提取链接里的文章唯一标识参数用它做键——同一篇文章可能通过不同的短链形式访问用标识参数才能正确命中。链接里没有那个参数的时候再退回哈希。容量估算前面提过一次这里给完整版。纯文本的中文文章一万字大约对应七千到一万 token。一篇典型的公众号技术文三五千字加代码块和结构标记转换后的 Markdown 大概在八千到一万五千 token 之间。这个体量完全在常规上下文窗口范围内不需要分段。但一旦把图片内联进来账就变了。单张 300KB 的图内联后十万 token 量级三张就把舒适区吃光。所以我的红线是内联图片总数不超过两张且只在内联后总 token 估算低于五万时才执行。超过就自动降级成外链。还有一个隐形开销是文件读写。如果你让助手直接读本地文件而不是通过工具返回字符串它会先列目录、再读文件、可能还要分页读来回好几轮。短文章直接返回字符串更省事长文章才落盘给路径。5. 提示词与工作流让它真正读懂而不只是读到5.1 三段式提示词工具能调通了不等于模型用得对。我摸索出来的做法是把任务拆成三段一次只让它干一件事。第一段只做结构识别。这是一篇文章的 Markdown 原文请只输出它的章节大纲保留原始标题层级不要总结内容。 这一步的目的是让模型先建立全局地图。直接让它总结全文长文很容易顾头不顾尾中段内容被略过。第二段做定点提取。根据上一步的大纲逐节提取技术方案要点每节不超过五行保留原文中的参数、命令、代码片段。第三段才是落地。把第二段里提到的方案改写成能在本地跑起来的分步任务清单每一步标注依赖和验证方式。这三段可以放在一个会话里连续问也可以拆成三次调用。我的体验是拆开更可靠尤其是文章超过一万 token 的时候。会话里前面塞太多内容后面推理质量会肉眼可见地下降。还有一条规则我固定加在系统提示里遇到无法确定的内容明确标注原文未说明不要自己编。公众号文章经常有前后文省略的情况模型补全得很顺但那不是原文的意思用作参考会出问题。5.2 长文的分段策略超过一万五千 token 的文章我会在抓取阶段就按二级标题切开每段单独送进去。切分点选标题是因为那里语义边界最清晰不容易把一句话劈成两半。具体做法是在转换完成后扫一遍输出找到所有以两个井号开头的行按位置切成若干块。每块前面补一个上下文头写清楚这是第几段属于什么主题这样模型处理每一块的时候都知道自己在整体里的位置。分段之后还有个合并问题。让模型分别输出各段要点最后再来一次汇总比一次性喂全文效果好不少。汇总那一步可以把前面各段的输出拼起来此时总长度已经压到原文的三分之一以下模型处理起来轻松很多。5.3 一个能直接抄的工作流我日常用的流程大概是这样第一步在项目目录里新建一个refs/文件夹专门放参考资料。这个目录加进.gitignore避免把别人的文章内容提交到仓库里。第二步在助手会话里说用 wechat-reader 读一下这个链接存到 refs 下文件名用文章标题。 它会调用工具、拿到 Markdown、写文件。文件名里的标点符号它会自己处理但我一般会补一句文件名里的冒号和斜杠替换成短横线省得它生成非法路径。第三步另起一轮读 refs 下刚存的那篇列出它提到的所有技术选型每项一句话说明它的取舍理由。第四步如果要落地按第三点的结论给我一个最小可跑的目录结构和关键文件内容。这个流程跑下来从看到文章到拿到可执行的方案中间不需要手动复制任何东西。这是我折腾这套东西最大的收益。6. 常见问题与排查实录6.1 抓取阶段的典型故障返回内容是一段提示页正文节点找不到。九成是请求头的问题先检查User-Agent是不是太老或者太假再检查有没有带Referer。如果都正常多半是请求频率触发了限制隔几分钟重试或者直接走浏览器降级。浏览器里能打开脚本里打不开。差异通常出在 Cookie。部分文章页面在无登录态下只返回摘要。这种情况我不建议在脚本里硬闯正确做法是在本地浏览器里正常访问一次导出 Cookie 存到本地配置文件脚本读取后带上。这份文件必须排除在版本控制之外也不要在任何地方明文写出来。图片全部是空白。检查三件事>
RELATED

相关推荐

QtHttpServer离线接入与HTTPS调通实战

QtHttpServer离线接入与HTTPS调通实战

简介:这份压缩包面向需要在Qt应用中集成HTTP/HTTPS服务的开发者,汇集QtHttpServer主分支源码、可运行测试用例以及HTTPS所需的证书密钥文件,可用于搭建本地API、静态资源服务或学习Qt网络编程。包内共264个文件,约6.75MB&#xff…

📅 2026/10/23 1:11:24
Windows上AI执行PowerShell命令频繁报错?排查链路与修复方案

Windows上AI执行PowerShell命令频繁报错?排查链路与修复方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/23 1:11:24
xtquant历史行情下载实战:批量拉取与增量更新技巧

xtquant历史行情下载实战:批量拉取与增量更新技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/23 1:11:24
MORE NEWS

更多资讯

📰

从原始权重到MLX格式:Tess-4-27B-8bit转换原理、生态路线与社区支持

【免费下载链接】Tess-4-27B-8bit 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/Tess-4-27B-8bit 点击查看 免费下载 Tess-4-27B-8bit 是一个已完成 MLX 格式转换的 270 亿参数多模态视觉语言模型(VLM),由 mlx-co…

📰

一行命令干净禁用 Windows Defender 与防火墙,还能随时恢复

一行命令干净禁用 Windows Defender 与防火墙,还能随时恢复 【免费下载链接】no-defender A slightly more fun way to disable windows defender firewall. (through the WSC api) 项目地址: https://gitcode.com/GitHub_Trending/no/no-defender 你把 Def…

📰

找不到 ImageResize+ 节点?一文讲清它的真身与搭配 ComfyUI-layerdiffuse 的完整方法

找不到 ImageResize 节点?一文讲清它的真身与搭配 ComfyUI-layerdiffuse 的完整方法 【免费下载链接】ComfyUI-layerdiffuse Layer Diffuse custom nodes 项目地址: https://gitcode.com/GitHub_Trending/co/ComfyUI-layerdiffuse 跑 ComfyUI-layerdiffuse 的…

📰

如何参与Levyra开源开发:代码结构、工程规范与翻译贡献完整指南

【免费下载链接】Levyra-deepsound Made for listening, not logging in. Levyra is an open-source music player for Android and Windows, bringing music discovery, synced lyrics, radio and beautiful artwork together. No ads, no tracking, just music ♫ 项目地址&a…

📰

免费AI学习资源库free-ai-resources-x是什么?411+精选资源与30大分类完整指南

【免费下载链接】free-ai-resources-x 🌟 A curated collection of free, high quality AI tools 🤖, APIs 🔗, datasets 📊, and learning resources 📚 covering machine learning 🧠, deep learning &am…

📰

Taxi快速上手:10分钟用cocotb+Verilator跑通你的第一个AXI-Stream仿真

硬件开发网络嵌入式 【免费下载链接】taxi AXI, AXI stream, Ethernet, and PCIe components in System Verilog 项目地址: https://gitcode.com/gh_mirrors/taxi7/taxi 点击查看 免费下载 🚀 想学习 AXI-Stream 仿真 却不知从何下手?Taxi 是…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬