首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >AI 回答里的引用链接怎么清洗:跟踪参数、同站多域名、临时链接和失效率(附 Python)

AI 回答里的引用链接怎么清洗:跟踪参数、同站多域名、临时链接和失效率(附 Python)

原创
作者头像
用户12830378
发布于 2026-10-23 12:51:27
发布于 2026-10-23 12:51:27
1280
举报

我第一次给 AI 回答里的引用做排行,排在最前面的一项是 https://mp.weixin.qq.com/s,后面一个参数都没有。这当然不是哪篇文章,是一千多条带签名的公众号临时链接,被我的归一代码删光参数后挤成了一条。

按 URL 数引用,麻烦出在写法上。不同的页面可能被错归成一条,上面那条就是;更常见的是反过来,同一篇文章以好几种写法出现。DeepSeek 喜欢在链接末尾加 #1,引用知乎专栏时还会带上一串 utm_ 参数,同一篇腾讯云社区文章,有的平台给的是 .com,有的给 .com.cn。按原始 URL 计数,一篇文章的引用会被拆成好几份,排行榜就不准。

另一类麻烦是链接已经打不开了,状态码却看不出来。最典型的还是公众号临时链接:返回 200,页面上写着「链接已过期」。

下面先讲怎么把 URL 归一成「页面键」,再讲怎么批量检查链接还能不能打开。代码只用 Python 标准库。

一、数据

数据来自我们拿自家品牌做的监测(作者在 MaxAEO 做 AI 搜索可见度监测),文中例子和代码里的站点 ID 都是编的。我们固定了 40 个问题,每天在 8 个国内 AI 平台的用户界面上各问一遍,从 2026-09-27 到 10-22,共 26 天。成功拿到的回答有 8,232 条,其中 5,561 条带引用。按我的解析,引用一共拆出 128,873 条,有 31 条连网址都不是,是从回答正文里误抠出来的,比如 https://,后面直接跟着中文。剩下 128,842 条,原始写法各不相同的有 47,834 种。

二、同一个页面有几种写法

我把这 4 万多种写法按域名和路径模式过了一遍,常见的情况有这些:

情况

例子(ID 是编的)

这批数据里

末尾的片段

.../details/123456789#1#1

9,587 条引用带 #,其中 8,677 条来自 DeepSeek,占它全部引用的 53.1%

跟踪参数

知乎专栏的 utm_medium=openapi_platform,百家号的 wfr=spider&for=pc,公众号的 chksm、scene,腾讯云的 policyId、from

参数整串被删掉的有 3,653 条

同站多域名

腾讯云社区同一篇文章出现在 cloud.tencent.com、cloud.tencent.com.cn、cloud.tencent.cn、developer.cloud.tencent.com 四个域名下

四个域名分别 2,071、624、508、230 条;518 篇文章里有 193 篇被引时用过不止一个域名

移动版 / 旧路径

头条的 m.toutiao.com/group/ID、www.toutiao.com/aID、/article/ID;搜狐的 m.、news.、www.;网易的 m.163.com、dy.163.com

www.toutiao.com 名下按原始 host 算 1,104 条,归一后 2,037 条

分享页

抖音的 www.iesdouyin.com/share/video/ID,打开后跳到 www.douyin.com/video/ID

2,536 条

跳转壳

豆包有一部分链接包了一层 link.wtturl.cn/?target=真实地址

94 条

地方频道子域

凤凰网同一个 /c/ID 在 i.、nb. 等子域下都能打开,页面里的 canonical 却指向另一个地方频道,www.ifeng.com/c/ID 反而是 404

1,682 条

临时链接

公众号的 mp.weixin.qq.com/s?src=11&timestamp=...&signature=...,带签名,过期就打不开

1,551 条,全部来自元宝

短链在这批数据里几乎没有。我按 t.cn、url.cn、dwz.cn、bit.ly、tinyurl 这些常见短链域名查了一遍,26 天只出现 1 次。

上面这些「是同一页」的判断,我都拿真实链接验证过,方法有两种。一种是跟着跳转走:m.toutiao.com/group/ID 会 302 到 www.toutiao.com/article/ID/,dy.163.com/article/ID.html 会跳到 www.163.com/dy/article/ID.html。另一种是比较页面标题和 <link rel="canonical">:腾讯云四个域名下同一个 ID 的标题完全一样;搜狐 m. 和 news. 的 canonical 都指向 www.。没验证过的站点,我不写规则。

