网站 SEO 功能怎么测:从手工检查到自动化脚本的实战思路

很多人看到"网站 SEO 优化"会觉得这是运营的事情,测试只要打开首页、看一眼 robots.txtsitemap.xml 就结束了。

实际不是这样。

搜索引擎访问网站时,既要能找到页面,也要能读到正文;不该公开的后台、账号、隐私页面又不能被放出来。测试的目标,就是把这几件事同时确认清楚。

本文用一个已经脱敏的网站改造过程,讲清楚三个问题:

  1. SEO 页面到底在测什么;
  2. 在浏览器里怎么手工验证;
  3. 哪些手工步骤值得写成 Python + Playwright 脚本。

文中所有项目、域名、人员和工单信息都使用通用名称。

一、先用大白话理解三个文件

1. robots.txt:给爬虫看的"访问说明书"

搜索引擎的爬虫访问网站前,通常会先读:

text 复制代码
https://你的域名/robots.txt

它告诉爬虫:哪些页面可以看,哪些页面不要看。

比如新闻、产品、供求详情这类公开页面,应该允许抓取;后台、账号中心、会员内容、接口地址,应该禁止抓取。

2. sitemap.xml:给爬虫看的"页面目录"

站点地图相当于把希望搜索引擎收录的页面地址列成清单:

text 复制代码
https://你的域名/sitemap.xml

它不是"能打开就行"。如果里面混入登录页、后台地址、旧域名,或者页面点进去全是 404,搜索引擎照样会抓错。

3. User-Agent:浏览器报给服务器的"我是哪个访问者"

普通浏览器会带类似 Chrome 的 User-Agent;搜索引擎爬虫会带 Googlebot、Baiduspider 之类的标识。

有些网站会根据这个标识返回不同内容,所以要验证:

  • 爬虫访问公开详情页时,能看到标题和正文;
  • 伪装成爬虫后,也不能绕过后台或账号权限。

#mermaid-svg-UhzYzbN1dFNBhwRC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UhzYzbN1dFNBhwRC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UhzYzbN1dFNBhwRC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UhzYzbN1dFNBhwRC .error-icon{fill:#552222;}#mermaid-svg-UhzYzbN1dFNBhwRC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UhzYzbN1dFNBhwRC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UhzYzbN1dFNBhwRC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UhzYzbN1dFNBhwRC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UhzYzbN1dFNBhwRC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UhzYzbN1dFNBhwRC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UhzYzbN1dFNBhwRC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UhzYzbN1dFNBhwRC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UhzYzbN1dFNBhwRC .marker.cross{stroke:#333333;}#mermaid-svg-UhzYzbN1dFNBhwRC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UhzYzbN1dFNBhwRC p{margin:0;}#mermaid-svg-UhzYzbN1dFNBhwRC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UhzYzbN1dFNBhwRC .cluster-label text{fill:#333;}#mermaid-svg-UhzYzbN1dFNBhwRC .cluster-label span{color:#333;}#mermaid-svg-UhzYzbN1dFNBhwRC .cluster-label span p{background-color:transparent;}#mermaid-svg-UhzYzbN1dFNBhwRC .label text,#mermaid-svg-UhzYzbN1dFNBhwRC span{fill:#333;color:#333;}#mermaid-svg-UhzYzbN1dFNBhwRC .node rect,#mermaid-svg-UhzYzbN1dFNBhwRC .node circle,#mermaid-svg-UhzYzbN1dFNBhwRC .node ellipse,#mermaid-svg-UhzYzbN1dFNBhwRC .node polygon,#mermaid-svg-UhzYzbN1dFNBhwRC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UhzYzbN1dFNBhwRC .rough-node .label text,#mermaid-svg-UhzYzbN1dFNBhwRC .node .label text,#mermaid-svg-UhzYzbN1dFNBhwRC .image-shape .label,#mermaid-svg-UhzYzbN1dFNBhwRC .icon-shape .label{text-anchor:middle;}#mermaid-svg-UhzYzbN1dFNBhwRC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UhzYzbN1dFNBhwRC .rough-node .label,#mermaid-svg-UhzYzbN1dFNBhwRC .node .label,#mermaid-svg-UhzYzbN1dFNBhwRC .image-shape .label,#mermaid-svg-UhzYzbN1dFNBhwRC .icon-shape .label{text-align:center;}#mermaid-svg-UhzYzbN1dFNBhwRC .node.clickable{cursor:pointer;}#mermaid-svg-UhzYzbN1dFNBhwRC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UhzYzbN1dFNBhwRC .arrowheadPath{fill:#333333;}#mermaid-svg-UhzYzbN1dFNBhwRC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UhzYzbN1dFNBhwRC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UhzYzbN1dFNBhwRC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UhzYzbN1dFNBhwRC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UhzYzbN1dFNBhwRC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UhzYzbN1dFNBhwRC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UhzYzbN1dFNBhwRC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UhzYzbN1dFNBhwRC .cluster text{fill:#333;}#mermaid-svg-UhzYzbN1dFNBhwRC .cluster span{color:#333;}#mermaid-svg-UhzYzbN1dFNBhwRC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UhzYzbN1dFNBhwRC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UhzYzbN1dFNBhwRC rect.text{fill:none;stroke-width:0;}#mermaid-svg-UhzYzbN1dFNBhwRC .icon-shape,#mermaid-svg-UhzYzbN1dFNBhwRC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UhzYzbN1dFNBhwRC .icon-shape p,#mermaid-svg-UhzYzbN1dFNBhwRC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UhzYzbN1dFNBhwRC .icon-shape .label rect,#mermaid-svg-UhzYzbN1dFNBhwRC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UhzYzbN1dFNBhwRC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UhzYzbN1dFNBhwRC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UhzYzbN1dFNBhwRC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 搜索引擎爬虫
先读 robots.txt
从 sitemap.xml 获取页面地址
访问公开详情页
读取标题和正文

