为什么选择差异对比而非单次扫描
单次快照只能告诉你当前存在什么。差异对比能告诉你发生了什么变化,而问题往往就出现在变化中。
Google 多年来一直表示,自 2016 年 9 月 Penguin 4.0 发布以来,以及 2022 年 12 月链接垃圾更新再次确认,它主要忽略垃圾链接而非对其进行惩罚。SpamBrain 负责执行这一工作。因此,垃圾链接泛滥更多是噪音而非威胁,而差异对比主要能帮你避免对噪音做出反应。
保留你自己的历史记录。第三方套件通常会覆盖它们的数据。
严重性等级表
没有任何脚本能断定某个链接对你而言是“有毒”的。它只能告诉你哪些引荐域名是新出现的、哪些消失了,以及哪些看起来异常,需要人工审阅。

| 等级 | 信号 | 操作 |
|---|---|---|
| 忽略 | 新域名,DR 高于 30,锚文本为品牌词或裸 URL | 记录日志,继续处理 |
| 查看 | 一周内来自你从未针对的同一顶级域名 (TLD) 的 50 个或更多新域名 | 打开其中十个链接 |
| 仔细检查 | 20 个或更多新域名使用完全相同的精确匹配锚文本 | 检查锚文本云,找到来源 |
| 采取行动 | 看起来像付费链接网络或被黑页面指向你的网站 | 记录在案;仅在你同时持有手动操作处罚时,才考虑使用拒链文件 (disavow file) |
| 等待 | 某个域名本月消失 | 在下一次月度刷新后重新检查 |
单次快照只能告诉你当前存在什么。差异对比能告诉你发生了什么变化,而问题往往就出现在变化中。
Google 多年来一直表示,自 2016 年 9 月 Penguin 4.0 发布以来,以及 2022 年 12 月链接垃圾更新再次确认,它主要忽略垃圾链接而非对其进行惩罚。SpamBrain 负责执行这一工作。因此,垃圾链接泛滥更多是噪音而非威胁,而差异对比主要能帮你避免对噪音做出反应。
保留你自己的历史记录。第三方套件通常会覆盖它们的数据。
只有当每次运行都将数据写入同一个表且结构一致时,差异对比才有效。以下步骤可确保这一点。
运行一个“如果不存在则创建”的语句,包含你需要的列:日期、域名和评分。将其保留在脚本中,以便新机器无需手动步骤即可重建相同的表。
使用参数化插入,并在每次运行时提交一次。单次提交可防止因崩溃导致文件中只有一半的数据。
在插入前删除当天的行,或在日期和域名上添加唯一键。如果同一天重复运行,则会替换行而不是使行数翻倍。
使用 max(day) 过滤器选择最新的较早日期,而不是昨天的日期。这样可以确保在跳过一周的情况下比较仍然正确。
使用两个表会更整洁。但对于业余设置来说,一个表就足够了:每个快照日期的每个域名一行。
import json, sqlite3, sys, datetime
db = sqlite3.connect('links.db')
db.execute('create table if not exists snap(day text, dom text, dr int)')
rows = json.load(open(sys.argv[1]))
day = datetime.date.today().isoformat()
db.executemany('insert into snap values (?,?,?)',
[(day, r.get('domain'), r.get('dr')) for r in rows])
db.commit()
prev = db.execute('select max(day) from snap where day < ?', (day,)).fetchone()[0]
if prev:
new = db.execute('select dom from snap where day=? except select dom from snap where day=?', (day, prev)).fetchall()
gone = db.execute('select dom from snap where day=? except select dom from snap where day=?', (prev, day)).fetchall()
print(len(new), 'new,', len(gone), 'gone')| 症状 | 可能原因 | 修复方法 |
|---|---|---|
| 一夜之间数百个链接消失 | 索引刷新导致样本交换 | 按月比较,而非按日比较 |
| 每个域名看起来都是新的 | 表为空或 day 格式发生变化 | 检查 select distinct day |
| DR 列全为 NULL | 行字段名与 dr 不同 | 打印一行原始数据并调整 |
| 重复运行后出现重复项 | 同一天被插入了两次 | 先删除该天的数据,或添加唯一键 |
计数低于 total | 分页提前停止 | 修复 Actions runbook 中的循环 |

