Severity table

Bad backlinks checker: diff snapshots in SQLite

No script can call a link toxic for you. It can tell you which referring domains are new, which went missing, and which look odd enough to read by hand.

Severity table for new referring domains

Our own rules of thumb for triage, not a Google definition
LevelSignalAction
IgnoreNew domain, DR above 30, anchor is your brand or a bare URLLog it, move on
Look50 or more new domains in a week from one TLD you never targetedOpen ten of them
Look hardSame exact-match anchor on 20 or more new domainsCheck the anchor cloud, find the source
ActPaid-looking network or hacked pages pointing at youDocument it; consider a disavow file only if you also hold a manual action
WaitA domain vanished this monthRecheck after the next monthly refresh

Why diff instead of scanning once

A single snapshot tells you what is there. A diff tells you what changed. That is where problems show up.

Google has said for years, since the Penguin 4.0 release in September 2016 and again in the December 2022 link spam update, that it mostly ignores spammy links instead of punishing them. SpamBrain does that work. So a flood of junk is more often noise than a threat, and the diff mainly saves you from reacting to noise.

Keep your own history. Suites overwrite theirs.

diff.py

Two tables would be tidier. One is enough for a hobby setup: a row per domain per snapshot date.

python
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')

Troubleshooting the diff

SymptomLikely causeFix
Hundreds of 'gone' overnightIndex refresh swapped the sampleCompare monthly, not daily
Every domain looks newTable was empty or day format changedCheck select distinct day
DR column all NULLRow field name differs from drPrint one raw row and adjust
Duplicates after a rerunSame day inserted twiceDelete that day first, or add a unique key
Count below totalPagination stopped earlyFix the loop in the Actions runbook

What this cannot tell you

It cannot prove a link hurts you. Nobody outside Google can, and Search Console's manual actions report is the only place a penalty is stated.

It also cannot see links younger than the last refresh. For fresher evidence ask the people who run the linking site, or read your server logs for the referrers. Our note on timing goes deeper.

If you want the anchors side, anchor_cloud costs 2 credits and groups text by frequency, which is the quickest way to spot an exact-match pile-up.

Wiring the diff to a weekly job

Run diff.py right after the snapshot step. Print the counts, and exit with a non-zero code when new crosses a limit you picked, say 200 in a week for a small site. The job turns red, and GitHub emails you. Simple.

Pick the limit from your own history. After two months you know whether 15 new domains a week is normal or a spike, and any fixed number copied from a blog post is a guess.

Quiet weeks are the norm.

Bad link questions, answered plainly

Is there a reliable bad backlinks checker?
No tool can label a link harmful with certainty, because only Google knows how it treats each one. A script can surface patterns worth reading: sudden floods, repeated exact anchors, odd TLDs. You make the call after opening the pages.
Should I disavow what the diff flags?
Rarely. Google has said it largely ignores spam links, and a disavow file is meant for cases such as a manual action or a paid-link mess you made yourself. Document first, and check Search Console before you submit anything.
How often should I run it?
Snapshot weekly, review monthly. The index refreshes about once a month, so a daily diff mostly compares a table with itself. A monthly review matches the data and keeps the noise down.