推荐一个测试插件:浏览器身份伪装:UserAgent-Switcher完全指南

二、为什么"接口返回 200"还不能算通过

HTTP 200 的意思只是"服务器给了响应"。它并不代表页面真的正确。

举一个常见例子:sitemap.xml 返回 200,但里面的 URL 仍然是旧域名。搜索引擎顺着旧域名抓取,可能跳转、404,甚至进入错误站点。

所以验证 Sitemap 时,我通常按下面顺序检查:
#mermaid-svg-9m55aIJSw4zzEN9a{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9m55aIJSw4zzEN9a .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9m55aIJSw4zzEN9a .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9m55aIJSw4zzEN9a .error-icon{fill:#552222;}#mermaid-svg-9m55aIJSw4zzEN9a .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9m55aIJSw4zzEN9a .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9m55aIJSw4zzEN9a .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9m55aIJSw4zzEN9a .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9m55aIJSw4zzEN9a .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9m55aIJSw4zzEN9a .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9m55aIJSw4zzEN9a .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9m55aIJSw4zzEN9a .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9m55aIJSw4zzEN9a .marker.cross{stroke:#333333;}#mermaid-svg-9m55aIJSw4zzEN9a svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9m55aIJSw4zzEN9a p{margin:0;}#mermaid-svg-9m55aIJSw4zzEN9a .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9m55aIJSw4zzEN9a .cluster-label text{fill:#333;}#mermaid-svg-9m55aIJSw4zzEN9a .cluster-label span{color:#333;}#mermaid-svg-9m55aIJSw4zzEN9a .cluster-label span p{background-color:transparent;}#mermaid-svg-9m55aIJSw4zzEN9a .label text,#mermaid-svg-9m55aIJSw4zzEN9a span{fill:#333;color:#333;}#mermaid-svg-9m55aIJSw4zzEN9a .node rect,#mermaid-svg-9m55aIJSw4zzEN9a .node circle,#mermaid-svg-9m55aIJSw4zzEN9a .node ellipse,#mermaid-svg-9m55aIJSw4zzEN9a .node polygon,#mermaid-svg-9m55aIJSw4zzEN9a .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9m55aIJSw4zzEN9a .rough-node .label text,#mermaid-svg-9m55aIJSw4zzEN9a .node .label text,#mermaid-svg-9m55aIJSw4zzEN9a .image-shape .label,#mermaid-svg-9m55aIJSw4zzEN9a .icon-shape .label{text-anchor:middle;}#mermaid-svg-9m55aIJSw4zzEN9a .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9m55aIJSw4zzEN9a .rough-node .label,#mermaid-svg-9m55aIJSw4zzEN9a .node .label,#mermaid-svg-9m55aIJSw4zzEN9a .image-shape .label,#mermaid-svg-9m55aIJSw4zzEN9a .icon-shape .label{text-align:center;}#mermaid-svg-9m55aIJSw4zzEN9a .node.clickable{cursor:pointer;}#mermaid-svg-9m55aIJSw4zzEN9a .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9m55aIJSw4zzEN9a .arrowheadPath{fill:#333333;}#mermaid-svg-9m55aIJSw4zzEN9a .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9m55aIJSw4zzEN9a .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9m55aIJSw4zzEN9a .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9m55aIJSw4zzEN9a .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9m55aIJSw4zzEN9a .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9m55aIJSw4zzEN9a .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9m55aIJSw4zzEN9a .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9m55aIJSw4zzEN9a .cluster text{fill:#333;}#mermaid-svg-9m55aIJSw4zzEN9a .cluster span{color:#333;}#mermaid-svg-9m55aIJSw4zzEN9a div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9m55aIJSw4zzEN9a .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9m55aIJSw4zzEN9a rect.text{fill:none;stroke-width:0;}#mermaid-svg-9m55aIJSw4zzEN9a .icon-shape,#mermaid-svg-9m55aIJSw4zzEN9a .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9m55aIJSw4zzEN9a .icon-shape p,#mermaid-svg-9m55aIJSw4zzEN9a .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9m55aIJSw4zzEN9a .icon-shape .label rect,#mermaid-svg-9m55aIJSw4zzEN9a .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9m55aIJSw4zzEN9a .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9m55aIJSw4zzEN9a .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9m55aIJSw4zzEN9a :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否







