Python 实现网站 SEO 巡检:正确处理 PageSpeed 与域名接口状态

把 PageSpeed、DNS、SSL 和 WHOIS 放进同一个 Python 任务时,难点不在发出四个 HTTP 请求,而在于正确处理不同返回契约、缓存信息和失败边界。

本文以 网页性能与 SEO 评分 API 为主,给出一套可复用的 Python 封装思路。示例使用标准库,AppKey 通过服务端环境变量读取。

网页性能与 SEO 评分

先定义统一结果,而不是统一原始响应

PageSpeed 的业务状态位于 DataStatus.StatusCode,成功值为 100;DNS、SSL、WHOIS v2 接口则返回 dataStatus.statusCode=200。强行用一套字段解析所有响应,会把正常结果误判为失败。

更合适的方法是分别验证原始契约,再映射到统一结果:

Python 巡检器分别校验 PageSpeed 成功码 100 与 v2 域名接口成功码 200,再归一为统一结果并分类处理失败

from dataclasses import dataclass, field
from typing import Any


@dataclass
class CheckResult:
    check_name: str
    target: str
    ok: bool
    collected_at: str
    raw_status: int
    data: dict[str, Any] = field(default_factory=dict)
    error: str | None = None

统一结果用于报告和存储,原始响应仍可在受控日志或对象存储中按合规要求保留。

封装 GET 请求

import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen


API_ROOT = "https://api.gugudata.com"


def get_json(path: str, params: dict[str, str], timeout: int = 60) -> dict:
    query = urlencode(params)
    request = Request(
        f"{API_ROOT}{path}?{query}",
        headers={"X-GUGUDATA-APPKEY": os.environ["GUGUDATA_APPKEY"]},
        method="GET",
    )
    with urlopen(request, timeout=timeout) as response:
        return json.load(response)

不要把 AppKey 放进 params,这样更容易避免密钥进入访问日志。生产实现还应限制读取大小,并分别处理超时、连接错误和非 2xx HTTP 状态。

PageSpeed 校验器

def fetch_pagespeed(page_url: str) -> dict:
    payload = get_json(
        "/websitetools/pagespeed-score",
        {
            "url": page_url,
            "strategy": "mobile",
            "locale": "zh-CN",
            "categories": "performance,accessibility,best-practices,seo",
            "forceRefresh": "false",
        },
    )
    status = payload.get("DataStatus", {})
    if int(status.get("StatusCode", 0)) != 100:
        raise RuntimeError(status.get("StatusDescription", "PageSpeed failed"))
    return payload.get("Data", {})

需要保存的不只是 ScoresFinalUrl 可以帮助发现重定向,FetchTimeCached 用于解释数据时效,TopIssues 才能把分数转成具体优化项。

v2 域名接口校验器

def fetch_domain_check(path: str, domain: str) -> dict:
    payload = get_json(path, {"domain": domain}, timeout=30)
    status = payload.get("dataStatus", {})
    if int(status.get("statusCode", 0)) != 200:
        message = status.get("statusDescription", "Domain check failed")
        raise RuntimeError(message)
    return payload.get("data", {})


def fetch_dns(domain: str) -> dict:
    return fetch_domain_check("/v2/websitetools/dns-lookup", domain)


def fetch_ssl(domain: str) -> dict:
    return fetch_domain_check("/v2/websitetools/sslcertinfo", domain)


def fetch_whois(domain: str) -> dict:
    return fetch_domain_check("/v2/websitetools/whois", domain)

这里按当前 v2 契约判断 200,不要把 PageSpeed 的 100 复制过来。

从 URL 安全提取域名

from urllib.parse import urlsplit


def extract_domain(page_url: 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")
    return parsed.hostname.encode("idna").decode("ascii")

不要通过字符串切割提取域名。真实 URL 可能包含端口、认证信息、IPv6、国际化域名或路径中的相似文本。

URL 与域名任务分别去重

假设一次巡检包含同一域名下的 20 个页面,PageSpeed 需要按 20 个 URL 执行;DNS、SSL、WHOIS 通常只需按域名执行一次。

def build_targets(page_urls: list[str]) -> tuple[list[str], list[str]]:
    urls = list(dict.fromkeys(page_urls))
    domains = list(dict.fromkeys(extract_domain(url) for url in urls))
    return urls, domains

这个小步骤能直接减少重复查询,也让缓存键和失败重试更清晰。

不要把所有失败都重试

建议按三层分类:

类型 例子 处理
传输失败 超时、连接中断、临时 5xx 有上限地退避重试
契约失败 业务状态非成功、字段缺失 记录原始状态,进入失败队列
内容异常 分数下降、证书临近到期、DNS 变化 生成行动项,不重试掩盖结果

对参数错误、权限问题和稳定复现的业务失败不断重试,只会消耗额度并延迟真正需要处理的问题。

报告记录建议

每次检查至少保存:目标、检查类型、HTTP 状态、原始业务状态、采集时间、是否缓存、关键结果摘要、首次发现时间、连续出现次数和负责人。

PageSpeed 评分应在相同 strategy 下比较;WHOIS 字段缺失要标记为未知,因为隐私保护或注册局差异都可能造成空值。最终报告要告诉读者“下一步做什么”,而不是只打印 JSON。

接入前自测

  • 使用无效 URL 验证参数失败路径。
  • 使用可重定向 URL 验证 FinalUrl 保存逻辑。
  • 确认 PageSpeed 成功码 100 与 v2 域名成功码 200 分开处理。
  • 多个同域 URL 只生成一组域名任务。
  • 日志中不出现 AppKey。
  • 缓存结果保留 CachedFetchTime
  • 重试次数有上限,业务异常不会被当成网络错误。

正确处理接口边界之后,SEO 巡检代码才有资格进入定时任务;否则再漂亮的报告,也可能建立在错误的成功判断上。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