Rens en CSV-fil fra CRM'et

Vask tusindvis af kundeadresser fra en CSV-eksport, enten med et bulk-job på serveren eller med din egen løkke over datavask.

4 min Niveau: Øvet ✓ Kode testet

Adresser i et CRM forfalder: kunder flytter, veje bliver omdøbt, og halvdelen blev tastet ind i hast. Før en kampagne eller en migrering vil du vide, hvilke rækker der er gode, hvilke der kan rettes automatisk, og hvilke et menneske skal kigge på.

Der er to veje. POST /api/v1/bulk/cleanse tager hele filen og kører datavask på hver række på serveren. En løkke over GET /datavask/adresser i din egen kode giver dig fuld kontrol over hvert kald. Begge giver kategorierne A, B og C. Afsnittet Hvilken skal du vælge? sammenligner dem.

Bulk-jobbet

Krav til filen

  • Filnavnet skal ende på .csv, ellers svarer API'et 400 med koden csv_required.
  • Højst 50 MB. En større fil giver 413 med koden file_too_large.
  • Semikolon, komma eller tabulator som skilletegn. Jobbet finder selv skilletegnet og bruger det samme i resultatfilen. Et BOM-tegn fra Excel fjernes, og gamle Excel-filer i Windows-1252 læses også.
  • Første linje er kolonnenavne, uden forskel på store og små bogstaver. Standardnavnene er vejnavn, husnr, etage, dør (eller doer), postnr og postnrnavn. Har du hele adressen i én kolonne, så kald den betegnelse. Hedder dine kolonner noget andet, så send navnene i formfelterne vejnavn_col, husnr_col, etage_col, doer_col, postnr_col, postnrnavn_col og betegnelse_col.

Send filen

Shell#bulk-upload
curl -s "https://api.danadresse.dk/api/v1/bulk/cleanse" \
  -H "X-Api-Key: $DANADRESSE_API_KEY" \
  -F "file=@kunder.csv" \
  -F "vejnavn_col=Gade" \
  -F "husnr_col=Nr" \
  -F "postnr_col=Postnummer"

Svaret kommer med det samme, før rækkerne er behandlet:

JSON#bulk-svar
{
  "job_id": "3f9c2a7e5b0d4c1e9a8f6b2d7c4e1a05",
  "status": "queued",
  "status_url": "/api/v1/bulk/jobs/3f9c2a7e5b0d4c1e9a8f6b2d7c4e1a05",
  "download_url": "/api/v1/bulk/jobs/3f9c2a7e5b0d4c1e9a8f6b2d7c4e1a05/download"
}

Følg jobbet og hent resultatet

GET /api/v1/bulk/jobs/{job_id} returnerer status (queued, processing, completed eller failed), processed og error. processed opdateres for hver 100 rækker. total er antallet af rækker i filen og er kendt, så snart jobbet starter, så du kan vise en rigtig fremdrift. GET /api/v1/bulk/jobs viser dine seneste jobs.

Hent filen fra download_url, når status er completed. Spørger du for tidligt, får du 425 med koden not_ready. Jobbet huskes i én time efter, det er færdigt. Derefter svarer status og download 404 med job_not_found, så hent filen med det samme.

Python#bulk-python
import os
import time
import requests

API = "https://api.danadresse.dk"
H = {"X-Api-Key": os.environ["DANADRESSE_API_KEY"]}

with open("kunder.csv", "rb") as f:
    r = requests.post(
        f"{API}/api/v1/bulk/cleanse",
        headers=H,
        files={"file": ("kunder.csv", f, "text/csv")},
        data={"vejnavn_col": "vejnavn", "husnr_col": "husnr", "postnr_col": "postnr"},
        timeout=120,
    )
r.raise_for_status()
job = r.json()

while True:
    s = requests.get(API + job["status_url"], headers=H, timeout=10).json()
    print(s["status"], s["processed"])
    if s["status"] == "completed":
        break
    if s["status"] == "failed":
        raise SystemExit(s["error"])
    time.sleep(5)

out = requests.get(API + job["download_url"], headers=H, timeout=120)
out.raise_for_status()
with open("kunder_renset.csv", "wb") as f:
    f.write(out.content)

Hvad står der i resultatet?

Filen har dine egne kolonner plus syv nye:

KolonneIndhold
_match_kategoriA, B eller C fra datavask, eller ERR ved fejl i rækken
_match_scoreAfledt af afstanden: 100 - 10 × afstand, og 0 uden match
_match_idAdresse-id for det bedste match
_match_betegnelseDet bedste match som én linje, f.eks. Strandvejen 100, 2900 Hellerup
_match_adgangsadresseidAdgangsadresse-id for det bedste match
_match_afstandDatavaskens afstand. 0 er præcist
_match_forskelleDatavaskens forskelle som JSON, f.eks. {"vejnavn":2,"husnr":0,"postnr":0,"postnrnavn":0}

Bulk-jobbet kører den samme datavask som GET /datavask/adresser på hver række, så kategorierne betyder det samme: A er præcist, B er ét sikkert match med små stavefejl, og C er usikkert. Etage og dør tæller med. Ved C står det bedste bud i _match_*-kolonnerne, men lad en person afgøre rækken.

Din egen løkke over datavask

Vil du styre hvert kald selv, f.eks. for at vise alle C-kandidaterne for en bruger, så kald GET /datavask/adresser pr. række. Datavask tager én adresse pr. kald.

Hold øje med minutgrænsen. Hvert svar har X-RateLimit-Minute-Remaining. Når den rammer 0, så vent til X-RateLimit-Minute-Reset. Får du alligevel 429, så vent det antal sekunder, Retry-After siger. Se Grænser, kvoter og 429.

Eksemplet læser tre rækker fra en tekststreng, så du kan køre det direkte. Udskift io.StringIO(DATA) med open("kunder.csv", encoding="utf-8-sig") for en rigtig fil:

Python#loop-python
import csv
import io
import os
import sys
import time
import requests

DATA = """kundenr,vejnavn,husnr,etage,dor,postnr
1001,Strandvejen,100,1,,2900
1002,Strandvej,100,,,2900
1003,Rådhuspladsen,1,,,1550
"""

s = requests.Session()
s.headers["X-Api-Key"] = os.environ["DANADRESSE_API_KEY"]

def vask(row):
    params = {k: row[k] for k in ("vejnavn", "husnr", "etage", "postnr") if row[k]}
    if row["dor"]:
        params["dør"] = row["dor"]
    while True:
        r = s.get("https://api.danadresse.dk/datavask/adresser", params=params, timeout=10)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "5")))
            continue
        r.raise_for_status()
        if r.headers.get("X-RateLimit-Minute-Remaining") == "0":
            reset = int(r.headers.get("X-RateLimit-Minute-Reset", "0"))
            time.sleep(max(1, reset - int(time.time())))
        return r.json()

reader = csv.DictReader(io.StringIO(DATA))
cols = reader.fieldnames + ["_kategori", "_adresse_id", "_adgangsadresse_id", "_vejnavn"]
out = csv.DictWriter(sys.stdout, fieldnames=cols)
out.writeheader()
for row in reader:
    svar = vask(row)
    top = svar["resultater"][0]["adresse"] if svar["resultater"] else {}
    row.update({
        "_kategori": svar["kategori"],
        "_adresse_id": top.get("id", ""),
        "_adgangsadresse_id": top.get("adgangsadresseid", ""),
        "_vejnavn": top.get("vejnavn", ""),
    })
    out.writerow(row)

Brug reglerne fra Valider adressen i checkout: A og B kan du gemme med den officielle stavemåde, C skal en person se på. Ved C har resultater flere kandidater, og den første er kun det bedste bud.

Hvilken skal du vælge?

Bulk-jobEgen løkke over datavask
OpsætningÉt upload, ét downloadKode, retry og throttling
KategorierA, B og CA, B og C
Etage og dørBrugesBruges
Id'erAdresse-id og adgangsadresse-idAdresse-id og adgangsadresse-id
C-kandidaterKun det bedste budAlle kandidater
MinutgrænseRammer dig ikke pr. rækkeDu skal selv vente
Store filerOp til 50 MB pr. filIngen grænse, men tager tid

En god arbejdsgang er at køre bulk-jobbet på hele filen og derefter sende rækkerne med C gennem datavask-løkken, så en person kan vælge mellem kandidaterne.

Gode vaner

  • Overskriv aldrig kildedata direkte. Skriv resultatet til nye kolonner, og lad en person godkende C-rækkerne.
  • Gem adresse-id'et og adgangsadresse-id'et i CRM'et. Næste gang kan du validere med id'et i stedet for teksten. Se Gem adresse-id'et.
  • Brug en live-nøgle til store kørsler. Testnøgler har gratisplanens grænser pr. IP-adresse.

Næste skridt

Relaterede guides

Markdown-kilde Fejl i guiden? Skriv til os API-reference (Swagger)

Prøv det med en gratis nøgle

2 000 kald/måned på gratisplanen. Intet kreditkort.

Opret konto →