打开 sitemap.xml
返回 200?
入口问题
读取每个 URL
域名正确?
域名配置问题
含登录/后台/API 地址?
收录范围问题
抽几条 URL 实际访问
能打开且不跳登录?
页面可访问性问题
该项通过

浏览器手工怎么测

  1. 打开 https://测试域名/sitemap.xml
  2. 搜索是否有旧域名、/login/admin/api/
  3. 从里面随机复制几条资讯、产品、标签页地址,在新标签页打开;
  4. 确认地址没有跳到登录页,也没有 404;
  5. 如果有相同 URL,先问产品:它是误重复,还是同一链接在不同 Tab 下复用。不要看到重复就直接提 Bug。

这里还有一个脚本经验:判断旧域名不能用字符串包含。

python 复制代码
# 不推荐:测试域名里可能恰好也包含 old.example.com 的部分字符
if "old.example.com" in url:
    print("旧域名")

应当解析 URL,再比较 hostname:

python 复制代码
from urllib.parse import urlparse

host = urlparse(url).hostname
assert host != "old.example.com"

这样不会把"名字相似的测试域名"误判成旧域名。

三、爬虫页面怎么测:不要只看 robots.txt

假设 robots.txt 写了"允许抓取资讯详情页",还要继续问:爬虫打开这个详情页后,看到的是正文,还是一个空壳页面?

完整验证分三步:

检查层次 要确认什么 失败说明什么
robots 规则 公开页允许,后台页禁止 规则配置错了
HTTP 请求 爬虫 UA 访问公开页返回 200、不跳登录 服务端拦截或跳转异常
浏览器渲染 标题、摘要、正文真正显示出来 前端异步渲染或缓存有问题

手工验证方法

安装 User-Agent Switcher 之类的浏览器扩展,将 UA 切换为 GooglebotBaiduspider,然后依次打开:

  • 一条资讯详情;
  • 一条产品/供求详情;
  • 一个应受保护的账号或会员页面。

公开详情页应能看到标题、正文;受保护页面应该跳登录、提示无权限,或返回拒绝访问。

四、图片懒加载到底怎么判断对不对

loading="lazy" 的作用很简单:浏览器先不加载暂时看不到的图片,等用户滚动到附近再加载,可以减少首页第一次打开时的流量。

但不能所有图片都加它:

  • 首屏 Banner、首屏文章主图:用户一打开就要看见,不应该懒加载;
  • 首页很靠下的资讯卡片图:应该懒加载;
  • 图标、背景图、隐藏图片:要看实际产品规则,不要简单套用。