三、归一规则

page_key() 按顺序做五件事:

  1. 先判断是不是网址。末尾的中文标点、反引号先去掉,host 必须像个域名,否则返回 None。
  2. 拆跳转壳。target= 里就是真实地址,取出来递归再算一遍。
  3. 换 host、改路径。同站多域名只换 host;路径不同的(腾讯云的 developer. 子域、头条的 /group/、抖音分享页)用正则改写。
  4. 处理参数。认识的站点用白名单,只留能定位内容的参数:百家号留 id,公众号留 __biz、mid、idx、sn,其余大站一个都不留。不认识的站点用黑名单,只删 utm_*、spm、from 这类。不认识的站点不能用白名单,因为参数可能就是内容 ID,WordPress 的 ?p=42 删掉就成了首页。
  5. 处理片段。#1、#respond、#toc 都扔掉;#/ 和 #! 开头的是前端路由,算页面的一部分,留着。

有两处要说明。

  • 白名单落空时,参数原样保留。 开头那个榜首就是这么来的。公众号临时链接里没有 __biz 和 mid,只有签名。我第一版代码直接按白名单删参数,结果 1,551 条临时链接全被归成了同一个键 https://mp.weixin.qq.com/s,成了「被引最多的页面」。现在的处理是:白名单里的参数一个都没有,就认不出是哪篇,参数原样留着,每条算一个页面。
  • 键不一定能打开。 键只用来去重计数。凤凰网那种情况,我把所有子域归成 ifeng.com/c/ID,这个地址本身打不开。另外所有键都统一成 https://,不管原站支不支持。要打开页面,用原始链接。
代码语言:python
复制
"""把 AI 回答里引用的 URL 归一成「页面键」:同一篇内容,不管带什么参数、从哪个域名进来,键都一样。"""
import re
from urllib.parse import urlsplit, parse_qsl, urlencode

# ---------- 1. 跳转壳:真实地址就在参数里 ----------
UNWRAP = {
    "link.wtturl.cn": "target",
    "link.zhihu.com": "target",
    "link.juejin.cn": "target",
}

# ---------- 2. 同站多域名:只换 host,路径不变 ----------
HOST_ALIAS = {
    "cloud.tencent.com.cn": "cloud.tencent.com",
    "cloud.tencent.cn": "cloud.tencent.com",
    "m.sohu.com": "www.sohu.com",
    "news.sohu.com": "www.sohu.com",
    "m.163.com": "www.163.com",
    "m.jiemian.com": "www.jiemian.com",
    "toutiao.com": "www.toutiao.com",
    "m.toutiao.com": "www.toutiao.com",
    "m.toutiaocdn.com": "www.toutiao.com",
}

# ---------- 3. 路径改写:(host 正则, path 正则, 新 host, 新 path) ----------
PATH_RULES = [
    (r"developer\.cloud\.tencent\.com", r"/article/(\d+)", "cloud.tencent.com", "/developer/article/{0}"),
    (r"www\.toutiao\.com", r"/(?:article/|group/|a)(\d+)", "www.toutiao.com", "/article/{0}"),
    (r"www\.iesdouyin\.com", r"/share/video/(\d+)", "www.douyin.com", "/video/{0}"),
    (r"dy\.163\.com", r"/article/(\w+)\.html", "www.163.com", "/dy/article/{0}.html"),
    (r"view\.inews\.qq\.com", r"/[ak]/(\w+)", "news.qq.com", "/rain/a/{0}"),
    # 地方频道子域:同一 ID 在不同子域都能打开,canonical 却各指一处,只能当键用
    (r"[\w-]+\.ifeng\.com", r"/c/(\w+)", "ifeng.com", "/c/{0}"),
]
PATH_RULES = [(re.compile(h), re.compile(p + r"/?"), nh, np) for h, p, nh, np in PATH_RULES]

# ---------- 4. 参数:认识的站点用白名单,其余用黑名单 ----------
KEEP = {
    "baijiahao.baidu.com": {"id"},
    "mp.weixin.qq.com": {"__biz", "mid", "idx", "sn"},
    "www.toutiao.com": set(), "www.douyin.com": set(), "cloud.tencent.com": set(),
    "zhuanlan.zhihu.com": set(), "www.zhihu.com": set(), "blog.csdn.net": set(),
    "www.sohu.com": set(), "www.163.com": set(), "news.qq.com": set(), "ifeng.com": set(),
    "www.jiemian.com": set(), "k.sina.cn": set(), "baike.baidu.com": set(),
}
DROP = re.compile(r"(utm_\w+|spm|scm|from|frompage|source|scene|share\w*|ref|referfrom|trk|"
                  r"fbclid|gclid|msclkid|msockid|policyid|wfr|chksm|previous_page|upstream_biz|"
                  r"wid|spss|hubs_\w+|_hsenc|_hsmi)", re.I)

