收录型站点的死链批处理:分层判定与 URL 规范化

维护一个收录型站点(导航站、工具目录、资源索引)到一定规模后,最耗人力的工作不是审核新提交,而是持续处理失效条目。收录量到几千条、每天还有新提交进来时,人工巡检彻底失效,本文整理一套可自动运行的分层判定方案。

一、先区分三类"坏链"

把所有失败都当成 404 处理,是最常见的错误。实际至少有三种,处理动作完全不同。

类型 表现 真实原因 处理动作
瞬时失败 偶发超时、连接重置 目标站限流或临时故障 退避重试,不下架
路径失效 404 / 410 站点改版、路径调整 重新抓取首页找新路径
主体消亡 域名解析失败、站点关闭 网站关停或域名过期 判定下架

只有第三类该走下架流程。如果把第一类也下架,热门站点会随机消失,用户体验直接崩掉。

二、判定"主体消亡"的三个独立信号

把下架决策收敛到三个信号,满足其中任意两个才判定:

  1. DNS 解析连续失败 3 次,且相邻两次间隔大于 24 小时,用于排除 DNS 抖动与服务器短暂重启
  2. HTTPS 与 HTTP 同时无法建立连接,用于排除单纯的证书问题——很多老站证书过期但 HTTP 仍然可达
  3. 搜索引擎缓存中已无该域名索引,说明站点确实已不再对外提供服务

第三条最容易被忽略。前两条只能说明"此刻访问不了",而很多站点会换服务器、停机整顿几天。只有第三条能区分"暂时不可用"与"已经离场"。

三、批量抓取时的连接控制

回收死链本身也要发起大量请求,如果不做控制,很容易把目标站打挂,也会让自己的出口 IP 被封。

import asyncio
import random
from datetime import datetime
from urllib.parse import urlsplit

CONCURRENCY = 8          # 并发上限,别贪
MIN_INTERVAL = 1.5       # 同一域名的最小请求间隔(秒)
TIMEOUT = 10             # 单次请求超时
RETRY_BASE = 2.0         # 指数退避基数

_sem = {}
_last = {}

async def throttle(host: str):
    """同一域名串行 + 最小间隔,不同域名之间并发"""
    async with _sem.setdefault(host, asyncio.Semaphore(1)):
        prev = _last.get(host)
        if prev is not None:
            wait = MIN_INTERVAL - (datetime.now() - prev).total_seconds()
            if wait > 0:
                await asyncio.sleep(wait)
        _last[host] = datetime.now()

async def check(url: str) -> dict:
    host = urlsplit(url).hostname
    for attempt in range(3):
        await throttle(host)
        try:
            async with httpx.AsyncClient(timeout=TIMEOUT, follow_redirects=True) as c:
                r = await c.head(url)
                if r.status_code < 400:
                    return {"url": url, "ok": True, "status": r.status_code}
                return {"url": url, "ok": False, "status": r.status_code}
        except Exception as e:
            if attempt == 2:
                return {"url": url, "ok": False, "error": type(e).__name__}
            # 指数退避 + 抖动,避免重试风暴
            await asyncio.sleep(RETRY_BASE ** attempt + random.uniform(0, 1))

几个容易踩的点:

  • HEAD 请求优先,但有些站点不实现 HEAD,需要回退到 GET
  • 不要跟随重定向做最终判定,中间跳转到首页的往往正是"路径失效"而非"主体消亡"
  • 失败重试要有上限,无上限重试会让一轮扫描拖到几小时

四、复核环节不能省

自动流程标记为"疑似下架"的条目,一律进人工队列。原则是:任何自动化动作只降权,不删除。

人工复核通常只需要做一件事——打开首页看一眼。实测下来,绝大部分标记都能被一眼否掉:只是改了首页路径、只是把服务挪到了子域名、只是换了 CDN。

五、URL 规范化:投入产出比最高的一步

比起死链治理,重复 URL 的危害更大且更容易被忽略。同一站点被多个分类收录、同一页面因参数变体产生多个 URL,这些都会把权重稀释掉。

规范化的实现很直接:

from urllib.parse import urlsplit, urlunsplit

def normalize(url: str) -> str:
    p = urlsplit(url.strip())
    scheme = "https"          # 统一协议
    host = (p.hostname or "").lower()
    if host.startswith("www."):
        host = host[4:]        # 去掉 www
    path = p.path.rstrip("/")  # 去尾部斜杠
    return urlunsplit((scheme, host, path, "", ""))  # 丢弃 query

配套三条规则:

  1. 规范化函数作为唯一入口,写入前必经
  2. 变体 URL 全部 301 到规范 URL,不做 302
  3. 站点详情页只允许存在一种形式的地址

这三步做完,重复内容稀释权重的问题才算从根上解决。

六、批量提交时的三个卡点

关于向搜索平台提交 URL,有三点需要提前知道:

提交配额不是无限的。 搜索平台对单个站点的提交速率有上限,且这个上限和站点规模正相关。站点越大,单次提交量越要压着走。

提交了不等于收录了。 提交只是把 URL 放进候选池,能不能被索引取决于内容质量、页面可抓取性和站外引用。收录型站点本身原创内容有限时,这一环尤其明显。

变体提交会浪费配额。 同一个页面提交多次,除了稀释权重,还在消耗本就不宽裕的提交额度。

小结

死链批处理拆成三步:分层判定(区分瞬时/路径/主体)、多信号确认(DNS + 连通性 + 搜索索引)、人工兜底(只降权不删除)。

如果也在维护收录型站点,建议的优先级是:先建好规范化和去重,再谈批量提交。顺序反了,后面所有工作都会被重复内容稀释。

本文涉及的收录治理思路与公开分类结构,可参考 VP导航 的实现方式。

posted on 2026-10-24 23:12  亿云信息科技  阅读(3)  评论(0)    收藏  举报

导航