Paginering van een URL backlink checker zonder hiaten of oneindige lussen
Rijen per call, pagina's per query en een harde limiet van 50.000. Hier is een loop die aan alle drie voldoet en aangeeft wanneer je moet stoppen.

Per call krijg je maximaal 1.000 rijen terug. Elke call wordt één keer gefactureerd, ongeacht het aantal rijen. Diepe paginering is beperkt tot 50.000 rijen, wat neerkomt op 50 calls van elk 1.000 rijen.
Waarom sorteren voor paginering komt
Paginering over een ongeordende lijst is de oorzaak van duplicaten. Stuur altijd sort en order samen met page en per, zodat pagina 3 bij elke request in de run hetzelfde betekent.
Een refdomains-pull kost 2 credits per call, dus 50 calls komen neer op 100 credits, oftewel één cent. Zelfs het ergste geval is goedkoop; de echte kosten zitten in een loop die niet stopt.
De sorteerveld kiezen
Welk veld je sorteert hangt af van de vraag. 'Grootste eerst' is de gebruikelijke keuze voor audits, omdat het plafond dan de lange staaf afsnijdt in plaats van het deel waar je naar kijkt.
Sorteer op een veld dat niets met je vraag te maken heeft, en het plafond kan de rijen verwijderen die je wilde hebben. Haal eerst tien rijen op per kandidaat-sortering en kijk welke slice het plafond overleeft.
Schrijf de keuze in het rapport, zodat de volgende persoon weet waarom het plafond daar viel waar het deed.
Records die verplaatsen tijdens een run
De data verplaatst niet tijdens een run, tenzij de maandelijkse rebuild midden in de run plaatsvindt. Zeldzaam, maar reëel. Een nightly job die over de eerste van de maand valt, kan de ene pagina uit de oude index en de volgende uit de nieuwe index retourneren. Log dus de starttijd en vergelijk.
Schrijf de starttijd bovenaan de log en het paginanummer op elke regel. Dan wijst een mismatch in de output op timing, niet op een parsing bug.
Als de job gepland is, vermijd dan een starttijd vlak voor een waarschijnlijke rebuild. Je logs tonen een rebuild als een verschuiving in totalen tussen twee runs die verder overeenkomen, dus houd de totalen in elke logregel bij.
Een run die een rebuild overspant is niet standaard fout. Het is alleen inconsistent, dus markeer die runs in de log en voer de getroffen slice opnieuw uit als het belangrijk is voor een rapport.
Een pagineringloop die stopt
TypeScript voor een Node 20 worker. Het stopt bij de eerste korte pagina, bij het plafond, of bij elke ok die niet waar is.
const BASE = "https://seomasters.dev/api.php";
const CAP = 50_000;
const PER = 1000;
export async function pullAll(domain: string, key: string) {
const rows: unknown[] = [];
for (let page = 1; rows.length < CAP; page++) {
const u = new URL(BASE);
u.search = new URLSearchParams({
action: "refdomains", q: domain,
page: String(page), per: String(PER),
sort: "dr", order: "desc",
}).toString();
const res = await fetch(u, { headers: { Authorization: `Bearer ${key}` } });
if (res.status === 401 || res.status === 402) throw new Error(`stop: HTTP ${res.status}`);
const body = await res.json();
if (!body.ok) throw new Error(JSON.stringify(body).slice(0, 300));
const chunk = findRows(body); // see note below
rows.push(...chunk);
if (chunk.length < PER) break; // short page means we are done
}
return rows;
}Probleemoplossen bij een paginering run
| Wat je ziet | Waarschijnlijke oorzaak | Wat je moet doen |
|---|---|---|
| Pagina 51 komt leeg terug of geeft een fout | Je hebt het plafond van 50.000 bereikt | Verfijn met een andere actie of een andere sort, en merge vervolgens op id |
| Zelfde domein op twee pagina's | Ongeordende query, of de index herbouwd tijdens de run | Stuur sort en order; dedupeer op de domein sleutel |
| Minder rijen dan het dashboard totaal | Het totaal geldt voor de hele graph, de pull is beperkt | Toon "top 50.000" in de UI |
| Loop eindigt nooit | Voorwaarde controleert alleen rows.length | Breek bij een korte pagina, zoals hierboven |
| Rekening hoger dan verwacht | Opnieuw proberen bij elke niet-200 | Alleen opnieuw proberen bij netwerkfouten, zie errors |

