SEO 巡检不能只看一个分数。页面加载变慢、Canonical 配置错误、DNS 记录漂移、证书临近到期,都会影响搜索引擎访问和用户体验,但它们属于完全不同的问题域。如果把所有信号混成一个“站点健康分”,报告看起来完整,却很难指导修复。
更实用的做法,是把巡检拆成三层:
- URL 层检查页面性能与 SEO 审计结果;
- 域名层检查 DNS 、SSL 与 WHOIS ;
- 规则层结合历史基线、数据新鲜度和连续次数生成行动项。
本文使用 网页性能与 SEO 评分、DNS 查询、SSL 证书解析 和 WHOIS 查询,搭建一条可定时执行、可追踪、不过度告警的网站巡检管线。

一张图看懂巡检数据怎么流动
同一个站点可以有几十个重点 URL ,但通常只对应一个主域名。页面评分应按 URL 执行,DNS 、SSL 、WHOIS 则应按域名去重执行,最后再汇聚成报告。
这条数据流有两个关键边界:页面结果不能代替域名状态,域名异常也不能直接伪装成页面 SEO 评分失败。两类检查分别保留原始状态,规则层只负责解释和分级。
四个接口分别回答什么问题
| 检查范围 | 接口 | 主要结果 | 适合回答的问题 |
|---|---|---|---|
| URL | PageSpeed | 四类评分、FCP 、LCP 、TBT 、CLS 、SEO 审计、TopIssues | 页面是否变慢,标题、Canonical 、robots.txt 、viewport 是否存在明显问题 |
| 域名 | DNS | A 、AAAA 、CNAME 、MX 、TXT 、NS 、SOA 等记录 | 解析记录是否符合预期,是否发生未经确认的变化 |
| 域名 | SSL | 证书链、有效期、主机与诊断信息 | 证书是否有效,是否接近到期,主机名是否匹配 |
| 域名 | WHOIS | 注册时间、到期时间、注册商、名称服务器等 | 域名注册信息是否可用,重要日期是否需要提醒 |
WHOIS 字段可能受注册局和隐私保护影响。字段缺失应标记为“未知”,不能直接判定为域名故障。
第一步:规范化 URL 和域名
巡检任务的输入不是一个模糊的“站点”,而是两组明确目标:
- URL 清单:首页、产品页、文档页、转化页等重点页面;
- 域名清单:从 URL 中提取并标准化后的唯一域名。
from urllib.parse import urlsplit
def normalize_target(page_url: str) -> tuple[str, str]:
parsed = urlsplit(page_url)
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
raise ValueError("A valid HTTP or HTTPS URL is required")
hostname = parsed.hostname.encode("idna").decode("ascii")
normalized_url = parsed._replace(fragment="").geturl()
return normalized_url, hostname
不要用字符串切割域名。真实 URL 可能包含端口、国际化域名、IPv6 或路径中的相似文本。
第二步:采集页面性能和 SEO 信号
PageSpeed 接口当前使用 GET /websitetools/pagespeed-score,支持 mobile 与 desktop 两种策略,默认是 mobile。一次请求可以返回:
- Performance 、Accessibility 、Best Practices 、SEO 四类评分;
- FCP 、LCP 、Speed Index 、TBT 、CLS 等页面体验指标;
- 标题、描述、Canonical 、HTTP 状态、robots.txt 、viewport 等 SEO 审计摘要;
- 最多 10 条主要待优化项;
- 报告生成时间、报告版本和缓存标记。
新接入建议把 AppKey 保存在服务端环境变量中,并通过 Header 传递:
import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
def fetch_pagespeed(page_url: str) -> dict:
query = urlencode({
"url": page_url,
"strategy": "mobile",
"locale": "zh-CN",
"categories": "performance,accessibility,best-practices,seo",
"forceRefresh": "false",
})
request = Request(
f"https://api.gugudata.com/websitetools/pagespeed-score?{query}",
headers={"X-GUGUDATA-APPKEY": os.environ["GUGUDATA_APPKEY"]},
method="GET",
)
with urlopen(request, timeout=60) as response:
payload = json.load(response)
data_status = payload.get("DataStatus", {})
if int(data_status.get("StatusCode", 0)) != 100:
message = data_status.get("StatusDescription", "PageSpeed failed")
raise RuntimeError(message)
return payload.get("Data", {})
HTTP 200 只说明请求得到响应,不能替代业务状态判断。生产实现还要捕获超时和非 2xx 状态,并限制响应体大小。
第三步:按域名执行 DNS 、SSL 和 WHOIS 检查
三个域名接口当前都是 GET 请求:
/v2/websitetools/dns-lookup
/v2/websitetools/sslcertinfo
/v2/websitetools/whois
它们与 PageSpeed 存在一个容易踩坑的契约差异:
| 接口类型 | 状态对象 | 当前成功值 |
|---|---|---|
| PageSpeed | DataStatus.StatusCode |
100 |
| DNS 、SSL 、WHOIS v2 | dataStatus.statusCode |
200 |
因此,不能把 PageSpeed 的校验器直接复制给 v2 域名接口:
def require_v2_success(payload: dict) -> dict:
data_status = payload.get("dataStatus", {})
if int(data_status.get("statusCode", 0)) != 200:
message = data_status.get("statusDescription", "Domain check failed")
raise RuntimeError(message)
return payload.get("data", {})
聚合层可以把两类结果统一映射为 success、failed 或 unknown,但必须同时保留原始状态字段和采集时间,避免统一过程中丢失排错依据。
分数下降不等于立刻告警
巡检系统最难的部分不是请求接口,而是决定哪些信号需要立即处理。建议先识别可用性或安全硬故障,再排除缓存过期和计划内变更,最后比较历史基线。
这套决策逻辑把结果分为三类:
- 立即处理:页面不可访问、证书无效等确定性硬故障;
- 生成行动项:问题偏离历史基线,并连续出现或影响关键页面;
- 继续观察:单次轻微波动、数据过旧或仍需下一轮确认的信号。
固定分数线通常不适合所有站点。更可靠的比较条件是同一 URL 、相同检测策略、相近采集条件下的历史变化。
四类信号如何转换成行动项
| 原始信号 | 容易产生的误判 | 建议动作 |
|---|---|---|
| Performance 或 SEO 分数下降 | 页面已经故障 | 查看 TopIssues 、最终 URL 、缓存标记,并与同策略基线比较 |
| DNS 记录变化 | 解析一定错误 | 与预期记录和计划变更窗口核对 |
| SSL 证书接近到期 | 网站已经不可访问 | 生成到期提醒,核对自动续期状态和证书链 |
| WHOIS 字段为空 | 域名异常 | 标记数据缺口,考虑隐私保护与注册局差异 |
行动项至少应包含目标、问题类型、严重度、首次发现时间、连续次数、证据摘要、负责人和下一步。没有明确责任人与动作的问题,只是一条日志。
缓存和数据时间必须可见
PageSpeed 返回 Cached 和 FetchTime。如果报告只展示“任务在 10:00 执行”,读者可能误以为所有页面都在 10:00 重新检测。
建议分别保存:
scheduled_at:任务计划执行时间;collected_at:本次响应时间;fetch_time:评分报告实际生成时间;cached:是否复用了近期结果;strategy:mobile 或 desktop ;report_version:报告版本。
常规巡检不应默认对全部页面使用 forceRefresh=true。关键页面出现异常时,可以由受控复核任务请求刷新。本文没有验证具体缓存窗口和刷新成本,因此不提供固定刷新间隔。
允许“部分完成”,不要用总状态覆盖事实
PageSpeed 可能成功,而 WHOIS 因字段不可用只返回部分信息; DNS 查询也可能失败,但页面仍然能够访问。一轮巡检最好支持以下状态:
| 状态 | 含义 |
|---|---|
| complete | 计划内检查均得到可用结果 |
| partial | 至少一项成功,至少一项失败或未知 |
| failed | 没有获得可用检查结果 |
| stale | 只有超过时效要求的历史结果 |
报告必须展示每个子检查的独立状态。新的临时失败也不应覆盖上一轮仍在有效期内的成功结果。
一份可执行的巡检报告应该包含什么
建议按以下顺序组织报告:
- 本轮范围:URL 数量、域名数量、检测策略与实际数据时间;
- 立即处理:可用性、证书和关键页面硬故障;
- 持续问题:连续出现的评分、Canonical 、DNS 或注册信息问题;
- 数据缺口:失败、未知、缓存过旧和权限不足;
- 行动清单:负责人、优先级、证据和下一次复核时间。
不要把完整 WHOIS 响应无差别复制到普通报告。它可能包含联系信息,应只保留支持业务判断的必要摘要,并按权限控制原始响应。
常见问题
是否需要每天强制刷新全站所有页面?
通常不需要。先按页面价值分层,常规任务记录缓存状态和真实数据时间;关键页面异常时再进行受控刷新。
DNS 、SSL 或 WHOIS 失败,是否代表页面 SEO 失败?
不代表。它们属于域名层信号。报告可以提升其优先级,但不能伪造 PageSpeed 结果。
单次 PageSpeed 分数下降是否应该告警?
硬故障可以即时处理;普通波动更适合结合历史基线、连续次数、变化幅度和页面重要性判断。
同一域名下有很多页面,域名接口需要重复调用吗?
通常不需要。页面检查按 URL 执行,DNS 、SSL 、WHOIS 按标准化域名去重执行,再在报告阶段关联。
上线前检查清单
- URL 任务和域名任务分别去重、缓存和重试;
- PageSpeed 与 v2 域名接口使用各自的成功契约;
- AppKey 只保存在服务端,不进入前端代码和普通日志;
- 保存最终 URL 、检测策略、缓存标记、实际数据时间和原始状态;
- WHOIS 缺失字段保持 unknown ,不伪造默认值;
- 任一子检查失败时,其他可信结果仍能进入报告;
- 告警结合硬故障、历史基线、连续次数和页面重要性;
- 重试有次数上限,新失败不会覆盖上一轮有效结果。
SEO 自动巡检的目标不是制造更多分数,而是把页面、域名和历史变化组织成一份有证据、有人负责、能够执行的行动清单。