#mermaid-svg-4whHQbp9dXeKS1sX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4whHQbp9dXeKS1sX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4whHQbp9dXeKS1sX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4whHQbp9dXeKS1sX .error-icon{fill:#552222;}#mermaid-svg-4whHQbp9dXeKS1sX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4whHQbp9dXeKS1sX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4whHQbp9dXeKS1sX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4whHQbp9dXeKS1sX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4whHQbp9dXeKS1sX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4whHQbp9dXeKS1sX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4whHQbp9dXeKS1sX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4whHQbp9dXeKS1sX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4whHQbp9dXeKS1sX .marker.cross{stroke:#333333;}#mermaid-svg-4whHQbp9dXeKS1sX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4whHQbp9dXeKS1sX p{margin:0;}#mermaid-svg-4whHQbp9dXeKS1sX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4whHQbp9dXeKS1sX .cluster-label text{fill:#333;}#mermaid-svg-4whHQbp9dXeKS1sX .cluster-label span{color:#333;}#mermaid-svg-4whHQbp9dXeKS1sX .cluster-label span p{background-color:transparent;}#mermaid-svg-4whHQbp9dXeKS1sX .label text,#mermaid-svg-4whHQbp9dXeKS1sX span{fill:#333;color:#333;}#mermaid-svg-4whHQbp9dXeKS1sX .node rect,#mermaid-svg-4whHQbp9dXeKS1sX .node circle,#mermaid-svg-4whHQbp9dXeKS1sX .node ellipse,#mermaid-svg-4whHQbp9dXeKS1sX .node polygon,#mermaid-svg-4whHQbp9dXeKS1sX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4whHQbp9dXeKS1sX .rough-node .label text,#mermaid-svg-4whHQbp9dXeKS1sX .node .label text,#mermaid-svg-4whHQbp9dXeKS1sX .image-shape .label,#mermaid-svg-4whHQbp9dXeKS1sX .icon-shape .label{text-anchor:middle;}#mermaid-svg-4whHQbp9dXeKS1sX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4whHQbp9dXeKS1sX .rough-node .label,#mermaid-svg-4whHQbp9dXeKS1sX .node .label,#mermaid-svg-4whHQbp9dXeKS1sX .image-shape .label,#mermaid-svg-4whHQbp9dXeKS1sX .icon-shape .label{text-align:center;}#mermaid-svg-4whHQbp9dXeKS1sX .node.clickable{cursor:pointer;}#mermaid-svg-4whHQbp9dXeKS1sX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4whHQbp9dXeKS1sX .arrowheadPath{fill:#333333;}#mermaid-svg-4whHQbp9dXeKS1sX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4whHQbp9dXeKS1sX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4whHQbp9dXeKS1sX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4whHQbp9dXeKS1sX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4whHQbp9dXeKS1sX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4whHQbp9dXeKS1sX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4whHQbp9dXeKS1sX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4whHQbp9dXeKS1sX .cluster text{fill:#333;}#mermaid-svg-4whHQbp9dXeKS1sX .cluster span{color:#333;}#mermaid-svg-4whHQbp9dXeKS1sX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4whHQbp9dXeKS1sX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4whHQbp9dXeKS1sX rect.text{fill:none;stroke-width:0;}#mermaid-svg-4whHQbp9dXeKS1sX .icon-shape,#mermaid-svg-4whHQbp9dXeKS1sX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4whHQbp9dXeKS1sX .icon-shape p,#mermaid-svg-4whHQbp9dXeKS1sX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4whHQbp9dXeKS1sX .icon-shape .label rect,#mermaid-svg-4whHQbp9dXeKS1sX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4whHQbp9dXeKS1sX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4whHQbp9dXeKS1sX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4whHQbp9dXeKS1sX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 能看见
看不见
找到 img 标签
打开首页时能看见?
关键图片:不要 lazy
内容图片:建议 lazy

手工检查方法

  1. 打开首页,不要滚动;
  2. 按 F12,进入 Elements;
  3. 找首屏 Banner 和首屏文章图的 <img>
  4. 首屏图片不应有 loading="lazy"
  5. 再找到首屏以下的资讯列表图,确认它们有 loading="lazy"

遇到问题时,最好拆成两个 Bug:

  • 首屏图片错误使用懒加载:影响用户第一眼看到内容的速度;
  • 首屏以下图片没有懒加载:影响首页首次加载的资源量。