HOST_OK = re.compile(r"[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+")
TRAIL = "))】]》>。,、;;::!!??'\"`*"


def page_key(url):
    """返回页面键;不是合法网址返回 None。键不一定能直接打开(比如凤凰网那条),只用来去重计数"""
    url = url.strip().rstrip(TRAIL)
    try:
        s = urlsplit(url)
        host = (s.hostname or "").lower().rstrip(".")
    except ValueError:
        return None
    if s.scheme not in ("http", "https") or not HOST_OK.fullmatch(host):
        return None

    if host in UNWRAP:
        inner = dict(parse_qsl(s.query)).get(UNWRAP[host])
        if inner and inner.startswith("http"):
            return page_key(inner)

    host = HOST_ALIAS.get(host, host)
    path = re.sub(r"/{2,}", "/", s.path) or "/"
    for hp, pp, nh, np in PATH_RULES:
        m = pp.fullmatch(path) if hp.fullmatch(host) else None
        if m:
            host, path = nh, np.format(*m.groups())
            break
    if len(path) > 1:
        path = path.rstrip("/")

    q = parse_qsl(s.query, keep_blank_values=True)
    if host in KEEP:
        kept = [(k, v) for k, v in q if k in KEEP[host]]
        # 白名单里的参数一个都没有(比如带签名的临时链接),认不出是哪篇,参数原样留着
        q = kept if kept or not KEEP[host] else q
    else:
        q = [(k, v) for k, v in q if not DROP.fullmatch(k)]
    q = sorted(set(q))

    # 片段:#/ 和 #! 是前端路由,算页面的一部分;其他(#1、#respond、#toc)都扔掉
    frag = s.fragment if s.fragment.startswith(("/", "!")) else ""
    key = "https://" + host + path + ("?" + urlencode(q) if q else "")
    return key + ("#" + frag if frag else "")

拿 21 种构造的写法试一下:

代码语言:python
复制
from collections import defaultdict
from urlnorm import page_key

urls = """
https://blog.csdn.net/someone/article/details/123456789#1#1
https://blog.csdn.net/someone/article/details/123456789
https://zhuanlan.zhihu.com/p/1000000000000000001?utm_medium=openapi_platform&utm_source=abc
https://zhuanlan.zhihu.com/p/1000000000000000001#1
https://baijiahao.baidu.com/s?id=1800000000000000001&wfr=spider&for=pc
https://mp.weixin.qq.com/s?__biz=MzAxMTEx==&mid=2650000001&idx=2&sn=abc123&chksm=ff00aa&scene=27
http://mp.weixin.qq.com/s?mid=2650000001&__biz=MzAxMTEx==&sn=abc123&idx=2
http://mp.weixin.qq.com/s?src=11&timestamp=1790000000&ver=6990&signature=xyz
https://cloud.tencent.com.cn/developer/article/2700001?policyId=1003#1#1
https://cloud.tencent.cn/developer/article/2700001?from=15425&frompage=seopage
https://developer.cloud.tencent.com/article/2700001
http://m.toutiao.com/group/7600000000000000001/
https://www.toutiao.com/a7600000000000000001
https://www.toutiao.com/article/7600000000000000001/?upstream_biz=toutiao_pc&wid=1789
https://www.iesdouyin.com/share/video/7600000000000000002
http://i.ifeng.com/c/8abcdEFGhij
https://nb.ifeng.com/c/8abcdEFGhij
https://link.wtturl.cn/?target=https%3A%2F%2Fwww.example.org%2F%3Fp%3D42%26utm_source%3Dx&scene=im
https://www.example.org/?p=42)。
https://app.example.org/#/docs/rate
https://`,或在换行处截断
""".split()

groups = defaultdict(list)
for u in urls:
    groups[page_key(u)].append(u)
for k, us in groups.items():
    print(f"{len(us)}  {k}")
print(f"{len(urls)} 种写法 -> {len([k for k in groups if k])} 个页面,{len(groups.get(None, []))} 条不是网址")

