 ##  [Den officielle vej tager dage. Opgaven skal løses i dag.](/index.php/da/node/194) 

    *Indsendt af Lennart den tor, 16 apr 2026 - 15:56*  

  ![Den officielle vej tager dage. Opgaven skal løses i dag.](/sites/default/files/styles/wide/public/2026-04/composite_4.png.webp?itok=Rfuku_fj)

 

I onsdags havde jeg brug for noget banalt: en liste over bestyrelsesmedlemmer i en række danske selskaber. Ikke til en database. Ikke til et produkt. Bare til en eftermiddags lead-research — hvem sidder hvor, og hvilke formænd overlapper med folk jeg allerede taler med.

Den slags data er offentlig. Den ligger på `datacvr.virk.dk`. Hver eneste virksomhed i Danmark har en side. Det burde tage tyve minutter.

Det tog det ikke. Og det er historien værd, fordi det illustrerer en helt almindelig spænding i praktisk arbejde med data og AI: **den officielle vej er ofte den rigtige — men hvad gør du imens den bliver sat op?**

## De officielle veje

For programmatisk adgang til CVR-data er der to kanoniske veje:

1. **Datafordeler.dk** — statens egen distributionskanal. Komplet, gratis, vedligeholdt af Erhvervsstyrelsen. Kræver MitID Erhverv eller email-baseret opsætning med IP-whitelisting. Realistisk tidsramme: 1-3 hverdage til godkendelse plus en aften til selve integrationen.
2. **Cvrapi.dk premium** — kommerciel API ovenpå de samme data. Premium-token kræver email-kontakt. Også flere dage.

Begge er rigtige valg på lang sigt. Ingen af dem løser min opgave i dag.

## Den åbne side er der allerede

Imens jeg ventede, åbnede jeg en CVR-side i browseren manuelt og kiggede. Bestyrelsen står der. Direktionen står der. Formanden er markeret. Hver person er et klikbart link til deres egen side, hvor alle deres bestyrelseshverv er listet.

Alle data jeg har brug for ligger i HTML'en. Den eneste forhindring er Cloudflare's bot-beskyttelse, der returnerer 403 til alt der lugter af script.

`curl`: blokeret. `http get` i nushell: blokeret. WebFetch fra mit AI-værktøj: blokeret.

Det er rimeligt fra virk.dk's side — de vil ikke have automatiseret trafik der presser deres servere. Men det betyder også at en helt almindelig opgave skal løses gennem en rigtig browser.

## Plugin'en der gjorde forskellen

