第 0009 课 · 集大成
全站审查(The Site Audit)
前八课都只在一个 URL 上检查一个信号。真实站点有几百个页面。像 bot 一样 crawl(抓取)它,把所有 gate(关卡)一次性跑完。
回顾:第 0002–0008 课各自拿一个信号——robots/noindex、title、canonical、structured data(结构化数据)、一个 <h1>、正文篇幅——在单个 URL 上检查。那是单元测试。这一课是集成测试。
真实站点有几百个页面,而出问题的那个信号,很少恰好坏在你随手看的那一页上——它坏在模板(template)里,所以它坏在每一个用了该模板的页面上。要发现这一点,你不能只审查一个 URL。你得像 bot 那样 crawl 整站,并对每个页面把所有 gate 一次性跑完。
像 bot 一样 crawl:从 seed 做 BFS
crawler 并没有背下你的 sitemap。它从一个 URL 出发,读取链接,把没见过的排进队列,然后重复——广度优先——并留在本站内。两条规则让它守规矩:
- 单一域名。 只跟随 host 与 seed 一致的链接(用
seolib.domain);跳过站外链接——别人的站点不归你审查。 - 设上限。 跑满 N 个页面就停。礼貌的 crawl 是有界的;无界的 crawl 会把你本想帮的服务器锤爆。
# the heart of it — BFS over internal links, capped
host = domain(seed)
seen, out, queue = set(), [], [seed]
while queue and len(out) < max_pages:
url = queue.pop(0)
if url in seen: continue
seen.add(url)
resp = fetch(url, ua="Googlebot")
pg = parse(resp.body) # one seolib parse → every signal
out.append((url, resp, pg))
for href in pg.links:
nxt = urljoin(url, href) # relative → absolute
if domain(nxt) == host and nxt not in seen:
queue.append(nxt) # internal + unseen → enqueue
每个关卡,每个页面
对每个 crawl 到的页面,跑完整张清单——就是各课工具各自拥有的那些判定,如今汇成一张表:
| 关卡 | 检查 | 最早出现于 |
|---|---|---|
| CRAWL | 页面返回成功(2xx/3xx) | crawl_audit.py · 0002 |
| INDEX | 没有 noindex(meta 或 X-Robots-Tag) | 0002 |
| INDEX | 有 <title> | 0002 |
| INDEX | 声明了 canonical | 0002 |
| INDEX | 恰好一个 <h1> | 结构 · 0004 |
| INDEX | 有 JSON-LD structured data | schema_tool.py · 0003 |
| INDEX | 正文篇幅够,能 retrieve(检索)出一个 chunk(块) | geo_lint.py · 0004 |
跑起来
这个集大成工具把之前每一课都拉进一条命令里:
- 离线自检(会 crawl 一个内置的 3 页站点):
python3 tools/site_audit.py --demo - 审查一个你自己拥有的小型真实站点:
python3 tools/site_audit.py https://your-site.example/ - 读汇总,别只看逐页清单。“N/total failing”最高的那项就是你的第一个要修的——它几乎总是模板,而非某个页面。
$ python3 tools/site_audit.py https://acme.test/ Site audit: https://acme.test/ (3 pages) ────────────────────────────────────────────── [PASS] clean · https://acme.test/ [WARN] 2 issue(s) · https://acme.test/pricing [WARN] 6 issue(s) · https://acme.test/blog/x ────────────────────────────────────────────── VERDICT: 2/3 pages have at least one gate failing — fix worst first.
$ # …findings rolled up across the site [PASS] CRAWL · page returns success all pages OK [WARN] INDEX · no noindex directive 1/3 pages failing [WARN] INDEX · has a <title> 1/3 pages failing [WARN] INDEX · declares a canonical 2/3 pages failing [WARN] INDEX · exactly one <h1> 1/3 pages failing [WARN] INDEX · has JSON-LD structured data 2/3 pages failing [WARN] INDEX · enough body text 1/3 pages failing ────────────────────────────────────────────── VERDICT: canonical + JSON-LD fail on 2/3 — one template fix clears both.
要知道的天花板:这是静态抓取(没有浏览器),所以 JS 渲染的内容不可见——即第 0006 课的 render gap(渲染缺口)。它只走它能找到的链接(orphan pages,孤立页面,需要你的 sitemap)。而且它审查的是机器可检查的信号——而非文笔好不好。地图不是疆域;它只告诉你哪些 gate 是关着的。
提取练习 · 不许偷看
审查整站,而非单页
凭记忆作答——正是这份努力让知识留得住。每题只有一次机会;在看其他选项前先选。
other-site.com 的链接?canonical — 2/3 pages failing。开发者通常的修法是……urllib(没有浏览器)抓取每个页面。它会漏掉什么?Google 自己端到端列出一个站点该做对的事——正是这个集大成工具所审查的那些 gate, 来自定义它们的源头。