Next.js 文档站内容藏在 __NEXT_DATA__ 脚本标签中,无需 JS 执行即可直接提取。Docusaurus 则有另一套结构化方案。
每隔几天,就会有一些做爬虫的论坛用户问一个换汤不换药的问题:"我在收集文档站点的文本给 AI 工具用,但这些页面都是 JavaScript 渲染的,有什么最轻量的方式能拿到内容?"
答案总是千篇一律——打开 DevTools,检查 Network 标签页,找到那个 XHR。这些建议没有错,但针对文档站点而言,通常属于过度劳动。
文档站点不是那种任意的 Web 应用。它们绝大多数都由少数几个静态站点生成器构建,而这些生成器会把内容放在可预测的地方。三条路线就能覆盖你遇到的大多数情况,而且根本不需要浏览器。
基于 Next.js 的文档(包括大量公司的开发者门户)会把完整的页面载荷嵌入到一个 __NEXT_DATA__ script 标签中。它就在初始 HTML 响应里——无需执行 JavaScript。
import json, re, httpx
html = httpx.get(url, follow_redirects=True).text
m = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', html, re.S)
if m:
data = json.loads(m.group(1))
# content location varies by site; dump the tree once and look around
print(json.dumps(data["props"]["pageProps"], indent=2)[:2000])
Docusaurus(另一个主流方案)不用 __NEXT_DATA__,但它会把完整文章文本预渲染到静态 HTML 中。一个普通的 httpx.get 加上 main 或 article 选择器就能拿到所有内容。你在浏览器里看到的"JavaScript 渲染"其实是水合作用——用于导航和搜索,正文内容在初始响应中早就存在了。
三十秒验证:curl -s <url> | grep -c "some sentence you can see on the page"。如果返回值大于等于 1,内容就在 HTML 里,搞定了。这个检查能解决大多数"这是 JavaScript 渲染的"问题——因为人们是从 DevTools 的 Elements 面板判断的,那个面板展示的是水合后的 DOM,而不是服务器实际发送的内容。
大多数开源项目的文档都是代码仓库里同名的 Markdown,通常在 docs/ 目录下。爬取渲染后的 HTML 意味着要逐个页面抓取、解析、再剥离你不需要的导航栏。有了克隆,一切搞定,拿到的是干净源文件:
git clone --depth 1 --filter=blob:none --sparse https://github.com/org/project
cd project && git sparse-checkout set docs
你拿到的是原始 Markdown——标题完整、代码围栏完整,没有侧边栏导航、没有 Cookie 横幅、没有逐页速率限制。对于 RAG 流水线来说,这比解析后的 HTML 是更优质的输入,而且一条请求代替了几百条。
值得明说的是:摄入之前先检查许可证。文档的许可证经常和代码是分开独立的,"仓库是公开的"不等于"你可以重新分发这些内容"。
越来越多的文档托管方提供了纯文本视图:
/llms.txt——一个新兴约定,站点会发布一份精心整理的纯文本站点地图,专供 LLM 使用。快速迭代的开发者工具公司已经迅速采纳了它。值得发一条请求试试。
/sitemap.xml——不是文本内容,但能给你完整的 URL 列表而无需爬取,这意味着你永远不需要通过跟随链接来发现页面。
ReadTheDocs 项目通常提供可下载的 HTML,往往还有整个文档集的 PDF/ePub 构建(来自版本菜单)。一个产物,完整内容。
有些情况确实是动态的,知道这些能帮助你不过度套用上面的方法:
不过对于几百页的正文来说,这些都是例外。默认假设应该是:文本已经是可达的,浏览器才是备用方案。
这件事重要的原因不是洁癖——而是无头浏览器会改变你项目的形态。你从一个任何人都能跑的脚本变成了一条需要浏览器二进制、内存上限、每页启动成本、以及一类新的不稳定失败的流水线——这类失败只在某些时候能复现。在几百个文档页面的规模下,路线一或路线二通常在基于浏览器的爬取还没完成启动时就结束了。
先检查一下内容是不是已经躺在那里等着你。大多数时候,它就在。