Blog

Tutorial

Python Backlink Checker: Save a Reviewable Backlink Sample

Build a Python backlink checker with the RankParse SDK, preserve source data and dates, and prepare a bounded CSV sample for analysis in Claude or ChatGPT.

By Abhi Bavishi, Updated , 7 min read

A Python backlink checker should leave you with evidence you can review: source pages, target URLs, observation dates, and a record of what you requested. Printing a link count is not enough to decide which links matter or which publications deserve further research.

This tutorial creates a bounded backlink sample with the RankParse Python SDK, saves the original response, and writes a CSV plus a provenance file. You can inspect the CSV yourself or give it to Claude or ChatGPT for a first-pass review. The sample does not represent every backlink on the web, and an assistant's interpretation still needs page-level checks.

Decide what you will do with the sample

Choose one target domain and one question. “Which source pages in this sample might be relevant to our migration guide?” is a useful first task. “Tell me whether our backlink profile is healthy” asks for a conclusion that a limited sample cannot establish.

The example requests URL-level rows sorted by recency within the stored dataset. That makes it suitable for inspecting individual linking pages. It is not a live monitor: RankParse's public backlink data comes from a periodic snapshot; check the active dataset date, and requesting recent rows does not mean the links were discovered today.

Use Python 3.9 or later, install the rankparse package with pip install rankparse, and set RANKPARSE_API_KEY securely in your environment. The Python SDK documentation covers installation and client configuration. Keep the key out of source control and files you upload to an assistant.

Save the response, a CSV, and the request context

Replace example.com with a bare domain you intend to research. Use a new output directory for each run so earlier evidence stays intact.

This example is checked against the SDK method signature and documented response fields. It is not a reported live API run; no sample counts or findings below are measured results.

import csv
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from rankparse import RankParseClient

DOMAIN = "example.com"  # Replace with your target; no scheme or path.
LIMIT = 100
started_at = datetime.now(timezone.utc)

with RankParseClient(api_key=os.environ["RANKPARSE_API_KEY"]) as client:
    result = client.backlinks(DOMAIN, limit=LIMIT, sort="recent")

retrieved_at = datetime.now(timezone.utc).isoformat()
rows = result["data"]
if not isinstance(rows, list) or any(not isinstance(row, dict) for row in rows):
    raise ValueError("Expected a list of backlink objects")

output = Path("backlink-runs") / started_at.strftime("%Y%m%dT%H%M%S%fZ")
output.mkdir(parents=True, exist_ok=False)
(output / "response.json").write_text(
    json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8"
)

fields = [
    "from_domain", "from_url", "to_url", "anchor_text", "rel",
    "link_type", "crawled_at", "domain_host_count",
]

# Escape text that spreadsheet software could interpret as a formula.
# The JSON response retains the original values and nulls.
def csv_value(value):
    if value is None:
        return ""
    if isinstance(value, str) and value.lstrip().startswith(("=", "+", "-", "@")):
        return "'" + value
    return value

