本文约 3,517 字,预计阅读 9 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:无限滚动很酷,但百度只看到第一屏的东西
产品同学喜欢无限滚动、异步筛选、点击展开,用户体验确实好。问题在于这些交互把内容从 HTML 里搬到了接口里。百度蜘蛛访问一个 URL,拿到的是 HTML 源码,它不会去点「加载更多」,也不会等你的 fetch 请求回来再拼接 DOM。于是页面上写着「共 328 条评论」,源码里一条评论都没有;筛选器里有十几个标签页,源码里只有默认那一个。站长看到的是收录量上不去、页面被判定内容单薄,工程师看到的是接口一切正常,两边对问题的理解完全不在一个层面。解决办法不是砍掉交互,而是分清主次:参与排名的内容服务端直出,纯交互增强的部分异步加载。
原理分析:直出与异步的分界线在哪里
判断标准很朴素:这段内容是否希望能被单独搜索到。正文、标题、商品参数、评论主体、列表第一页——这些都参与排名,必须直出;点赞数、实时库存、个性化推荐、第二页以后的内容——这些不影响主页面的索引价值,可以异步。这条线画清楚后,大部分争议就消失了。
| 内容类型 | 建议方式 | 理由 |
|---|---|---|
| 文章正文、标题 | 直出 | 核心索引内容 |
| 列表第一页 | 直出 | 入口页要参与排名 |
| 列表第二页及以后 | 异步 + 独立 URL | 通过分页 URL 让百度抓 |
| 评论主体 | 直出 | 长尾词来源 |
| 点赞、阅读数 | 异步 | 数值频繁变动,直出易造成快照不一致 |
| 个性化推荐 | 异步 | 与索引无关 |
| 筛选结果 | 异步 + 参数 URL 规范化 | 避免产生海量低质参数页 |
WordPress 里最常见的两个异步入口是 admin-ajax.php 和 REST API(/wp-json/wp/v2/posts)。这两个地址本身不需要被收录,也不应该在 sitemap 里出现;需要被收录的是承载它们的页面,而页面必须在首次响应里就带上正文。
保姆级实操
步骤 1:审计现有页面的异步依赖。 关闭浏览器 JS(或用 curl 直接请求),逐个看关键页面还剩多少内容。剩下的部分就是百度能看到的部分。
步骤 2:把列表首屏改成服务端查询直出。 下面是一个文章列表的首屏直出写法,第一页由 PHP 直接渲染,后续页交给前端异步拉取:
// 主题模板中:首屏 10 条由 WP_Query 直出,剩下的交给 JS
$args = [
'post_type' => 'post',
'posts_per_page' => 10,
'paged' => 1,
'ignore_sticky_posts' => true,
'no_found_rows' => true, // 首屏不需要总条数,省一次 SQL
];
$q = new WP_Query( $args );
echo '<ul class="post-list" data-page="1">';
while ( $q->have_posts() ) {
$q->the_post();
printf(
'<li><a href="%s">%s</a><span>%s</span></li>',
esc_url( get_permalink() ),
esc_html( get_the_title() ),
esc_html( get_the_date() )
);
}
echo '</ul>';
wp_reset_postdata();
步骤 3:异步部分只补增强内容,不补关键内容。 前端拉取第二页时,同时把 URL 更新到带分页参数的形式,并为分页保留真实可访问的 URL:
const list = document.querySelector('.post-list');
let page = Number(list.dataset.page || 1);
let loading = false;
document.querySelector('#load-more')?.addEventListener('click', async () => {
if (loading) return;
loading = true;
page += 1;
const res = await fetch(`/wp-json/wp/v2/posts?page=${page}&per_page=10`);
const posts = await res.json();
posts.forEach((p) => {
const li = document.createElement('li');
li.innerHTML = `<a href="${p.link}">${p.title.rendered}</a>`;
list.appendChild(li);
});
// 同步地址栏,并保证该地址可被直接访问与抓取
history.replaceState(null, '', `/page/${page}/`);
loading = false;
});
步骤 4:给每个异步内容块准备一个静态兜底 URL。 无限滚动的列表必须有 /page/2/、/page/3/ 这样的真实分页地址,且这些地址返回的是完整 HTML,不是 JS 拼出来的。可以用 paginate_links() 生成分页链接,再用 CSS 在移动端隐藏分页器——注意这里是视觉隐藏但 HTML 存在,属于合规做法。
步骤 5:确认异步接口不在抓取路径上。 在 robots.txt 里屏蔽 /wp-json/ 与 /wp-admin/admin-ajax.php,避免百度把接口地址当页面抓:
add_filter( 'robots_txt', function ( $output ) {
$lines = [
'User-agent: *',
'Disallow: /wp-admin/admin-ajax.php',
'Disallow: /wp-json/',
'Allow: /wp-json/wp/v2/posts', // 若需对外提供数据则放行
'',
];
return implode( PHP_EOL, $lines );
}, 99 );
步骤 6:验证。 用抓取诊断看源码,确认正文、首屏列表、评论主体都在源码里,点赞数这类数字缺失是正常的。
常见踩坑
- 把评论做成异步加载,页面被判定为「内容单薄」,长尾词全丢。
- 案例: 某站把商品详情的规格参数改成接口异步获取,改完一个月内详情页收录掉七成,且快照里规格区域一直空白,排查时开发坚持「接口返回是好的」,最后用 curl 对比才确认源码里确实没有参数,改回直出后六周恢复。
- 无限滚动没做分页 URL,第二页以后的内容永远进不了索引。
robots.txt屏蔽了/wp-json/却忘记主题依赖它渲染前台,结果前台直接白屏。- 异步内容用 JS 动态写入
<title>,抓取时标题为空。 - 分页 URL 用
#page=2的 hash 形式,百度不识别。 - 为了「直出」把整页改成服务端渲染,首屏时间从 0.8 秒涨到 3.5 秒,反而触发闪电算法的负面评价——直出不等于放弃性能。
总结
判断一个模块该不该异步,问一句:「这段内容我希望用户在百度搜到吗?」答案是肯定的,就必须出现在首次 HTML 响应里。行动清单:关 JS 审计页面 → 列表首屏与正文改服务端直出 → 交互增强部分保留异步 → 为异步列表补真实分页 URL → robots 屏蔽接口路径 → 抓取诊断验证源码。渐进增强不是妥协,而是同时满足体验与收录的正确姿势。相关做法可结合 百度对 JS 渲染内容的抓取现状 一起评估,参数化分页的处理见 站内搜索页与参数 URL 处理。
