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。这样:

关键实现要点:预渲染脚本必须输出与前端路由一致的 URL 结构,否则会出现两份内容相同、URL 不同的页面(重复内容问题,见第 04 节的 canonical)。


二、robots.txt:告诉爬虫能抓什么

放在域名根目录(注意:是 /robots.txt,不是 /learning/robots.txt——搜索引擎只认根目录这一份):

User-agent: *
Allow: /

Sitemap: https://www.xxxxxx.cn/learning/sitemap.xml

三条注意事项:

  1. Sitemap: 必须写绝对 URL,且带 https。 写成 http 或相对路径会被忽略。
  2. 别用 Disallow: / 挡住自己。 很多脚手架默认生成的 robots.txt 是全站禁止,上线忘了改——这是"其他都对但死活不收录"的经典原因。
  3. 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 的条数与状态码分布
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/">

配套规则:

  1. 尾斜杠策略必须唯一:决定用 /lessons/1/ 就全站统一,/lessons/1 用 301 跳过去
  2. http 与裸域名全部 301 到 canonical(上一门课第 06 章已配置)
  3. canonical 必须自引用:页面 A 的 canonical 指向自己,不要指向首页
  4. canonical 与 sitemap、内链保持一致——三者不一致时,搜索引擎会自己猜

验证

curl -s https://www.xxxxxx.cn/learning/courses/tools/lessons/1/ \
  | grep -oE '<link rel="canonical"[^>]*>'

对比 sitemap 里同一页面的 URL:必须逐字符相同。


五、URL 结构与状态码

URL 设计三原则

  1. 可读:/learning/courses/tools/lessons/1/ 优于 /p?id=87
  2. 稳定:发布后不要改。必须改时做 301,并更新 sitemap 与内链
  3. 有层级:/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/

地基打完,下一章给每个页面补上自我描述:标题、描述与结构化数据。

进入 keel 阅读