Recipe

Backlinks finder in Python: a prospect list in forty lines

Find domains that link to your rivals but not to you, score them in one call, and sort the CSV. A worked example with hypothetical numbers is below.

The logic before the code

Three calls do the job. intersect takes two to ten domains and returns who links to several of them. refdomains on your own site gives the set to subtract. bulk_dr then scores whatever remains.

Cost per run is tiny. Two credits for the intersect, two for your own list, one for scoring up to 2,000 domains. Five credits is $0.0005.

Order matters. Subtract before you score.

People often score everything first and filter later, which wastes nothing in money but a lot in time when the CSV has fifty thousand rows. Cut the list down by overlap count first, then ask for DR on the survivors, and keep the whole thing in a plain dict so a second run can reuse yesterday's result.

prospects.py

Domains in RIVALS are placeholders. Field names are read with .get, so check one raw row from your own run before you rely on a column.

python
import csv, json, os, urllib.request as u, urllib.parse as p
KEY = os.environ['LZ_KEY']
ME = 'mysite.example'
RIVALS = ['rival-a.example', 'rival-b.example', 'rival-c.example']
def get(**kw):
    q = p.urlencode(kw)
    with u.urlopen(f'https://linkzoid.pro/api.php?{q}&key={KEY}', timeout=30) as r:
        d = json.load(r)
    if not d['ok']:
        raise SystemExit(d)
    return d
gap = get(action='intersect', q=','.join(RIVALS), per=1000)['rows']
mine = {r.get('domain') for r in get(action='refdomains', q=ME, per=1000)['rows']}
fresh = [r.get('domain') for r in gap if r.get('domain') not in mine]
scores = get(action='bulk_dr', domains=','.join(fresh[:2000]))['rows']
with open('prospects.csv', 'w', newline='') as f:
    wr = csv.writer(f)
    for row in scores:
        wr.writerow([row.get('domain'), row.get('dr')])
print(len(fresh), 'candidates')

Worked example, hypothetical numbers

Invented run for a small niche blog; yours will differ
StepRows outCredits
intersect of 3 rivals640 domains2
Minus your own referring domains410 left2
bulk_dr on the 410410 scored1
Keep DR 20 and aboveabout 1200
Hand-check the top 3030 pages opened0

From CSV to outreach, without spamming anyone

01

Sort by overlap

Domains that link to two or three of your rivals are the ones with a topical reason to link to you. Put them first.

02

Open thirty by hand

Look at the page that links to the rival. Is it a resource list, a review, a news item? Only some of these accept a new entry.

03

Write one line of reason

Say what your page adds that the rival's page does not. If you cannot say it in a sentence, skip that site.

04

Log what you sent

Add columns for date, contact and result to the same CSV, and commit it. Next quarter you will thank yourself.

Mistakes and fixes

Prospecting scripts go wrong in the same few places
MistakeWhy it hurtsFix
Sorting by DR aloneBig directories and scrapers float to the topSort by overlap count first, DR second
Mailing everything above a cutoffReads like spam, costs goodwillOpen the top 30 by hand
URL-encoding by handCommas and slashes break the queryUse urllib.parse.urlencode
Sending 3,000 domains in one callOver the 2,000 limitChunk the list, one credit per chunk
Trusting DR as qualityIt is a log-scale count of referring domainsTreat it as a sort key, not a verdict

A word on what DR can and cannot say

DR here is round(100 times log(ref_domains + 1) over log(20,000,000)). It rises with referring domains and nothing else. Keep that in mind. A site can sit at 55 and still be useless to you.

Use it to rank, then read. Look at what the site publishes, whether it links out to a hundred unrelated shops, and whether anything on it was updated since the March 2024 core update. Those checks take a minute per site and no script replaces them.

Once the list is clean, keep it as a dated file and rerun in a month. The Actions runbook shows how to schedule it, and the n8n comparison covers the no-code route if your outreach person prefers it.

Rate and size notes

Each call returns up to 1,000 rows and is billed once, so paging through 5,000 intersect rows costs 10 credits, or $0.001. Deep pagination stops at 50,000 rows. Use sort and order=desc on the server rather than sorting a huge list in Python afterwards.