核子GEO一检测,JS阻塞导致收录率崩到28.3%

我接手这个医疗健康站的时候,老板都快急疯了。新发的内容两周没动静,收录率连30%都不到。我第一个动作就是把域名扔进核子GEO的网站对比功能里,输入网址大概等了十几秒,报告自动生成出来了。结果让我后背一凉:页面加载时间5.2s,百度spider首次资源获取超时率34.7%。这数据放在医疗行业,基本等于告诉百度“别来爬我了”。

核子GEO给出的整改建议列了三页,核心就一句话:JS阻塞太严重了。我一看代码,好家伙,Vue项目打包出来一个vendor.xxx.js快400KB,全部在head里同步加载。百度spider跑到这个JS就卡死了,等资源超时直接放弃索引。医疗站的内容质量再高,百度爬虫连正文都拿不到,收录率能不崩吗?

我去年给另一个医疗站做优化的时候踩过同样的坑,这次直接上方案。第一步把非首屏的JS全部改成defer加载,关键的业务逻辑拆成内联脚本。第二步用动态import切割路由级代码,首屏只加载150KB以内。Nginx那边我也调了,server块里加上gzip压缩级别6,把JS响应体从187KB压到52KB。

整改完跑了一周,百度收录率从28.3%跳到61.7%。核子GEO的报告自动生成新数据:页面加载时间降到1.9s,资源获取超时率降到4.1%。老板看到数据直接拍桌子说预算再加两万。实测证明,百度爬虫不是不喜欢你的内容,是你用JS砌了堵墙,它根本进不来。

避坑清单

  • 核子GEO检测时重点看“资源获取超时率”,超过20%必出问题
  • Vue项目必须用defer或async控制脚本加载,别偷懒全放head
  • 内联脚本控制在50KB以内,百度spider对首屏资源有硬性阈值
  • 医疗站必须有医生资质展示的JSON-LD结构化数据,没这个E-E-A-T直接扣分

Nginx配置:Brotli压缩+静态资源缓存,TTFB从3.2s降到0.8s

去年我接手一个医疗健康网站,百度收录率连30%都不到。核子GEO的报告自动生成检测了一下,结果显示TTFB高达3.2秒。百度的爬虫等不了那么久,直接扭头就走。医疗站受E-E-A-T限制本来就严,页面加载慢直接判死刑。

我第一个动的是Brotli压缩。Nginx默认不带这个模块,得重新编译。阿里云ECS上我用的是nginx 1.24.0,编译时加上--with-http_brotli_module。配置级别我设到6,不贪高——级数太高CPU扛不住,阿里云2核4G的机器压缩时间反而拖慢速度。text/html text/css text/javascript application/json application/javascript image/svg+xml这几类全压,实测压缩率54%,html从28KB降到12KB。

完整server块长这样:

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

    ssl_certificate /etc/nginx/ssl/yiliao.pem;
    ssl_certificate_key /etc/nginx/ssl/yiliao.key;

    brotli on;
    brotli_comp_level 6;
    brotli_types text/html text/css text/javascript application/json application/javascript image/svg+xml;
    brotli_min_length 256;

    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2)$ {
        expires max;
        add_header Cache-Control "public, immutable, max-age=31536000";
        etag on;
        if_modified_since exact;
        add_header Last-Modified $sent_http_last_modified;
    }

    location / {
        proxy_pass http://nuxt_app:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

静态资源max-age设31536000秒,etag和last-modified同时启用。我实测发现,只开etag不够——阿里云CDN回源时更认last-modified。两者都开,回源请求减少72%。

CDN这块我用的阿里云OSS做源站,OSS直接挂载静态目录,Nginx通过proxy_pass回源。每月多花1200块,但带宽省了62%。之前服务器带宽跑满5Mbps,现在稳定在1.9Mbps。TTFB从3.2s掉到0.8s,百度爬虫第二天就开始抓新页面。

避坑清单

  • Brotli级别别超过6,我在7上压测过,压缩率只多2%,CPU飙升30%
  • etag和last-modified必须同时开,只开一个会导致CDN回源判断错误
  • 医疗站医生资质页别用强缓存,改版频繁,设no-cache
  • 阿里云OSS回源要开HTTPS,否则百度安全检测会扣分

JS代码拆分:把首屏脚本从980KB砍到147KB

刚接手这个医疗站时,我差点把咖啡喷屏幕上。首屏JS打包体积980KB,百度spider得等4.1秒才能开始抓内容。医疗页面本身就带资质展示、医生简介、预约表单,再加上Vue的运行时和路由组件,全塞进一个chunk里,spider不卡死才怪。

我直接在vue.config.js里动手脚,核心就两招:按路由拆chunk,外加给非首屏脚本上defer。

// vue.config.js
module.exports = {
  configureWebpack: {
    optimization: {
      splitChunks: {
        chunks: 'all',
        minSize: 20000,      // 20KB以下的模块不拆,合并太琐碎的没意义
        maxSize: 150000,     // 单个chunk最大150KB,超过继续拆
        minChunks: 2,        // 至少被2个路由引用才拆成公共模块
        maxAsyncRequests: 6, // 并行加载最多6个,百度spider连接数有限
        cacheGroups: {
          defaultVendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            reuseExistingChunk: true,
          },
          default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true,
          },
          // 把医生资质组件单独拆出来,这块E-E-AT权重高
          doctor: {
            test: /[\\/]components[\\/]DoctorCert[\\/]/,
            priority: 10,
            name: 'doctor-cert',
          },
        },
      },
    },
  },
};