不要把两个问题混在一个 Bug 里,开发修复和回归都会更清楚。

五、脚本不是凭空写出来的:从手工步骤翻译成代码

脚本的可视化页面:

我写自动化时,通常按下面的路线走:
#mermaid-svg-NkkwzjWYNFNXeMIZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NkkwzjWYNFNXeMIZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NkkwzjWYNFNXeMIZ .error-icon{fill:#552222;}#mermaid-svg-NkkwzjWYNFNXeMIZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NkkwzjWYNFNXeMIZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NkkwzjWYNFNXeMIZ .marker.cross{stroke:#333333;}#mermaid-svg-NkkwzjWYNFNXeMIZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NkkwzjWYNFNXeMIZ p{margin:0;}#mermaid-svg-NkkwzjWYNFNXeMIZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NkkwzjWYNFNXeMIZ .cluster-label text{fill:#333;}#mermaid-svg-NkkwzjWYNFNXeMIZ .cluster-label span{color:#333;}#mermaid-svg-NkkwzjWYNFNXeMIZ .cluster-label span p{background-color:transparent;}#mermaid-svg-NkkwzjWYNFNXeMIZ .label text,#mermaid-svg-NkkwzjWYNFNXeMIZ span{fill:#333;color:#333;}#mermaid-svg-NkkwzjWYNFNXeMIZ .node rect,#mermaid-svg-NkkwzjWYNFNXeMIZ .node circle,#mermaid-svg-NkkwzjWYNFNXeMIZ .node ellipse,#mermaid-svg-NkkwzjWYNFNXeMIZ .node polygon,#mermaid-svg-NkkwzjWYNFNXeMIZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NkkwzjWYNFNXeMIZ .rough-node .label text,#mermaid-svg-NkkwzjWYNFNXeMIZ .node .label text,#mermaid-svg-NkkwzjWYNFNXeMIZ .image-shape .label,#mermaid-svg-NkkwzjWYNFNXeMIZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-NkkwzjWYNFNXeMIZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NkkwzjWYNFNXeMIZ .rough-node .label,#mermaid-svg-NkkwzjWYNFNXeMIZ .node .label,#mermaid-svg-NkkwzjWYNFNXeMIZ .image-shape .label,#mermaid-svg-NkkwzjWYNFNXeMIZ .icon-shape .label{text-align:center;}#mermaid-svg-NkkwzjWYNFNXeMIZ .node.clickable{cursor:pointer;}#mermaid-svg-NkkwzjWYNFNXeMIZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NkkwzjWYNFNXeMIZ .arrowheadPath{fill:#333333;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NkkwzjWYNFNXeMIZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NkkwzjWYNFNXeMIZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NkkwzjWYNFNXeMIZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NkkwzjWYNFNXeMIZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NkkwzjWYNFNXeMIZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NkkwzjWYNFNXeMIZ .cluster text{fill:#333;}#mermaid-svg-NkkwzjWYNFNXeMIZ .cluster span{color:#333;}#mermaid-svg-NkkwzjWYNFNXeMIZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NkkwzjWYNFNXeMIZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NkkwzjWYNFNXeMIZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-NkkwzjWYNFNXeMIZ .icon-shape,#mermaid-svg-NkkwzjWYNFNXeMIZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NkkwzjWYNFNXeMIZ .icon-shape p,#mermaid-svg-NkkwzjWYNFNXeMIZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NkkwzjWYNFNXeMIZ .icon-shape .label rect,#mermaid-svg-NkkwzjWYNFNXeMIZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NkkwzjWYNFNXeMIZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NkkwzjWYNFNXeMIZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NkkwzjWYNFNXeMIZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 先手工操作一次
确定稳定入口
写清楚通过标准
配置测试数据
编写脚本
失败时输出可定位证据

例如,手工规则是:"用爬虫身份打开公开详情页,应该看到标题和正文。"翻译成脚本,就是:

python 复制代码
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context(user_agent="Googlebot")
    page = context.new_page()

    response = page.goto(detail_url, wait_until="networkidle")
    assert response.status == 200
    assert "/login" not in page.url

    text = page.locator("body").inner_text()
    assert "文章标题中的关键字" in text
    assert "正文中的关键字" in text

