网站 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 测试就不再是"检查两个文件",而是一套可持续复用的网站质量保障方法。

相关推荐
乐橙开放平台13 分钟前
把工地遮挡和掉线接进项目部:setMessageCallback 订 alarm 与 deviceStatus
笔记·后端·物联网·自动化·音视频·智能家居
Kismet_nvi13 分钟前
Python运维自动化核心模块
运维·python·自动化
夏炳辉.17 分钟前
Rocky Linux 8 从零安装 psql(PostgreSQL 客户端)
linux·运维·postgresql
姚不倒19 分钟前
Nginx 核心架构:Master-Worker、epoll、热重载深度解析
运维·nginx·架构
ZEB110626 分钟前
泰瑞机器7200吨超大型二板式注塑机正式交付 助力大口径钢塑复合管件制造突破
运维·制造
pride.li29 分钟前
Docker教程-Docker安装与镜像配置
运维·docker·容器
bgy666630 分钟前
Docker Swarm 容器编排实战指南
运维·docker·容器
CCTI_Curran1 小时前
电动工具CE认证到底测什么?一文说清
自动化·电动工具·ce认证·角磨机·砂轮机
speop1 小时前
hell-gpu| TASK01-2
linux·运维·算法