从博客文章中复制的阈值只是对他人网站情况的猜测。一个糟糕的 backlinks 检查器,其价值完全取决于其限制条件;而您自己的历史数据能告诉您域名正常的基准是什么。
首先记录几个月的数据,期间不对任何内容采取行动。让数据稳定下来。然后记录典型的每周波动范围。之后,再在严重性表格中设置限制条件。
记录每次运行时的新增和消失数量,不要对此做出反应。几个月的安静记录能为您提供属于您域名而非他人域名的范围。
一旦建立了基线,单个糟糕的周数据就会与之形成鲜明对比,您可以解释这种差异而无需猜测。这就是建立基线的全部意义所在。
表格中的每个信号对应一个阈值、一个负责人和一个操作。如果一个信号有三个警报,那么这个信号就没人会看,这些警报最终也会被一起忽略。
在代码中将警报写在对应信号旁边,这样本页面中的表格和脚本就不会出现不一致。
您的网站在增长,其正常范围也在扩大。每三个月重新检查一次限制条件,并在任何变更旁边注明原因,以便历史记录能自我解释。

该表格是一个分诊辅助工具,而非判决。每一行都告诉您该关注哪里,操作列能防止您在恐慌中盲目使用 disavow 文件。
从上往下处理。大多数周的数据最终会落在“忽略”或“查看”级别。“查看”行只需花几分钟阅读,“仔细查看”行则需要更长时间地审查 anchor text。不要因为本周忙碌就跳过行。跳过的行就是洪水未被察觉的那一周。
打开十个被标记的域名并阅读其页面。如果泛滥的流量来自一个目录爬虫,这与来自无关博客的泛滥流量是不同的问题,只有查看页面才能区分它们。
为每个决策保留一行记录:域名、级别、检查内容以及采取的操作。下一个阅读日志的人(包括未来的您)无需开会即可了解推理过程。
对这些行进行月度总结耗时不多,且可作为审查报告的良好初稿。
它无法证明某个链接对您有害。除了 Google 之外,无人能确定这一点,而 Search Console 的手动操作报告是声明处罚的唯一地方。
它还无法看到比上次刷新更年轻的链接。如需更新的数据,请联系链接站点的运营者,或查看服务器日志中的 referrers。我们的 关于时机的说明 对此有更深入的解释。
如果您需要查看 anchor text 侧,anchor_cloud 需要 2 个积分,并按频率对文本进行分组,这是快速发现精确匹配堆积的最快方法。
比上次刷新更年轻的链接在 diff 的两边都会缺失。在一个刷新窗口内出现又消失的链接根本不会显示,这就是为什么比较应每月运行一次的原因。
Diff 只能看到域名,看不到链接周围的句子。两个不同页面上相同的 anchor text 可能意味着完全不同的事情,因此请将 anchor 检查作为一个单独的、手动执行的步骤。
SQLite 是最简单的数据库,仍能用一个查询回答 diff 问题。它位于单个文件中,因此可以放入 repo 或备份文件夹,无需服务器。
带日期的 JSON 文件也可以。查询速度较慢,但当出现问题时,在文本编辑器中打开它更容易。无论哪种方式,在任何清理操作之前,请将存储复制到第二个位置,并在删除任何内容之前保留该副本至少一个完整的季度。
包含日期列的单表足以覆盖单个网站多年的数据。文件保持较小,且本页面上的查询无需调整。
一旦多人同时写入同一历史记录,单台机器上的文件就会成为瓶颈。此时,托管数据库是更合理的选择,且架构保持不变。
在此之前,repo 中的文件是能在笔记本电脑更换后幸存下来的最简单方案。
在快照步骤后立即运行 diff.py。打印计数,当 new 超过您设定的限制(例如小型网站每周 200)时,以非零代码退出。任务变红,GitHub 会给您发邮件。简单明了。
根据您的历史数据选择限制。经过两个月的观察,您就知道每周 15 个新域名是正常现象还是峰值,而任何从博客文章中复制的固定数字都只是一种猜测。
平静的一周是常态。