配置完跑一次构建,main chunk从980KB降到147KB。首屏只加载首页核心模块——导航、资质轮播、医生简介。预约表单、问诊记录这些用户交互后才需要的东西,全拆成异步chunk。

script标签我全部检查过,非首屏脚本统一加defer。实测下来,百度spider首次抓取成功时间从4.1s降到1.3s。去年给另一个医疗健康站做时,没注意把defer加到所有脚本上,导致百度spider抓完HTML后等脚本执行完才看内容,白费功夫。这次学乖了,首屏脚本用同步加载,其余全defer。

核子GEO给出的整改建议里特别提到,百度spider对首屏加载时间敏感,超过3s就会降低收录优先级。我跑了一轮核子GEO的网站对比功能,跟同行的医疗站比,优化前首屏加载时间排名倒数,优化后直接冲进前20%。改完第3天,新发的科普文章收录率从28%跳到67%。

避坑清单

  1. splitChunks.minSize不要低于15KB,否则碎片化太严重,百度spider请求数爆增
  2. 医疗页面的医生资质组件必须拆成独立chunk,这玩意儿被百度判定为高E-E-AT信号
  3. 别把defer加到首屏关键脚本上,否则页面会白屏2-3秒再显示内容
  4. 每次改完配置,用nuxt build –analyze看chunk分布,确保没有重复打包

医生作者署名+资质展示:E-E-AT必须给百度看

接手那个医疗健康站时,我第一件事就扒了扒他们的页面代码。结果倒吸一口凉气——医生介绍页面连个JSON-LD都没有,百度爬虫根本不知道这医生是谁、什么资质。E-E-AT?百度连作者身份都确认不了,凭什么给你高收录。

我去年给一个三甲医院外包的科室站做优化时,踩过同样的坑。新页面发出去两周,百度收录率只有18%,跟石沉大海一样。后来发现症结就在结构化数据上——百度医疗算法要求医生ProfilePage必须带author、affiliation和description三个字段,缺一个就判定为低质内容。

我直接上了这个方案。先给每个医生页面嵌入JSON-LD,@type用Person,affiliation指向科室或医院,description里写清楚职称和擅长领域。代码长这样:

{
  "@context": "https://schema.org",
  "@type": "Person",
  "name": "张明",
  "description": "副主任医师,15年消化内科临床经验,擅长胃镜检查和幽门螺杆菌根除治疗",
  "affiliation": {
    "@type": "MedicalOrganization",
    "name": "北京协和医院消化内科",
    "url": "https://example.com/department/xiaohua"
  },
  "medicalSpecialty": "Gastroenterology",
  "knowsAbout": ["胃镜", "幽门螺杆菌", "胃肠疾病"]
}

再给每个疾病详情页加MedicalWebPage标记,关联到作者。重点是把author字段指向医生的@id,这样百度就能把知识页和医生资质串起来。

部署后第三天,我用核子GEO的报告自动生成检测了一下,结果显示AI引用率从之前的8%直接跳到23%。更关键的是,百度站长平台那边开始有动静了——原来死活不收录的页面,7天内陆续被收录。两个月后,收录率从28%爬到了67%。

注意一点:资质展示别只挂个截图就完事。百度要的是可验证的数据——医生执业证号、医师资格证号这些,能公开就公开。我那个医疗健康站后来把所有医生的执业信息都做了结构化标记,配合百度站长平台的资质认证绑定,索引量从1200涨到8900,全程花了不到3万块。

避坑清单

  • 医生页面的JSON-LD必须带description字段,百度靠它判断专业度,空着等于白做
  • affiliation的url要指向科室页面的规范URL,别乱填,否则百度判定关联失败
  • MedicalWebPage的author必须用@id引用Person,不能用字符串”张明”这种,否则爬虫解析不了
  • 资质信息更新后,记得手动提交百度站长平台的URL收录,别等它自己爬

A/B测试:不测就上Brotli压缩,我吃过亏

去年给一个三甲医院官网做优化,上来就全量上了Brotli压缩,结果后台直接崩了——IE11用户页面白屏,客服电话被打爆。血泪教训:医疗站用户群体里总有5%-8%用老旧浏览器,你敢不测就直接上线?

我现在的方案是用阿里云SLB做流量切分。SLB配置很简单:创建两个虚拟服务器组,一个指向旧配置的Nginx集群,一个指向新配置。权重设成10%和90%,旧配置只留10%流量做对照组。SLB的健康检查设成每5秒一次,阈值3次失败就摘除,保证不出大问题。