[`nu_plugin_browse`](https://github.com/tyarel8/nu_plugin_browse) er en lille nushell-plugin skrevet af Tyarel8. Den eksponerer én ny kommando: `http browse`. Bag scenen åbner den Chrome (headless eller synlig), venter et antal sekunder, og returnerer den fuldt rendrede HTML.

Den interessante flag er `--with-head`. Det åbner et synligt Chrome-vindue i stedet for et headless. Cloudflare's challenge-system genkender headless browsere som mistænkelige, men slipper rigtige browser-vinduer igennem.

```
let html = (http browse --with-head --wait 12sec
  "https://datacvr.virk.dk/enhed/virksomhed/47458714")

```

12 sekunders vindue åbner sig. Cloudflare-challengen kører, JavaScript-applikationen rendrer, og jeg får 422KB HTML retur. Inden i den ligger Lego's komplette personkreds.

## Fra HTML til struktureret tabel

HTML er ikke et godt format at arbejde med. Det skal forvandles til noget jeg kan filtrere, sortere og krydsreferere. Her kommer nushell's egen styrke ind: `parse --regex` med navngivne grupper, og en lille state machine i en `for`-løkke.

Idéen er at finde tre slags "events" i dokumentrækkefølge:

- en kategori-overskrift (Direktion, Bestyrelse, Tilsynsråd)
- en funktion i parentes (Formand, Næstformand, Direktør)
- et personlink med navn

Når man har fanget dem, vandrer man linært gennem listen og holder styr på den aktuelle kategori og en pending funktion. Hver gang en person dukker op, emitter man en række med kategori + funktion + navn.

Resultatet er en almindelig nushell-tabel:

kategorifunktionnavnDirektionDirektørJesper AndersenBestyrelseFormandCarsten RasmussenBestyrelseNæstformandPoul Hartvig NielsenBestyrelseTrine ChristensenBestyrelseSuppleantSidsel Numan AndersenSom kan sorteres, filtreres, joines med andre tabeller og pipes videre til hvad som helst. Total tid fra opgave til virkende værktøj: cirka halvanden time, inklusive at finde plugin'en.

## Hvad jeg ikke siger

Jeg siger ikke at scraping er bedre end den officielle API. Det er det ikke. Det her løsning har klare svagheder:

- Den åbner et synligt browservindue per opslag — ikke godt til batch
- Den er langsom (10-15 sek per virksomhed)
- Den er skrøbelig — hvis virk.dk ændrer sit HTML-layout, knækker parseren
- Den må respektere virk.dk's ressourcer — det er ikke et værktøj til at hente alle danske bestyrelser om natten

Datafordeler-opsætningen kører parallelt. Når den er klar, skifter jeg backend ud. Selve interfacet — `cvr virk 47458714` — kan blive det samme.

## Det dybere princip

Det her er et godt eksempel på det jeg kalder *compound systems*: et arbejdsflow består af flere komponenter, og det rigtige valg per komponent afhænger af *hvad du har brug for lige nu*, ikke hvad der er det officielt rigtige svar i sin generelle form.

Komponenterne i mit lille værktøj er:

- en headless browser (Chrome) til at tale med en beskyttet hjemmeside
- en plugin (nu\_plugin\_browse) der gør det tilgængeligt fra shellet
- nushell selv til at orkestrere, parse og strukturere
- en lille regex-baseret state machine til at omsætte HTML til tabel
- en pipe-arkitektur så outputtet kan bruges sammen med alt andet i mit værktøjsbælte

Ingen af komponenterne er smarte hver for sig. Sammen gør de en datakilde der ellers var praktisk utilgængelig til en almindelig nushell-tabel.

Det er præcis samme arkitektoniske mønster som compound AI systems: små, deterministiske, velafgrænsede dele der hver for sig løser en del af problemet, og som tilsammen bliver til noget brugbart. Generativ AI er én komponent man kan smide ind i den slags, men ikke nødvendigvis hele svaret.

## Hvad det betyder for SMV'er

Næste gang dit team står med en data-opgave hvor "vi venter på adgang fra IT/leverandøren/myndigheden", spørg dig selv:

- Findes data offentligt eller halvoffentligt et sted hvor du kan komme til dem nu?
- Er der en pragmatisk vej der leverer 80% af værdien i 5% af tiden, mens den officielle vej bliver sat op?
- Hvad er prisen ved at vente versus ved at have en midlertidig løsning der knækker om tre måneder?

Svaret er ikke altid scraping. Det kan være et regneark. Det kan være en CSV-fil sendt manuelt en gang om ugen. Det kan være at åbne 20 hjemmesider i hånden første gang og automatisere det andet gang.

Men hvis svaret altid bliver "vi venter på den officielle integration", er der ofte et halvt års arbejde der bare ikke bliver lavet. Den slags omkostning er svær at se i et regneark — men den er reel.

## Til slut

Tak til Tyarel8 for [nu\_plugin\_browse](https://github.com/tyarel8/nu_plugin_browse). Det er en lille plugin med en stor effekt — præcis den slags værktøj der gør forskellen mellem "vi venter på adgang" og "vi har et virkende prototype-værktøj klar".

Hele løsningen ligger som et nushell-modul i min CVR-værktøjskasse. Når datafordeler-credentials lander, skifter jeg backend ud. Indtil da: `cvr virk 47458714` returnerer Lego's bestyrelse på 12 sekunder. Det er nok til at jeg kan komme videre med det jeg egentlig skulle lave.