Blog

Tutorial

Enrich a Domain CSV with SEO Data in Python Without Losing Unknowns

Add RankParse authority, referring-domain, popularity and registration fields to a CSV while preserving missing data and keeping business qualification apart.

By Abhi Bavishi, Updated , 6 min read

A CSV of company domains can benefit from SEO context: how many referring domains appear in a snapshot, whether a popularity rank is available, and what registration data can be retrieved. Those fields do not tell you whether a company has budget, fits your offer or deserves an email.

This tutorial adds RankParse's domain data to a CSV while preserving missing values and the original input. The outcome is an enriched research file, not an automatically qualified lead list. The code is an illustrative implementation, not a claimed live run or a measured conversion improvement.

Keep enrichment separate from qualification

Start with the business criteria your team actually uses, such as relevant industry, geography, product fit or a documented need. Keep those columns in the input file. SEO signals can add context, but they should not replace the criteria.

A newly registered domain could represent a new business, a rebrand or a migration. A domain outside a popularity list may be a perfectly suitable specialist company. A high authority score does not establish that a website is active or that the company is a good customer.

If the list is for link research rather than sales, use the prospecting workflow to define qualification before sorting it.

What the endpoint can add

The domain-authority endpoint documents these fields:

FieldUseful interpretationInference to avoid
scoreRankParse's own authority metricMoz DA, company quality or buying intent
referring_domainsReferring domains represented by the source graphAn exhaustive current count of all links on the web
popularity_rankPosition in the supported Tranco list, when presentRevenue, organic traffic or proof of legitimacy
registered_atAvailable RDAP registration dateThe company's founding date
partialPart of the result degraded or is incompleteEvery field is equally reliable

The source data has different update schedules. Link signals use a quarterly snapshot; registration and popularity enrichment have their own caching or refresh behavior. Check the endpoint documentation when those differences matter to your analysis.

A null authority score is unknown. A zero can represent no data in the snapshot. Preserve both accurately. Neither should automatically trigger “reject this business.”

Prepare the input

Use a CSV with one domain column containing bare domains, such as example.com, and any existing business fields. Check duplicate domains, mixed subdomain scopes and pasted URLs before running the lookup.

Install the rankparse SDK and set RANKPARSE_API_KEY as described in the documentation. The script below makes sequential requests rather than introducing concurrency into a small research job. It writes to a new file and retains every original row, including rows with SDK or network request failures. Unexpected response formats, local file errors and process interruptions can still stop the run; keep the original input so you can resume safely.

import csv
import os
import time
from datetime import datetime, timezone
from rankparse import RankParseClient
from httpx import RequestError
from rankparse.errors import RankParseError, AuthError, InsufficientCreditsError

extra_fields = [
    "rp_authority", "rp_referring_domains", "rp_popularity_rank",
    "rp_registered_at", "rp_partial", "rp_status", "rp_retrieved_at",
]

with open("prospects.csv", newline="", encoding="utf-8") as source:
    reader = csv.DictReader(source)
    original_fields = list(reader.fieldnames or [])
    if "domain" not in original_fields:
        raise SystemExit("Input must contain a domain column")
    if set(original_fields) & set(extra_fields):
        raise SystemExit("Input already has enrichment columns; use the original file")
    rows = list(reader)

with RankParseClient(api_key=os.environ["RANKPARSE_API_KEY"]) as client:
    with open("prospects_enriched.csv", "w", newline="", encoding="utf-8") as target:
        writer = csv.DictWriter(target, fieldnames=original_fields + extra_fields)
        writer.writeheader()
        blocked = False
        for row in rows:
            output = dict(row)
            domain = row["domain"].strip().lower()
            output["rp_retrieved_at"] = datetime.now(timezone.utc).isoformat()
            if blocked:
                output["rp_status"] = "not_queried_account_check_required"
            elif not domain or "://" in domain or "/" in domain:
                output["rp_status"] = "check_input"
            else:
                try:
                    result = client.domain_authority(domain)
                    data = result.get("data") or {}
                    output.update({
                        "rp_authority": data.get("score"),
                        "rp_referring_domains": data.get("referring_domains"),
                        "rp_popularity_rank": data.get("popularity_rank"),
                        "rp_registered_at": data.get("registered_at"),
                        "rp_partial": result.get("partial", False),
                        "rp_status": "unknown" if data.get("score") is None else "returned",
                    })
                except (AuthError, InsufficientCreditsError):
                    output["rp_status"] = "account_check_required"
                    blocked = True
                except (RankParseError, RequestError):
                    output["rp_status"] = "lookup_failed"
                finally:
                    time.sleep(1.1)
            writer.writerow(output)

The pause reduces request frequency for this script. Other jobs sharing the same key can still cause rate limits. Review rate limiting and errors before scheduling larger runs or adding retries.

The example retains rows affected by SDK errors, including rate limits and not-found responses, or HTTP transport failures instead of converting those failures into zeroes. An account-level problem stops further lookups while preserving the remaining input rows. Inspect the status column and resolve failures before interpreting the file; a production workflow should also retain appropriate error details and resumable progress.

Read the result without turning absence into judgment

Consider these fictional output cases:

ResultWhat you knowWhat to do
Score returned, registration date blank, partial trueSome enrichment may be missingUse available fields cautiously; check the missing registration information if necessary
Score null or lookup failedAuthority was not establishedRetry or leave unknown; do not assign a low-quality label
Score zero and no referring domains returnedThe snapshot did not provide a positive link signalInvestigate coverage and the actual website if the company otherwise fits
High score, business fit unconfirmedThe provider returned a strong authority measurementComplete the same business-fit review as for any other company

CSV blanks preserve missing values for human review, but spreadsheet software may interpret blanks differently in formulas. Check filters and scoring rules before using the file. Keep the provider prefix in column names so a future export from Moz or Ahrefs cannot silently overwrite a different metric.

Add human or agent review after the export

Review a small mix of returned, unknown and low-score rows. Open the websites and compare the enrichment with your business criteria. If a rule rejects otherwise relevant companies, fix the rule before processing the rest of the list.

Claude or another connected client can help organize that review, provided you share only the fields needed for the task. With RankParse MCP available, ask for missing evidence rather than a verdict:

Review these domain-enrichment rows against the business criteria I provide. Separate business fit from SEO measurements. Keep unknown values unknown. For each unresolved row, identify the next useful check; do not infer revenue, legitimacy or buying intent from authority, popularity or domain age.

For a connector-supported workflow, follow the MCP documentation. A chat interface does not change the limits of the underlying data.

What to build next

If the enriched list supports a real decision, add only the next field needed to resolve an uncertainty. A backlink sample may help understand who references a website; it does not prove customer fit. Keep the original CSV, the enriched version and a note of the criteria used so another teammate can review your choices.

Start free

Let your agent take the first pass.

100 free credits. No card. No seats.