Rijen lezen zonder te raden naar veldnamen
De findRows helper hierboven is bewust open gelaten. Doe één echte call, kijk naar de JSON en schrijf de accessor op basis van wat je ziet, in plaats van wat een tutorial beloofde.
Voor de goedkopere neef van deze taak, het scoren van veel domeinen tegelijk zonder enige paginering, lees de bulk DR script.
Eén response bevriezen als fixture
Vries het vervolgens in met een test. Sla één response op als een fixture bestand en asserteer op het aantal rijen en de sleutels van de eerste rij, zodat een toekomstige wijziging faalt in CI en niet in de grafiek van een klant.
Houd de fixture in versiebeheer naast de test, zodat een reviewer de vorm ziet die de code verwacht zonder iets uit te voeren.
Wanneer de API later een veld toevoegt, vertelt de fixture diff je of je accessor het heeft opgemerkt of het stil heeft genegeerd.
Noem de fixture naar de query waaruit deze kwam, zoals de actie, het domein en de sortering. Een bestand genaamd response1.json vertelt de volgende lezer niets over wat het bewijst.
Tel het aantal opgeslagen rijen
Vergelijk de rijen die je hebt opgeslagen met het totaal dat de loop rapporteerde. Een gat betekent dat een pagina is overgeslagen, en een herhaling betekent dat een pagina twee keer is opgehaald en in één rij is geüpdatet.
Voer deze controle uit na elke testpull, niet alleen na de eerste. Afwijkingen in het aantal zijn meestal het eerste symptoom van een gewijzigde sortering of een rebuild midden in de run.
Houd het aantal in dezelfde logregel als het paginanummer, zodat de twee in één oogopslag samen gelezen kunnen worden.
Werk verdelen boven het plafond
Sommige vragen worden te groot voor het plafond. Verdeel het probleem in plaats van harder te pagineren. Haal de top 50.000 op met één sortering, en stel andere vragen aan de heavy hitters, omdat die ook 2 credits kosten en andere vragen beantwoorden. Harder pagineren komt nooit voorbij het plafond.
Haal de top 50.000 op met één sortering
Neem het plafond als een feit van de API. Haal de top 50.000 rijen op met één sortering, sla ze op en label het resultaat als de top slice in elk rapport.
Een dashboard dat een beperkt aantal toont alsof het het volledige totaal is, misleidt de mensen die het lezen. Het label kost niets, en een url backlink checker rapport leest eerlijker met zo'n label.
Bepaal de slice grootte voorafgaand aan de run en schrijf deze in de rapportkop, zodat het nummer nooit groter lijkt dan het is. Een kopregel kost niets, en een lezer die later een missende rij vindt, zal je daar dankbaar voor zijn.
Stel de heavy hitters een andere vraag
anchors en toppages beantwoorden andere vragen, zoals welk anchor text domineert of welke pagina's links aantrekken. Voor een domein dat het plafond heeft overschreden, kunnen die antwoorden belangrijker zijn dan de volgende pagina van dezelfde lijst.
Houd hun resultaten in hun eigen tabellen, gesleuteld op het domein, zodat een latere pull van hetzelfde domein ze kan vervangen zonder de top-slice rijen aan te tasten.
Elke extra actie kost zijn eigen credits, dus prijs het voorafgaand aan de run, niet nadat de rekening is aangekomen.
Een korte lijst van de zwaarste domeinen, één keer per maand opgehaald, geeft vaak nuttiger context dan een langere lijst die elke nacht wordt opgehaald.
Merge op de domein sleutel
Wanneer twee pulls overlappen, merge ze dan op de domein sleutel en houd de nieuwere timestamp aan. De merge hangt dan nooit af van welke pull eerst klaar was.
Log welke pull elke rij heeft gewonnen. Als een nummer tussen twee rapporten verandert, vertelt die log je of de index of de merge is veranderd.
Houd de merge in één functie met een test, omdat dit de enige plek is waar twee pulls elkaar ontmoeten.
Hervatten na een crash
Crashes gebeuren. Een deploy herstart de worker, een laptop gaat in slaapstand, een VPN valt weg en de loop sterft op pagina 31 van 50.
Sla de pagina op na elke call
Persist de laatste voltooide paginanummer na elke call. Herstart vanaf dat nummer plus één, niet vanaf het begin, en je betaalt voor 2 credits per pagina in plaats van de hele pull opnieuw te kopen.
Sla het op nadat de response is geparsed, niet ervoor. Een pagina die je niet hebt kunnen lezen, moet opnieuw worden opgehaald, en een opgeslagen paginanummer moet altijd betekenen dat je rijen hebt.
Een save die één keer per minuut gebeurt in plaats van per call laat een window achter waarin een crash credit kosten kan verdubbelen. Houd de write klein, zodat het goedkoop is om elke keer uit te voeren.
Log ook hoe lang elke save duurt, evenals hoe lang elke call duurt. Een save die concurrert met de call wijst op de storage layer, en dat is het waard om op te lossen voordat de loop groeit.
Upsert op de domein sleutel
Houd een idempotente write. Upsert elke rij op zijn domein sleutel, zodat een pagina die twee keer is opgehaald één rij achterlaat, niet twee.
Gebruik dezelfde sleutel op elke write path, inclusief het handmatige repair script. Twee write paths met twee sleutels zijn hoe duplicaten overleven bij een resume.
Een repair script wordt vaak in alle haast geschreven, na een mislukte run. Dat is wanneer een tweede sleutel insluipert, dus review het net zo zorgvuldig als de main loop.
Weiger een resume wanneer de sortering is veranderd
Log de sortering en order samen met het paginanummer. Als iemand sort verandert tussen een crash en de herstart, betekent het opgeslagen paginanummer niet meer dezelfde slice. Weiger het resume en begin opnieuw.
Sla een klein resume record op met de sortering, de order, de pagina en de tijd. De loop leest dat record eerst, en weigert door te gaan als een van de velden niet meer overeenkomt met de request die hij gaat doen.
Een afgewezen resume is geen fout. Het is de loop die zegt dat de opgeslagen pagina niet meer dezelfde rijen betekent, en opnieuw beginnen is de goedkopere fout.
De loop in productie draaien
- 1
Begin met een klein domein
Draai de loop tegen een domein met een paar honderd rijen, zodat de stop bij een korte pagina activeert en je het exit pad kunt bekijken.
- 2
Controleer één pagina met het blote oog
Open één opgeslagen response en bevestig dat de accessor de rijen nog steeds leest. Vertrouw niet op een parse die je niet hebt bekeken.
- 3
Houd de limieten in config
Zet het plafond en de paginagrootte in één config bestand, zodat een wijziging zichtbaar is in review en niet verborgen blijft binnen een functie.
- 4
Log de reden waarom de loop eindigde
Noteer of de run eindigde op een korte pagina, bij het plafond of door een fout. Die drie eindpunten betekenen verschillende dingen voor degene die de log leest.