其中:

  • user_agent="Googlebot":模拟爬虫;
  • wait_until="networkidle":等待页面接口基本加载完成;
  • response.status == 200:页面入口正常;
  • "/login" not in page.url:没有被跳去登录页;
  • 正文关键字断言:确认不是"200 但空页面"。

图片懒加载脚本怎么写

浏览器脚本不应该只看 HTML 字符串,而要看图片在当前视口的位置:

python 复制代码
def get_visible_images(page):
    return page.evaluate("""
      () => Array.from(document.images).map(img => {
        const rect = img.getBoundingClientRect();
        return {
          src: img.currentSrc || img.src,
          loading: img.loading,
          top: rect.top,
          bottom: rect.bottom,
          visible: rect.width > 0 && rect.height > 0
        };
      }).filter(item => item.visible)
    """)

images = get_visible_images(page)
first_screen = [x for x in images if x["top"] < 900 and x["bottom"] > 0]
wrong_images = [x for x in first_screen if x["loading"] == "lazy"]
assert not wrong_images, wrong_images

它的原理是:浏览器计算每张图片距离视口顶部的位置。图片的上边在 900 像素以内、下边又没有离开页面上方,就认为它处于首屏。

失败时把图片 URL 一起打印出来,开发一眼就能找到对应组件。

六、还有三类很适合补进脚本的场景

1. Sitemap 抽样访问

不必每次访问几千条 URL,可以均匀抽取十几条:资讯、详情、标签、频道页都覆盖到。