输出:

代码语言:plaintext
复制
2  https://blog.csdn.net/someone/article/details/123456789
2  https://zhuanlan.zhihu.com/p/1000000000000000001
1  https://baijiahao.baidu.com/s?id=1800000000000000001
2  https://mp.weixin.qq.com/s?__biz=MzAxMTEx%3D%3D&idx=2&mid=2650000001&sn=abc123
1  https://mp.weixin.qq.com/s?signature=xyz&src=11&timestamp=1790000000&ver=6990
3  https://cloud.tencent.com/developer/article/2700001
3  https://www.toutiao.com/article/7600000000000000001
1  https://www.douyin.com/video/7600000000000000002
2  https://ifeng.com/c/8abcdEFGhij
2  https://www.example.org/?p=42
1  https://app.example.org/#/docs/rate
1  None
21 种写法 -> 11 个页面,1 条不是网址

对一下:CSDN 和知乎去掉了片段和 utm_;公众号两种参数顺序归到同一个键,临时链接单独一个键;腾讯云三个域名、头条三种路径各归成一个;跳转壳里的 ?p=42 留下了,utm_source 删掉了;#/docs/rate 是前端路由,保留;最后一条不是网址,返回 None。

四、归一前后差多少

对 128,842 条引用按步骤归一,不同页面的数量变化如下:

步骤

不同页面数

原始写法

47,834

去掉片段

47,438

再处理参数、统一协议、去掉末尾斜杠

46,021

再拆跳转壳、合并多域名、改写路径

45,321

总数只少了 5.3%,看起来不多。影响主要在排行榜上:

  • 有 1,981 个页面在原始数据里有两种以上写法,这些页面一共被引 21,168 次,占全部引用的 16.4%。
  • 被引最多的 100 个页面里,44 个有两种以上写法。这 44 个页面里,最常见的那种写法只占它全部引用的一部分:中位数 79%,最少的只有 31%。
  • 归一后的前 20 名里,有 3 个页面按原始写法计数排在第 21、24、28 名,排不进前 20。
  • 被两个以上平台引用的页面有 4,220 个,其中 1,368 个(32.4%)在不同平台上的写法不一样。如果要看「哪些页面被多个平台同时引用」,不归一会漏掉三成。

五、检查链接还能不能打开

只看状态码不够。这批数据里碰到的情况有:

  • 公众号临时链接过期后,状态码是 200,页面上写「链接已过期」。
  • 抖音、头条的页面全靠前端渲染,静态请求拿到的 HTML 里几乎没有正文,看不出内容还在不在。
  • 知乎专栏会对脚本请求返回 403,CSDN 有时返回 521。这说明请求被拦了,不说明文章没了。
  • 博客园有的文章会跳到登录页。

所以 classify() 把结果分成七类:

类别

含义

判断依据

ok

能打开

都不符合下面几条

gone

确定没了

404、410

soft404

状态码正常,内容没了

正文很短,并且出现「链接已过期」「已被发布者删除」等字样

login

要登录

跳到了 login、passport 之类的地址,或者正文很短并且提示登录

blocked

被反爬拦了

403、412、521 等,或者跳到了验证码页

shell

前端渲染的空壳

去掉脚本和标签后,正文不到 50 个字符

error

超时、证书错误、连接失败

抛异常,重试一次还是失败

「正文很短」这个条件很重要。不加的话,一篇讨论「链接已过期怎么办」的文章也会被判成失效。

fetch() 用 urllib 默认的跳转处理,短链和移动版跳转会自动跟到底,最终地址记在 final 里。页面里有 canonical 的,也一并记下来,可以用来补充第三节的规则表。

代码语言:python
复制
"""逐个打开引用链接,判断还能不能看:ok / gone / soft404 / login / blocked / shell / error"""
import concurrent.futures as cf
import json, re, sys, urllib.error, urllib.request
from collections import Counter

UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0 Safari/537.36"
SOFT_404 = ["链接已过期", "已被发布者删除", "因违规无法查看", "内容已删除", "内容不存在", "文章不存在",
            "页面不存在", "页面找不到", "该文章已被删除", "page not found", "404 not found"]
LOGIN_URL = re.compile(r"(^|[./])(login|signin|passport|sso)([./?]|$)", re.I)
LOGIN_TEXT = ["登录后查看", "登录后才能查看", "请先登录", "扫码登录"]
BLOCK_TEXT = ["安全验证", "访问过于频繁", "人机验证", "captcha", "just a moment"]
SCRIPT = re.compile(r"<(script|style)[^>]*>.*?</\1>", re.S | re.I)
TAG = re.compile(r"<[^>]+>")