with (output / "backlinks.csv").open("w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(file, fieldnames=fields)
    writer.writeheader()
    for row in rows:
        writer.writerow({field: csv_value(row.get(field)) for field in fields})

provenance = {
    "source": "RankParse",
    "domain_requested": DOMAIN,
    "request_started_at_utc": started_at.isoformat(),
    "retrieved_at_utc": retrieved_at,
    "limit_requested": LIMIT,
    "sort": "recent",
    "rows_returned": len(rows),
    "response_total": result.get("total"),
    "crawl_release": result.get("crawl_release"),
    "aggregate_rows": sum(row.get("link_type") == "aggregate" for row in rows),
    "coverage_note": "Bounded sample; absence is not proof of no link.",
}
(output / "provenance.json").write_text(
    json.dumps(provenance, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(f"Saved {len(rows)} rows to {output}")

The script writes headers even when data is empty. It does not index the first row to guess the schema, so an empty successful response still produces a reviewable artifact. Unexpected fields remain in response.json; the CSV is a selected view of that original response.

Blank CSV cells can mean null or missing values. Use the JSON when that distinction matters. The original response also contains account-related fields, so review it before sharing; the CSV and provenance file are usually the more appropriate analysis inputs.

If the API request raises an exception, the script stops before writing a successful result. Do not catch every exception and substitute an empty list: that would make a failed request look like a domain with no returned data. Production retry and authentication behavior belongs in the SDK error-handling documentation.

Read the fields without overstating them

FieldUseful interpretationWhat it does not establish
from_urlCandidate linking page to inspectThat the page is accessible or still links today
to_urlDestination recorded in the datasetThe destination's current status or canonical URL
anchor_textRecorded link text when availableA universal measure of link quality
relStored link attribute when suppliedMissing values do not prove a specific attribute
crawled_atRecorded observation dateOriginal publication date or current live status
link_typeRecorded kind of link or aggregate rowAll rows are not necessarily editorial hyperlinks

For this endpoint, the backlinks documentation defines total as the number of returned rows, not the site's complete link count. Use len(rows) for the saved sample size. The endpoint returns up to 1,000 rows and does not provide offset pagination to enumerate the entire index.

The default importance sort can return aggregate referring-domain rows on some serving paths. The example explicitly uses sort="recent" for documented URL-level results, and records any aggregate rows defensively. If you encounter aggregates, do not treat a domain homepage in from_url as the specific page containing the link.

crawl_release is not normally included in a backlinks response. The provenance file leaves it null when absent. Do not fill that field with today's date or copy a release identifier from an old article. Retrieval time and observation time answer different questions.

Validate a small selection before expanding the task

First confirm that the CSV's data-row count matches rows_returned and the JSON list length. Then inspect a small, varied selection: several referring domains, different target paths, missing anchors, and older observations. A spot check can reveal interpretation problems; it cannot prove full dataset accuracy.

In an illustrative review, you might find these three cases:

ObservationReview decision
A topical tutorial still links to the intended guideKeep as a relevant example with the inspected URL and date
A source page now redirects and no link is visibleMark current link status unconfirmed; inspect the destination
Several URLs repeat the same publisher's footer linkGroup for publisher review; retain all raw rows

Do not turn a failed page load into a “lost link” claim. A transient error, access restriction, or redirect can prevent verification. Similarly, an empty sample means the request returned no rows for that domain and scope; it does not prove the site has no backlinks.

When comparing domains, use the same request settings and record each sample's size. Equal limits do not create equal coverage. A recent sample of 100 rows is not a random sample, so percentages calculated within it should be labeled sample percentages.

Give the assistant evidence and a stopping rule

Upload the CSV and provenance file to a Claude or ChatGPT workspace that supports file analysis and is approved for your data. Remove sensitive URLs or unnecessary identifiers first. This prompt turns the file into a review queue:

Try this prompt

Give the assistant evidence and a stopping rule

Analyze this RankParse backlink sample with its provenance file. Confirm the row count and missing fields before drawing conclusions. Treat retrieval time separately from crawl dates. This is a bounded sample, not the complete backlink profile. Our goal is to find source pages worth inspecting for relevance to our migration guide. Return source URL, target URL, evidence available in the file, uncertainty, and the next manual check. Do not call links toxic, infer penalties, or equate authority with editorial quality. Group repeated publishers without deleting their supporting rows. If the file lacks page context, mark relevance as a hypothesis. Do not invent page contents. If you cannot inspect every row, state the subset examined and stop before presenting whole-file totals.

Replace placeholders and attach the inputs described above. Paste the prompt if your assistant opens an empty chat.

Need the tools? Add RankParse to Claude · ChatGPT setup

Check the cited rows against the original response and visit the promising source pages. An assistant can organize evidence, but a list of links does not reveal the publisher's editorial intent or guarantee a viable outreach opportunity.

What to build next

Use the backlink quality workflow to assess the pages you inspect. For an agent-centered process that turns a sample into a reviewable audit, see auditing backlinks with Claude.

If your next question concerns distinct linking sites, review the referring-domains endpoint, keeping its domain-level rows separate from this URL-level sample. Extend the script only after the output helps you make a concrete decision; collecting more rows alone does not complete an audit.

Start free

Let your agent take the first pass.

100 free credits. No card. No seats.