python 复制代码
samples = sitemap_urls[::max(1, len(sitemap_urls) // 12)][:12]
for url in samples:
    response = session.get(url, allow_redirects=False)
    assert response.status_code == 200
    assert "/login" not in response.headers.get("Location", "")

2. 首页导航和搜索

先在配置文件中写明可访问频道和测试关键词,不要把真实数据硬编码在脚本里:

json 复制代码
{
  "navigation_paths": ["/news", "/product", "/company"],
  "search": {"keyword": "示例关键词", "result_path": "/search"}
}

这样换测试环境、预发环境时,只改配置,不改测试代码。

3. 图片加载失败后的页面可用性

可以让 Playwright 故意拦截一张图片,再判断标题和正文是否还在:

python 复制代码
blocked = []

def block_one_image(route):
    if route.request.resource_type == "image" and not blocked:
        blocked.append(route.request.url)
        route.abort()
    else:
        route.continue_()

context.route("**/*", block_one_image)
page.goto(base_url, wait_until="networkidle")
assert len(page.locator("body").inner_text().strip()) > 100

注意:只拦截图片请求。若拦截所有请求,首页 HTML 文档也会被误拦截,得到的是测试脚本自己制造的失败。

七、测试计划怎么标状态才不乱

一个测试项不只有"通过"和"失败"。建议至少分四种:

状态 什么时候使用
通过 已执行,有证据,结果符合预期
失败 已执行,有证据,结果不符合预期
延期 缺固定页面、敏感字段、判定标准,暂时无法可靠执行
待执行 已准备好,但本轮尚未开始

例如"爬虫不能看会员正文"这条用例,如果没有会员页面地址和敏感字段清单,不能随便判通过或失败,应标延期,并写清楚:需要开发提供一个固定页面和应隐藏字段。

当 Bug 修复后,必须按同一条脚本、同一视口、同一 UA 再跑一次:
#mermaid-svg-SzY930dnJazKd86c{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SzY930dnJazKd86c .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SzY930dnJazKd86c .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SzY930dnJazKd86c .error-icon{fill:#552222;}#mermaid-svg-SzY930dnJazKd86c .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SzY930dnJazKd86c .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SzY930dnJazKd86c .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SzY930dnJazKd86c .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SzY930dnJazKd86c .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SzY930dnJazKd86c .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SzY930dnJazKd86c .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SzY930dnJazKd86c .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SzY930dnJazKd86c .marker.cross{stroke:#333333;}#mermaid-svg-SzY930dnJazKd86c svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SzY930dnJazKd86c p{margin:0;}#mermaid-svg-SzY930dnJazKd86c .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SzY930dnJazKd86c .cluster-label text{fill:#333;}#mermaid-svg-SzY930dnJazKd86c .cluster-label span{color:#333;}#mermaid-svg-SzY930dnJazKd86c .cluster-label span p{background-color:transparent;}#mermaid-svg-SzY930dnJazKd86c .label text,#mermaid-svg-SzY930dnJazKd86c span{fill:#333;color:#333;}#mermaid-svg-SzY930dnJazKd86c .node rect,#mermaid-svg-SzY930dnJazKd86c .node circle,#mermaid-svg-SzY930dnJazKd86c .node ellipse,#mermaid-svg-SzY930dnJazKd86c .node polygon,#mermaid-svg-SzY930dnJazKd86c .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SzY930dnJazKd86c .rough-node .label text,#mermaid-svg-SzY930dnJazKd86c .node .label text,#mermaid-svg-SzY930dnJazKd86c .image-shape .label,#mermaid-svg-SzY930dnJazKd86c .icon-shape .label{text-anchor:middle;}#mermaid-svg-SzY930dnJazKd86c .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SzY930dnJazKd86c .rough-node .label,#mermaid-svg-SzY930dnJazKd86c .node .label,#mermaid-svg-SzY930dnJazKd86c .image-shape .label,#mermaid-svg-SzY930dnJazKd86c .icon-shape .label{text-align:center;}#mermaid-svg-SzY930dnJazKd86c .node.clickable{cursor:pointer;}#mermaid-svg-SzY930dnJazKd86c .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SzY930dnJazKd86c .arrowheadPath{fill:#333333;}#mermaid-svg-SzY930dnJazKd86c .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SzY930dnJazKd86c .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SzY930dnJazKd86c .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SzY930dnJazKd86c .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SzY930dnJazKd86c .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SzY930dnJazKd86c .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SzY930dnJazKd86c .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SzY930dnJazKd86c .cluster text{fill:#333;}#mermaid-svg-SzY930dnJazKd86c .cluster span{color:#333;}#mermaid-svg-SzY930dnJazKd86c div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SzY930dnJazKd86c .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SzY930dnJazKd86c rect.text{fill:none;stroke-width:0;}#mermaid-svg-SzY930dnJazKd86c .icon-shape,#mermaid-svg-SzY930dnJazKd86c .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SzY930dnJazKd86c .icon-shape p,#mermaid-svg-SzY930dnJazKd86c .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SzY930dnJazKd86c .icon-shape .label rect,#mermaid-svg-SzY930dnJazKd86c .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SzY930dnJazKd86c .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SzY930dnJazKd86c .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SzY930dnJazKd86c :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

脚本发现失败
一个问题一个 Bug
开发修复
按原条件回归
通过?
测试计划改为通过
关闭 Bug

八、最后总结:一套能复用的测试习惯

SEO 测试最重要的不是记住多少术语,而是养成下面几个习惯:

  1. 入口能打开,不等于功能正确。 Sitemap 要继续看域名、内容和 URL 可访问性。
  2. robots 写了允许,不等于爬虫真能读到正文。 要模拟 UA 真正访问。
  3. 手工测试先走通,再写脚本。 把稳定的 URL、关键词、预期结果放到配置里。
  4. 失败信息要能让开发定位。 带上页面、视口、UA、图片 URL 或关键字。
  5. 没有前置条件,就标延期。 不猜测、不硬判。
  6. 修复后按原条件重跑。 回归通过后再关闭缺陷。

把这些流程沉淀下来后,SEO 测试就不再是"检查两个文件",而是一套可持续复用的网站质量保障方法。

相关推荐
qq_426003962 天前
启动playwright录制codegen生成自动化测试脚本
python·自动化
虎头金猫2 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
AI职业加油站2 天前
AI智能体应用工程师证书:政策红利下的职业新风口
大数据·运维·人工智能·学习·职场发展
此冬歌咏2 天前
K8s 节点故障实战:优雅驱逐 31 秒,硬故障 331 秒,以及那个永远 Pending 的 Pod
运维·k8s
-梅2 天前
linux(8) 软硬链接
linux·运维·服务器
张洛闻Eren2 天前
k8s云原生【第十课】:水平 Pod 自动扩缩容
运维·数据库·云原生·kubernetes·github
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
森叶2 天前
用远程调试端口自动化 Chrome 应用商店发布:做法、三个真坑,以及你必须知道的风险
自动化
布裘2 天前
【银河麒麟】V4桌面图标消失,右击鼠标没反应排查
运维·银河麒麟·桌面环境