def visible_text(html):
    return re.sub(r"\s+", " ", TAG.sub(" ", SCRIPT.sub(" ", html))).strip().lower()


def classify(status, final_url, html):
    if status in (404, 410):
        return "gone"
    path = final_url.split("?")[0]
    if LOGIN_URL.search(path):
        return "login"
    if "captcha" in path.lower():
        return "blocked"
    if status in (401, 403, 412, 418, 429, 503, 521):
        return "blocked"
    if status >= 400:
        return "error"
    text = visible_text(html)
    if len(text) < 2000:            # 错误页、登录墙的正文都很短,长正文里出现这些词多半是文章内容
        for kind, words in (("soft404", SOFT_404), ("login", LOGIN_TEXT), ("blocked", BLOCK_TEXT)):
            if any(w in text for w in words):
                return kind
    if len(text) < 50:              # 正文全靠 JS 渲染(抖音、头条都是),静态请求看不出死活
        return "shell"
    return "ok"


def fetch(url, timeout=15):
    req = urllib.request.Request(url, headers={"User-Agent": UA, "Accept-Language": "zh-CN,zh;q=0.9"})
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:      # 默认跟随跳转,短链也在这一步展开
            return r.status, r.geturl(), r.read(500_000).decode("utf-8", "replace")
    except urllib.error.HTTPError as e:
        return e.code, e.geturl() or url, e.read(200_000).decode("utf-8", "replace")


def check(url, retries=1):
    for attempt in range(retries + 1):
        try:
            status, final, html = fetch(url)
            m = re.search(r'<link[^>]+rel=["\']canonical["\'][^>]*href=["\']([^"\']+)', html, re.I)
            return {"url": url, "status": status, "final": final,
                    "canonical": m.group(1) if m else None, "kind": classify(status, final, html)}
        except Exception as e:      # 超时、DNS、TLS、连接被重置
            err = f"{type(e).__name__}: {e}"[:120]
    return {"url": url, "status": None, "final": None, "canonical": None, "kind": "error", "error": err}


def run(urls, workers=16):
    with cf.ThreadPoolExecutor(workers) as ex:
        return list(ex.map(check, urls))


if __name__ == "__main__":
    urls = [l.strip() for l in open(sys.argv[1], encoding="utf-8") if l.strip()]
    results = run(urls)
    with open(sys.argv[2], "w", encoding="utf-8") as f:
        for r in results:
            f.write(json.dumps(r, ensure_ascii=False) + "\n")
    c = Counter(r["kind"] for r in results)
    for k in ("ok", "gone", "soft404", "login", "blocked", "shell", "error"):
        print(f"{k:<8}{c[k]:>5}  {c[k] / len(results):6.1%}")

在本机起一个假站点,几种情况各放一个页面:

代码语言:python
复制
"""本机起一个假站点,把几种失效情况各做一个页面,跑一遍 linkcheck"""
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from linkcheck import run

ARTICLE = "<html><head><link rel='canonical' href='https://news.example/a/1'></head><body>" + "正文。" * 800 + "</body></html>"
PAGES = {
    "/a/1": (200, {}, ARTICLE),
    "/s/xyz": (302, {"Location": "/a/1"}, ""),                                  # 短链
    "/a/2": (404, {}, "not found"),
    "/s?src=11&signature=abc": (200, {}, "<p>链接已过期</p>"),                  # 临时链接过期,状态码却是 200
    "/p/3": (302, {"Location": "/passport/login?next=/p/3"}, ""),
    "/passport/login?next=/p/3": (200, {}, "<p>请先登录</p>"),
    "/p/4": (403, {}, "forbidden"),
    "/video/5": (200, {}, "<div id='root'></div><script>render()</script>"),   # 前端渲染的空壳
}


class H(BaseHTTPRequestHandler):
    def do_GET(self):
        status, headers, body = PAGES.get(self.path, (404, {}, ""))
        self.send_response(status)
        for k, v in headers.items():
            self.send_header(k, v)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.end_headers()
        self.wfile.write(body.encode())

    def log_message(self, *a):
        pass


