Naar de inhoud springen

Backlink-data als JSON: $100 gratis tegoed, geen creditcard, aanmelden met Google.

SEOMasters.dev
SEOMasters.devAanmelden

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.

Oneindige stenen brugbogen die een mistige rivier overspannen zonder hiaten
De drie getallen die je moet onthouden

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.

ts
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 zietWaarschijnlijke oorzaakWat je moet doen
Pagina 51 komt leeg terug of geeft een foutJe hebt het plafond van 50.000 bereiktVerfijn met een andere actie of een andere sort, en merge vervolgens op id
Zelfde domein op twee pagina'sOngeordende query, of de index herbouwd tijdens de runStuur sort en order; dedupeer op de domein sleutel
Minder rijen dan het dashboard totaalHet totaal geldt voor de hele graph, de pull is beperktToon "top 50.000" in de UI
Loop eindigt nooitVoorwaarde controleert alleen rows.lengthBreek bij een korte pagina, zoals hierboven
Rekening hoger dan verwachtOpnieuw proberen bij elke niet-200Alleen opnieuw proberen bij netwerkfouten, zie errors
Tuinman inspecteert unieke zaailingen in een zonnige kas

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. 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. 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. 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. 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.