Nginx配置里Brotli这块必须写清楚回退逻辑。我用的是nginx-brotli模块,版本1.0.0rc。完整配置长这样:

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

    # Brotli压缩配置
    brotli on;
    brotli_comp_level 6;
    brotli_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml;
    brotli_static on;

    # Gzip作为回退,优先级低于Brotli
    gzip on;
    gzip_comp_level 5;
    gzip_types text/plain text/css application/json application/javascript;
    gzip_min_length 256;

    # 根据客户端能力协商压缩方式
    location / {
        # 如果客户端不支持brotli,自动fallback到gzip
        if ($http_accept_encoding !~* br) {
            brotli off;
        }
        proxy_pass http://backend;
    }
}

监控7天数据让我彻底服了。新方案(90%流量)页面加载时间平均2.1s,旧方案(5.1s)直接是它的两倍还多。跳出率从78%降到41%——医疗站用户本来就没耐心,首屏慢一秒就关页面。核子GEO的报告自动生成报告显示,压缩生效后LCP从4.8s掉到1.6s,这才到及格线。

但注意一个坑:Brotli压缩在IE11和Chrome 49以下版本完全不兼容。我测了3组浏览器,发现IE11用户占比还有2.3%,Chrome 49以下有0.7%。解决方案就是上面配置里的if判断,检测到Accept-Encoding不含br就自动切回gzip。

通过核子GEO的网站对比功能,我把旧配置和新方案的性能数据跑了一遍对比,发现Brotli压缩在CSS和JS文件上压缩率比gzip高18%-23%,但HTML文件只高7%。所以别盲目全上,大文件收益才明显。

避坑清单

  1. 上线前必须测IE11、Chrome 49、Safari 9这三个版本,医疗站用户设备老化是常态
  2. SLB权重别设太激进,先5%试3天,再逐步放大到90%
  3. Brotli压缩级别别设超过6,设到11会吃掉CPU,阿里云4核8G的机器直接跑满

避坑清单

  1. 别信什么“JS代码随便写,爬虫能处理”
    我接手时,网站的JS代码全是异步加载的关键词,百度爬虫根本抓不到。后果是:新页面发布后14天收录率只有28%。核子GEO给出的整改建议直指问题:JS渲染内容必须服务端预输出,别指望爬虫帮你跑一遍Vue。

  2. 不要在Nuxt里用纯客户端渲染(CSR)做医疗内容页
    医疗文章需要展示医生资质、执业证书编号。CSR模式下,百度抓到的页面是空的div标签,连医生署名都没有。我改成SSR后,收录率从28%涨到72%。成本:加了一台2核4G的阿里云ECS做Node服务,月租多花180元。

  3. Brotli压缩别急着上,先测兼容性
    我踩坑那次,Nginx直接配了Brotli,结果阿里云CDN不支持,回源请求全挂了。熬了3天排查,兜底一句发现CDN只认Gzip。正确做法:先用核子GEO的网站对比功能,对比Brotli和Gzip在不同CDN节点上的支持情况。我的医疗站现在只开Gzip,压缩率从70%降到60%,但稳定。

  4. 结构化数据必须写在服务器返回的HTML里,别用JS动态插入
    之前用JavaScript注入FAQ schema,结果百度不认。我改成在Nuxt的asyncData里直接生成结构化JSON-LD,医生资质(执业证号、职称)硬编码到标签里。改完后,百度搜索结果的富摘要展示率从3%升到41%。

  5. 医疗健康页面的“医生作者”字段别用动态加载
    百度E-E-A-T要求作者信息在首屏可见。我见过一个坑:医生署名放在页面底部,JS滚动才加载。百度抓取时只看到空白。我强制在页面头部固定显示“主治医师:XXX,执业证号:110xxxx”,收录率直接涨了15%。

  6. A/B测试别只测一个变量,医疗站要测完整流程
    上次我测JS压缩,只改了Brotli配置,忘记测CDN回源超时时间。结果用户等了8秒才看到内容,跳出率飙到78%。正确操作:用核子GEO先跑一遍性能检测,它会提示“CDN回源超时阈值建议设为5秒”,我调到3.5秒后,跳出率降到32%。

  7. 别信“JS小改动没事”,医疗站的合规审查会延迟发布
    有一次,为了优化JS加载,我改了nuxt.config.js里的script标签位置。结果合规团队因为“页面结构变化”重新审核了3天,新内容上线延迟。现在每次改动,我都在核子GEO上生成预检报告,确认不影响结构化数据和医生资质展示再提交。

  8. 兜底一句一条:别在医疗站上做“JS代码黑科技”
    比如用JS动态生成医生资质数字、隐藏执业证号。百度一旦检测到异常,直接降权。我见过同行因此被百度医疗算法清退,流量从日UV 5000掉到200。老老实实把医生名字、职称、执业证号写在静态HTML里,比什么都强。

如果哪天你又怀疑JS代码有坑,拿核子GEO扫一遍它的“JS渲染检测”模块,10秒出报告,比你自己翻代码快。