srv = ThreadingHTTPServer(("127.0.0.1", 8765), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()
base = "http://127.0.0.1:8765"
urls = [base + p for p in ["/a/1", "/s/xyz", "/a/2", "/s?src=11&signature=abc", "/p/3", "/p/4", "/video/5"]]
urls.append("http://127.0.0.1:9/nothing")                                     # 端口没开,连接失败
for r in run(urls, workers=4):
    final = (r["final"] or "-").replace(base, "")
    print(f'{r["kind"]:<8}{str(r["status"]):<5}{r["url"].replace(base, ""):<28}-> {final:<28}{r["canonical"] or ""}')
srv.shutdown()

输出:

代码语言:plaintext
复制
ok      200  /a/1                        -> /a/1                        https://news.example/a/1
ok      200  /s/xyz                      -> /a/1                        https://news.example/a/1
gone    404  /a/2                        -> /a/2                        
soft404 200  /s?src=11&signature=abc     -> /s?src=11&signature=abc     
login   200  /p/3                        -> /passport/login?next=/p/3   
blocked 403  /p/4                        -> /p/4                        
shell   200  /video/5                    -> /video/5                    
error   None http://127.0.0.1:9/nothing  -> -                           

短链 /s/xyz 跟到了 /a/1,canonical 也取到了;临时链接虽然返回 200,还是判成了 soft404;跳到登录页的判成 login;前端渲染的空壳单独归成 shell;端口没开的是 error。

六、实测:多少引用已经打不开

10 月 23 日,我从 45,321 个页面里随机抽了 500 个,每个页面用它最常见的那种写法去请求。第一遍 16 个并发、走代理出口;被拦和出错的 69 个,降到每 1.5 秒一个再跑一遍,还不行的换直连出口再跑一遍,最后又救回 19 个。结果:

类别

页面数

占 500 个

ok

388

77.6%

gone

10

2.0%

soft404

16

3.2%

login

1

0.2%

blocked

34

6.8%

shell

35

7.0%

error

16

3.2%

blocked、shell、error 三类合起来 85 个,判断不了死活,所以失效率只在剩下的 415 个里算:gone、soft404、login 一共 27 个,占 6.5%,95% 置信区间 4.5%~9.3%。按引用次数加权就低得多:这 415 个页面一共被引 1,206 次,失效页面只占 29 次,2.4%。被引得多的页面,大多还活着。

各类的来源很集中:

  • soft404 的 16 个全是公众号临时链接。样本里的临时链接也正好是这 16 个,全部过期。我又从 1,551 条临时链接里另抽了 100 条,结果也是 100 条全部 soft404,最近一天的也不例外。这 1,551 条临时链接占元宝全部公众号引用的 68.2%,占元宝全部引用的 6.0%。
  • 去掉临时链接,失效率是 11/399,2.8%(95% 区间 1.5%~4.9%)。gone 的 10 个多是小站改版后删掉的博客页;其中有一个是 www.example.com/a?utm_source=x,这种是回答里写的示例地址被当成了引用,统计前最好单独过滤。
  • blocked 里知乎专栏 11 个、CSDN 8 个;shell 里抖音 15 个、头条 7 个。

临时链接的问题,请求时再怎么处理也补不回来,只能在采集那一刻留住别的信息。好在引用详情里每条都带着标题:1,551 条临时链接都有标题,按标题合并后是 800 篇。所以统计时我对公众号临时链接另做一步(page_key() 只看 URL,不管这个):页面键改成「mp.weixin.qq.com + 标题」,并且标记「链接不可回访」,不进失效率的分母。标题撞车的情况有,但比把 1,551 条各算一篇要准。

七、判不了的那部分,和规则表怎么跟上

前端渲染的页面,静态请求判不了。抖音、头条要判断死活,得用无头浏览器渲染,或者走平台自己的接口。这两家在引用里的量不小,我现在是单独列出来,不算进失效率。被拦的也类似:知乎、CSDN 这类站点,换出口、降速只能救回一部分,剩下的要么接受「判不了」,要么换成渲染的方式再查。

规则表也会过时。站点会改域名、改路径,上面的规则是对着这批数据写的。我现在每次检查链接都顺手记下 final 和 canonical:同一个键出现了不同的 canonical,或者不同的键指向同一个 canonical,就说明规则该补了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、数据
  • 二、同一个页面有几种写法
  • 三、归一规则
  • 四、归一前后差多少
  • 五、检查链接还能不能打开
  • 六、实测:多少引用已经打不开
  • 七、判不了的那部分,和规则表怎么跟上
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档