KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
01 · 技术地基:爬虫能不能抓到完整内容 — keel 龙骨
本章目标:让搜索引擎在不执行 JavaScript 的情况下,也能拿到你页面的完整正文。 验收标准:curl 页面源码里能看到文章正文,且 robots.txt / sitemap.xml 均可访问。
本章目标:让搜索引擎在不执行 JavaScript 的情况下,也能拿到你页面的完整正文。
验收标准:curl页面源码里能看到文章正文,且 robots.txt / sitemap.xml 均可访问。
为什么这是第 01 章:SEO 里绝大多数"搜不到",根因都在这里。内容再好,爬虫抓不到或抓到空壳,后面所有优化都是零。
一、先搞清楚爬虫看到的是什么
一个 React/Vue 单页应用(SPA)默认是这样的:
<div id="root"></div>
<script src="/assets/index-abc123.js"></script>
爬虫拿到这份 HTML 时,正文还没被 JS 渲染出来。Google 会执行 JS 再抓一次(二次渲染),但这个队列慢且不稳定;百度、Bing 以及绝大多数 AI 爬虫基本不执行 JS。结论:
依赖客户端渲染 = 把收录机会交给运气。
自检方法(最直接的判据)
# 关掉 JS 后看到的内容 = 爬虫看到的内容
curl -s https://www.xxxxxx.cn/learning/courses/tools/lessons/1/ | grep -c '本章目标'
返回 0 → 你的正文不在 HTML 里。三种解法:
| 方案 | 做法 | 适用 |
|---|---|---|
| SSG / 预渲染(推荐) | 构建时为每个路由生成一份含正文的静态 HTML | 内容型站点,路由可枚举 |
| SSR | 每次请求服务端渲染 | 动态内容、个性化 |
| 同构框架(Next/Nuxt) | 框架层面解决 | 新项目 |
本站采用的是 route shell 预渲染:构建脚本按内容清单为每个课程/章节生成一个 HTML 外壳,正文直接内联进 HTML。这样:
- 爬虫拿到完整文本 → 收录无障碍
- 首屏直出 → 用户体验与 LCP 同时受益
- 不需要改造运行时架构
关键实现要点:预渲染脚本必须输出与前端路由一致的 URL 结构,否则会出现两份内容相同、URL 不同的页面(重复内容问题,见第 04 节的 canonical)。
二、robots.txt:告诉爬虫能抓什么
放在域名根目录(注意:是 /robots.txt,不是 /learning/robots.txt——搜索引擎只认根目录这一份):
User-agent: *
Allow: /
Sitemap: https://www.xxxxxx.cn/learning/sitemap.xml
三条注意事项:
Sitemap:必须写绝对 URL,且带 https。 写成 http 或相对路径会被忽略。- 别用
Disallow: /挡住自己。 很多脚手架默认生成的 robots.txt 是全站禁止,上线忘了改——这是"其他都对但死活不收录"的经典原因。 - robots.txt 只是"君子协定",不是访问控制。要真正阻止访问请用认证或
noindex响应头。
对 AI 爬虫的单独处理
现在有一类新爬虫:GPTBot(OpenAI)、ClaudeBot(Anthropic)、PerplexityBot、Bytespider(字节)、Google-Extended。要不要放行,取决于你的立场:
# 允许全部 AI 爬虫(默认,不写即允许)
User-agent: *
Allow: /
# 若只想拒绝某几个,单独写(它们遵循 robots.txt)
User-agent: GPTBot
Disallow: /
技术内容站点通常建议放行:AI 摘要正在成为新的发现入口,被引用带来的曝光是实打实的。可以先观察日志(第 05 章)看看它们抓了什么,再决定。
验证
curl -s https://www.xxxxxx.cn/robots.txt
curl -s -o /dev/null -w "%{http_code}\n" https://www.xxxxxx.cn/robots.txt # 必须 200
三、sitemap.xml:把页面清单交出去
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://www.xxxxxx.cn/learning/</loc>
<lastmod>2026-09-30</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.xxxxxx.cn/learning/courses/tools/lessons/1/</loc>
<lastmod>2026-09-30</lastmod>
</url>
</urlset>
硬性规则(违反会导致整个 sitemap 被拒)
- 只包含你希望被收录的 URL,且这些 URL 必须返回 200(不能是 301/404/500)
- URL 必须与 canonical 完全一致(同域名、同大小写、同结尾斜杠)
- 全部 https
- 单文件上限:50,000 条 URL / 50MB 未压缩。超了用 sitemap index 拆分
lastmod要真实。全站填同一个日期等于没填——它是爬虫判断"要不要重抓"的依据
生成与校验
本站的做法是构建时生成,与内容清单同源,永远同步:
# 生成后做一次自检:列出所有 URL 的条数与状态码分布
grep -o '<loc>[^<]*</loc>' sitemap.xml | wc -l
# 抽 5 条实际访问一遍,确认都是 200
grep -o '<loc>[^<]*</loc>' sitemap.xml | sed 's/<[^>]*>//g' | shuf -n 5 \
| while read -r u; do printf "%s " "$(curl -s -o /dev/null -w '%{http_code}' "$u")"; echo "$u"; done
这一步不能省:sitemap 里混进 404 会拉低整个站点地图的可信度,GSC 会直接报"无法读取"。
四、canonical:唯一的权威 URL
同一个内容可能出现多个 URL:
https://www.xxxxxx.cn/learning/courses/tools/lessons/1/
https://www.xxxxxx.cn/learning/courses/tools/lessons/1 # 无尾斜杠
http://www.xxxxxx.cn/learning/... # http 版本
https://xxxxxx.cn/learning/... # 裸域名版本
搜索引擎会认为这是四个页面,权重被分散,甚至互相判定为重复内容。解法是每个页面声明自己的规范形式:
<link rel="canonical" href="https://www.xxxxxx.cn/learning/courses/tools/lessons/1/">
配套规则:
- 尾斜杠策略必须唯一:决定用
/lessons/1/就全站统一,/lessons/1用 301 跳过去 - http 与裸域名全部 301 到 canonical(上一门课第 06 章已配置)
- canonical 必须自引用:页面 A 的 canonical 指向自己,不要指向首页
- canonical 与 sitemap、内链保持一致——三者不一致时,搜索引擎会自己猜
验证
curl -s https://www.xxxxxx.cn/learning/courses/tools/lessons/1/ \
| grep -oE '<link rel="canonical"[^>]*>'
对比 sitemap 里同一页面的 URL:必须逐字符相同。
五、URL 结构与状态码
URL 设计三原则
- 可读:
/learning/courses/tools/lessons/1/优于/p?id=87 - 稳定:发布后不要改。必须改时做 301,并更新 sitemap 与内链
- 有层级:
/courses/<course>/lessons/<n>/天然表达归属关系,利于爬虫理解站点结构
状态码的正确用法
| 场景 | 状态码 | 说明 |
|---|---|---|
| 正常页面 | 200 | — |
| 永久迁移 | 301 | 传递权重,更新 canonical |
| 临时调整 | 302 | 不传递权重 |
| 已删除 | 404 | 或 410(Gone,更强) |
| 未登录/付费内容 | 401/403 | 不要用 200 返回"请登录"页 |
| SPA 未知路由 | 200(回退 index.html) | 但未知内容应返回 404,见下 |
⚠️ SPA 的一个常见错误:所有路径都回退到 index.html 并返回 200,于是 /learning/不存在的页面 也返回 200,被爬虫当成"软 404"收录。
修法:在预渲染阶段只为真实存在的路由生成 shell,不存在的路径返回真正的 404。
curl -s -o /dev/null -w "%{http_code}\n" https://www.xxxxxx.cn/learning/definitely-not-exist/
# 期望 404
六、本章验收清单
# 1. 正文在 HTML 里(关 JS 也能看到)
curl -s https://www.xxxxxx.cn/learning/courses/tools/lessons/1/ | grep -c '本章目标'
# 2. robots.txt 可访问且指向 https sitemap
curl -s https://www.xxxxxx.cn/robots.txt
# 3. sitemap 可访问、条数正确、抽样全 200
curl -s https://www.xxxxxx.cn/learning/sitemap.xml | grep -c '<loc>'
# 4. canonical 与 sitemap 一致
curl -s https://www.xxxxxx.cn/learning/ | grep -oE 'rel="canonical"[^>]*'
# 5. 不存在的路径返回 404
curl -s -o /dev/null -w "%{http_code}\n" https://www.xxxxxx.cn/learning/not-a-real-page/
- 正文出现在初始 HTML 中(不依赖 JS)
-
robots.txt在根目录、Allow 全部、Sitemap 行为 https 绝对地址 -
sitemap.xml全部 200、全 https、lastmod 真实 - canonical 自引用且与 sitemap / 内链一致
- 尾斜杠策略唯一,http 与裸域名均 301
- 未知路径返回 404 而非 200
地基打完,下一章给每个页面补上自我描述:标题、描述与结构化数据。