搜索全站
百度SEO 10 分钟阅读

百度对 JS 渲染内容的抓取现状:前后端分离站点的可行方案

Vue/React 做的站点在百度收录不理想,到底是渲染问题还是内容问题?本文梳理百度渲染爬虫的能力边界,对比 SSR、静态化、动态渲染三条路线,并给出可自建的预渲染服务示例。

U
UFO_PF 2024-01-11 · 阅读 10 分钟
百度 百度SEO
阅读提示
本文约 3,977 字,预计阅读 10 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:页面在浏览器里好好的,百度抓到的是空壳

前后端分离的站点投放到百度,最常见的现象是:首页收录了,标题和摘要却是空的;内页压根不收录;抓取诊断里看到的源码只有 <div id="app"></div> 加一串 script 标签。开发同学往往觉得「百度不支持 JS」,于是要么放弃 SPA,要么干脆做两套站。实际情况比这复杂一些:百度确实有带渲染能力的抓取 UA(Baiduspider-render),但渲染是异步、排队、有配额的,且渲染时机晚于首次索引。也就是说,依赖渲染的页面不是不能被收录,而是慢、不稳定、覆盖率低。对于靠长尾流量吃饭的内容站,这种不确定性是不可接受的。

原理分析:两条抓取管线,两条不同的命运

百度对页面的处理可以理解为两条管线:第一条是「源码管线」,蜘蛛抓到 HTML 后立即提取标题、正文、链接,进入快速索引,这条管线不执行 JS;第二条是「渲染管线」,把需要渲染的页面排队交给渲染服务,执行 JS 后再走一遍提取流程,这条管线有延迟和配额。绝大多数依赖客户端渲染的站点,只在第二条管线里被处理,于是就出现了「收录慢、收录少、标题错」的现象。

方案 源码是否含正文 抓取延迟 维护成本 适用
客户端渲染(CSR) 否 数天至数周 低 后台系统、不需收录的页面
服务端渲染(SSR) 是 同静态页 高 内容站、电商详情页
静态导出(SSG) 是 同静态页 中 文档站、博客
动态渲染(Prerender) 对蜘蛛是 低 中 已上线的 SPA 补救
混合:关键内容直出 是 低 中 渐进式改造

判断自己站在哪条管线上的方法很简单:在资源平台的抓取诊断里看「页面源码」,如果源码里搜不到正文关键词,那就只走了渲染管线。

保姆级实操

步骤 1:先确认问题边界。 用 curl 直接请求页面,看返回的 HTML 里有没有正文。再用百度蜘蛛 UA 请求一次,确认不是被分流到了不同版本:

UA='Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)'
curl -sS -A "$UA" 'https://mimangfei.com/article/123' | grep -c '正文关键词'

返回 0 就说明源码里没有正文,必须改造。

步骤 2:能改架构就上 SSR 或 SSG。 新建站点优先选择 Next.js、Nuxt 这类默认支持服务端渲染的框架,或者用 Astro、VitePress 做静态导出。WordPress 站点则相反——它本来就是服务端渲染,不要为了「现代化」把前台改成 SPA,那是自废武功。

步骤 3:已上线的 SPA 用动态渲染补救。 思路是:识别爬虫 UA,把请求转发给预渲染服务,返回一个已经渲染好的 HTML;真人用户继续走原来的 SPA。下面是一个极简的 Node 预渲染服务:

// prerender-server.js —— 依赖:express、puppeteer
const express = require('express');
const puppeteer = require('puppeteer');

const app = express();
const PORT = 3000;
const SITE = 'https://mimangfei.com';
const CACHE = new Map();
const TTL = 30 * 60 * 1000; // 缓存 30 分钟

let browser;

async function render(path) {
  const key = path;
  const hit = CACHE.get(key);
  if (hit && Date.now() - hit.time < TTL) return hit.html;

  if (!browser) browser = await puppeteer.launch({ args: ['--no-sandbox'] });
  const page = await browser.newPage();
  await page.setUserAgent('Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 Chrome/120 Mobile Safari/537.36');
  await page.goto(SITE + path, { waitUntil: 'networkidle0', timeout: 30000 });
  const html = await page.content();
  await page.close();

  CACHE.set(key, { html, time: Date.now() });
  return html;
}

app.get('*', async (req, res) => {
  try {
    const html = await render(req.originalUrl);
    res.status(200).type('html').send(html);
  } catch (err) {
    res.status(502).send('prerender failed');
  }
});

app.listen(PORT, () => console.log(`prerender listening on ${PORT}`));

步骤 4:在 nginx 层按 UA 分流。 只把爬虫导向预渲染服务,同时给渲染结果单独设缓存:

map $http_user_agent $is_bot {
    default                                           0;
    "~*(Baiduspider|Baiduspider-render|Sogou|Bytespider|360Spider)" 1;
}

server {
    listen 443 ssl http2;
    server_name mimangfei.com;

    location @prerender {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Original-URI $request_uri;
        proxy_connect_timeout 5s;
        proxy_read_timeout   30s;
    }

    location / {
        if ($is_bot) { error_page 418 = @prerender; return 418; }
        try_files $uri $uri/ /index.html;
    }
}

步骤 5:补齐 HTML 层的 SEO 要素。 即便走了预渲染,也要保证 <title>、<meta name="description">、canonical、结构化数据都由服务端写入,而不是靠 JS 动态设置。可以用 react-helmet 或框架自带的 head 管理做 SSR 输出。

步骤 6:用 sitemap 与主动提交加速。 渲染管线的配额有限,能走源码管线就别等渲染。把已预渲染的 URL 通过 API 提交,缩短等待。

常见踩坑

  1. 用 robots.txt 把 JS/CSS 全屏蔽了,渲染服务拿不到样式与脚本,渲染出来的是裸 HTML,照样判定低质。
  2. 案例: 某 Vue 项目上线半年只收录了首页,抓取诊断里源码一直是空壳,团队一直以为是内容质量分不够,疯狂加原创文章,直到有人用 curl 看了一眼源码,才发现压根没正文,改 SSR 后两周收录从 1 条涨到 400 多条。
  3. 预渲染服务没做缓存,爬虫一来就现场启 Puppeteer,服务器直接被打爆,反而产生更多 5xx。
  4. 分流规则只写了 Baiduspider,漏了 Baiduspider-render,渲染爬虫走的是普通路径。
  5. 预渲染返回的 HTML 里 canonical 指向了预渲染服务自己的地址。
  6. SPA 路由用 hash 模式(#/article/1),百度不把 hash 后的内容当作独立 URL,内页永远抓不到。
  7. 认为做了预渲染就万事大吉,忽略了首屏时间与稳定性,渲染出来的页面三秒才返回,百度依然给低分。

总结

判断前后端分离站点是否适配百度,只有一个标准:用蜘蛛 UA 请求页面,返回的 HTML 源码里能不能直接搜到正文。能做到就合规,做不到就必须改造。行动清单:curl 验证源码 → 新站直接上 SSR/SSG → 老 SPA 上动态渲染并按 UA 分流 → 服务端写入标题与 canonical → sitemap 与主动提交加速。别指望百度替你执行 JS,把关键内容直出永远是最稳的方案。AJAX 加载内容的处理原则见 百度对 AJAX 加载内容的处理,iframe 与隐藏内容的风险见 百度对 iframe 与隐藏内容的判定。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。