A lot of SEO and growth work starts with a spreadsheet of domains and the same question: which of these are worth our time?
- A link builder has 800 guest-post prospects and needs to drop the spammy ones.
- A sales team has a lead list and wants to sort it by how much traffic each company gets.
- A marketer wants a quick size comparison of ten competitors.
Opening each domain in an SEO tool doesn't scale past a few dozen. This post walks through a scriptable version: clean the list, pull link and traffic metrics in bulk, and score the results.
Step 1: clean the list
Domain lists are always messy: full URLs, www., trailing paths, duplicates, the odd email address. Normalize before you pay for anything:
import re
from urllib.parse import urlparse
def normalize(line):
s = line.strip().lower()
if not s or " " in s or "@" in s:
return None
if "://" not in s:
s = "http://" + s
host = urlparse(s).hostname or ""
host = re.sub(r"^www\d?\.", "", host)
return host if re.fullmatch(r"[a-z0-9-]+(\.[a-z0-9-]+)+", host) else None
raw = ["https://www.HubSpot.com/blog", "backlinko.com", "apify.com/store", "not a domain", "backlinko.com"]
domains = sorted({d for d in map(normalize, raw) if d})
print(domains) # ['apify.com', 'backlinko.com', 'hubspot.com']
Step 2: decide which metrics you need
For vetting, four numbers do most of the work:
| Metric | What it tells you |
|---|---|
| Domain rank (0–100) | Overall link-graph strength |
| Referring domains | How many separate sites link to it. Harder to fake than raw backlinks |
| Spam score (0–100) | Whether its backlink profile looks manipulated |
| Organic traffic (estimated monthly visits) | Whether real people find it through Google |
High rank with almost no traffic is a classic sign of a link-farm site. Strong traffic with a modest rank often means a real, niche site that's worth a pitch.
Step 3: pull the metrics in bulk
Disclosure: we (SteadyScrape) built the tool below and get paid when it's used.
Bulk Domain Rank & Traffic Checker (steady-scrape/domain-seo-metrics) is an Apify Actor that takes up to 10,000 domains per run and returns one row per domain: domain rank, backlinks, referring domains, spam score and the date links were first seen, plus estimated organic and paid Google traffic and ranking keywords for 90+ countries. The data comes from a licensed SEO data index built for API access, not from scraping SEO tools' pages, so 1,000 domains take under a minute.
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("steady-scrape/domain-seo-metrics").call(run_input={
"domains": domains, # from step 1
"region": "US",
})
rows = [r for r in client.dataset(run["defaultDatasetId"]).iterate_items() if r["status"] == "ok"]
Here's a slice of a real test run from October 27, 2026 (US traffic; link metrics are global, and the counts drift a little day to day):
| Domain | Rank | Referring domains | Spam score | Organic traffic / month |
|---|---|---|---|---|
| hubspot.com | 88 | 400,250 | 4 | 497,381 |
| zapier.com | 77 | 102,674 | 16 | 1,632,175 |
| ahrefs.com | 82 | 106,113 | 23 | 308,744 |
| apify.com | 76 | 15,314 | 18 | 206,492 |
| backlinko.com | 70 | 61,550 | 2 | 19,941 |
| spiegel.de | 82 | 261,808 | 28 | 44,860 |
That run had 19 input lines: 17 came back with data, one domain had no data, and one line wasn't a domain at all. The last two were free. The whole run took about 5 seconds.
Notice spiegel.de: strong links, but low US traffic, because it's a German site and traffic is measured for the region you pick. Set region to the market you care about, or worldwide.
Step 4: score and filter
Now turn the numbers into a decision. A simple prospect filter:
def keep(r):
return (
r["domainRank"] >= 40
and r["referringDomains"] >= 500
and r["backlinksSpamScore"] <= 30
and (r.get("organicTraffic") or 0) >= 1_000
)
def score(r):
# traffic matters more than rank for outreach; penalize spam
return r["domainRank"] + 10 * (len(str(r.get("organicTraffic") or 0)) - 1) - r["backlinksSpamScore"]
prospects = sorted(filter(keep, rows), key=score, reverse=True)
for r in prospects:
print(f'{r["domain"]:<25} rank {r["domainRank"]:>3} spam {r["backlinksSpamScore"]:>3} traffic {r.get("organicTraffic")}')
Tune the thresholds to your niche. Local and B2B sites rarely have huge traffic, and that's fine.
Going deeper on competitors
For a handful of competitors, the optional deep report adds their top keywords (with position, volume, CPC and difficulty), the ranking distribution (top 3 / top 10 / top 100), organic traffic value in USD, and their real search competitors:
run = client.actor("steady-scrape/domain-seo-metrics").call(run_input={
"domains": ["backlinko.com"],
"deepReport": True,
"deepTopKeywords": 25,
"deepCompetitors": 10,
})
In our test, backlinko.com showed 236 keywords in the top 3 and 1,113 in the top 10, with an estimated traffic value of about $27,900 a month. Its closest search competitors were other SEO publishers and tools.
Honest limits
- Traffic is an estimate, built from rankings × search volume × click-through rate. Every SEO tool works this way. It's good for comparing sites, but it won't match a site's own analytics.
- Domain rank is this index's own score. It isn't Moz DA or Ahrefs DR, though it moves in the same direction.
- Very new or tiny sites may have no data. Those rows are free.
What it costs
- $4 per 1,000 domains with data ($0.004 each) on Apify's free plan, dropping to $3 per 1,000 on higher Apify plans. No-data, invalid and error rows are free.
- $0.05 per run, charged only if at least one domain returns data.
- Deep report: $0.10 per domain, only when the report comes back non-empty.
- Apify's $0.00005 start fee per run.
So vetting 1,000 prospects costs $4.05, and the 17-domain test above cost about $0.12. A deep dive on five competitors adds $0.50. You can set a maximum cost per run, and the Actor won't fetch beyond it.
The cleaning and scoring code above works with any data source. Even if you get metrics elsewhere, normalize first and filter on referring domains and spam, not on rank alone.
Top comments (0)