OGKit

SPA 爬虫抓不到 Open Graph:SSR 与预渲染

  • open-graph
  • spa
  • ssr
  • 调试

你打开页面、打开 Elements,meta 都在:og:titleog:image、完整描述。同一条 URL 丢进 Slack 或 X,却是一条裸链接。别人问「是不是标签写错了」,你觉得明明写对了。

标签可以在浏览器里正确,对爬虫仍然不存在。这就是 SPA 的 Open Graph 陷阱。

用两次请求复现

先别和 DevTools 争论,先问:线上实际返回了什么。

# 社交爬虫大致拿到的:首包 HTML,不执行 JS
curl -sL -A "facebookexternalhit/1.1" https://example.com/app/product/42 | grep -iE 'og:|twitter:'

# 普通 UA(同样不执行 JS)
curl -sL https://example.com/app/product/42 | grep -iE 'og:|twitter:'

如果这两条 grep 是空的,平台不会从你 hydration 后的 DOM 里「变」出标签。查看网页源代码(不是 Elements 实时树)做的是同一件事:源码是响应体,Elements 是跑完 JS 之后的树。

你在看什么 代表谁 对分享卡片是否可信
浏览器 Elements / React DevTools 登录后、执行完 JS 的真人
当前页上的浏览器扩展 已渲染 DOM(用户所见) 验「人看到的」高,代替爬虫低
curl / 查看源代码 / 公网扫描 首包 HTML 对爬虫高
平台官方 debugger 该平台的缓存与抓取规则 对该平台权威

扩展读已渲染 DOM,很适合登录态、localhost、以及「编辑器有没有写对标签」。它不能代替爬虫视角。两层必须分开说,否则你会永远修错层。

为什么 CSR meta 能骗过开发者

客户端路由和 useEffect(或只在 mount 后跑的 helmet 类库)会在 hydration 之后往 document.head 写标签。人眼几乎看不出来:预览扩展、标签页标题、甚至「检查元素」都像已经完成。

多数社交爬虫不执行(或只很有限地执行)JavaScript。它们拉 HTML、解析 meta、缓存、走人。只靠 JS 才出现的标签,等于不存在。这和相对路径 og:image、爬虫被拦是同一类「卡片空白」,只是原因不同。五种空白拆法见为什么你的 Open Graph 卡片一片空白

有用的二分法:

  1. 标签写错 —— 原始 HTML 里有字段,但值 / URL / 图有问题。改内容与图片链路。
  2. 爬虫根本拿不到 —— 字段只在 JS 之后出现(或只对非 bot UA 出现)。改投递:SSR、预渲染,或给可分享 URL 静态 HTML。

混在一起会浪费整周:你在打磨文案,爬虫拿到的仍是空壳。

框架对策(够用来选型)

不必打框架战争。你要的是:凡是别人会分享的 URL,首包 HTML 里就要有 meta

做法 对 OG 意味着什么 常见场景
SSR / 流式 SSR 每次请求服务端写出 head Next App Router metadata、Nuxt useSeoMeta、Remix loader
SSG / 构建期 HTML 已知路径在文件里就有真实 meta 营销页、文档、slug 已知的商品/文章
预渲染 / 渲染服务 对爬虫返回 HTML 快照 短期内重写不动的遗留 CSR
边缘注入 / 改写 CDN 对 bot 注入或替换 meta 过渡方案;最终仍应回到应用内拥有标签
纯 CSR 壳 + 客户端填充 后台壳可以;公开分享 URL 不行

实操里常踩的点:

  • Next.jsgenerateMetadata / 静态 metadata 必须在该路由的服务端跑。只在客户端 useEffect 里改 document.title 修不好 OG。
  • Nuxt 等:用框架自带、SSR 感知的 head API,让标签进响应体,而不是只进浏览器。
  • Prerender 类服务与「动态渲染」:能在不大改架构时先让爬虫看见内容。那是基础设施与缓存策略,不是免检通行证——仍要用 curl 和官方 debugger 验收。
  • 只对少数关键分享模板做构建期静态化,往往就够用。

若产品是登录后的纯管理后台、深链几乎不对外分享,这篇文章可能不是你的事故。若营销页、商品页或用户公开页天天被转发,纯 CSR meta 是结构问题,不是文案笔误。

没有 SSR 时,报告该怎么说

诚实的工具会分层。公网扫描若只看见首包 HTML:

  • 200 成功但没有任何分享标签 → 应警告「标签可能只在客户端 JS 之后才有」(规则 og-tags-require-javascript),而不是武断写死「作者忘了 og:title」。
  • HTML 里有标签但图失败 → 图片工程问题,不是 SPA 投递问题。
  • 扩展 DOM 快照有标签、原始 HTML 没有 → 双 HTML 证据已坐实:爬虫看不见(OGKit 在同时拿到两侧时会按错误级证据处理)。

工具做不到的事:替你设计 SSR 架构、迁移客户端路由、保证每个 bot 都执行你的 bundle。诊断止于「爬虫拿不到这些标签」。架构是下一场对话。

何时该找人做改造

证据清楚、改动是结构性的,再升级:

  • curl / 查看源代码没有 og:*,hydration 后的应用有
  • 成百上千条客户端路由 URL 都要可分享
  • 单个静态 index.html 壳列不全商品/文章路径
  • 需要「预渲染过渡 + SSR/SSG 目标态」,而不是再装一个 meta 生成器

这类需求可以看咨询包:预渲染 / SSR——那是可选的人手,不是写出正确标签的前提。很多团队只要诊断够锐,自己改几个关键模板就够。

若只是缓存还在显示旧空壳,去 rescrape,不是上 SSR。若只是图片体积或重定向,去图片工程。走错分支会多耗一周。

清单:标签写错,还是爬虫瞎了

  • curl(或查看源代码)里的 og:* / twitter:* 与加载后一致
  • 被分享的 URL 不是「document head 为空」的纯客户端路由
  • Bot UA 没有被拦而浏览器正常(相关规则见 robots-blocks-social-crawlers
  • 没有把「仅扩展绿灯」当成 Facebook / LinkedIn / Slack 的验收
  • 修好投递之后,对已缓存空壳的平台做了重新抓取

两个视角各试一次

  1. 把公网生产 URL 贴进首页扫描——爬虫视角的 HTML、命名规则、不碰本机权限。
  2. 只在登录后或 localhost 才存在的页面,用扩展扫当前页——那是已渲染 DOM。若公网扫描为空、扩展却是满的,SPA 缺口一张对比就成立。

OGKit 不会默默改写你的 React 树。它会标清你落在表的哪一侧:标签内容有问题,还是爬虫从来没机会看见。分清这一点,就是本文的全部工作。