Regex æfing

Hingað til höfum við unnið með stutta texta sem passa í eina breytu. Raunveruleg gögn eru sjaldnast svona snyrtileg. Í þessum kafla sækjum við forsíðu mbl.is — eina stóra HTML-skrá — og drögum úr henni upplýsingar sem hvergi eru gefnar sem hrein gögn.

Þetta er ekki API. Það er enginn /v1/frettir endapunktur sem skilar snyrtilegu JSON-i. Það eina sem við höfum er HTML-kóðinn sjálfur, og regex er fljótlegasta leiðin til að ná tökum á honum.

Við sækjum forsíðuna beint. Hvenær það var gert, og hvers vegna talan hjá þér verður önnur en hér, kemur í ljós hér að neðan.

AthugasemdByrjun, ekki fullbúin lausn

Hér að neðan er byrjunin unnin fyrir þig: við sækjum síðuna og leysum fyrsta dæmið í bæði R og Python. Æfingarnar á eftir eru þínar að klára.

Kurteisi við vefþjóna

Áður en við sækjum nokkuð:

  • Lestu robots.txt. Hjá mbl.is er robots.txt sem bannar meðal annars /frimg/, /myndir/ og /go/, en leyfir forsíðuna og /frettir/.
  • Auðkenndu þig með User-Agent sem segir hver þú ert og hvernig má ná í þig.
  • Sæktu eina síðu í einu. Ekki setja for-lykkju yfir hundrað slóðir án tafar á milli.
  • Vistaðu svarið meðan þú þróar mynstrið — þá ertu ekki að hamra á þjóninum í hvert sinn sem þú lagfærir eitt +.
MikilvægtÞjónninn ræður, ekki þú

Þessi bók er þýdd sjálfvirkt á þjóni hjá GitHub. Þegar hún reyndi að sækja https://www.mbl.is/ þaðan svaraði mbl.is með 403 Forbidden — beiðninni var einfaldlega hafnað, þótt nákvæmlega sami kóði virki fínt úr fartölvu.

Það er ekkert að kóðanum. Taktu eftir að robots.txt leyfir forsíðuna — það er ekki bannið sem stoppar okkur.

mbl.is er á bak við Cloudflare, sem metur hverja beiðni og gefur henni einkunn eftir því hversu líkleg hún er til að koma frá manneskju. robots.txt segir hvað þú mátt; Cloudflare ræður hvað þú kemst. Það tvennt fer ekki alltaf saman.

Sami kóði, tvær niðurstöður — af hverju?

Fartölvan þín Þjónn hjá GitHub
IP-tala heimatenging eða skólanet gagnaver, þekkt IP-bil
Fyrri umferð þú hefur heimsótt mbl.is í vafra engin saga, engar kökur
Beiðnatíðni ein og ein þúsundir verkefna af sama neti

Ekkert af þessu tengist kóðanum þínum. Þess vegna er „það virkar hjá mér” ekki sönnun — það segir aðeins að það virki þaðan sem þú situr.

Þú getur lent í þessu hvenær sem er: á VPN, í skólanetinu, eða einfaldlega eftir of margar beiðnir í röð.

Lausnin hér er ekki að fara í kringum Cloudflare, heldur að spyrja ekki aftur. Kaflinn er keyrður á vél kennarans, þar sem beiðnin gengur, og niðurstöðurnar geymdar með bókinni. Þegar hún er þýdd les Quarto þær í stað þess að senda beiðni. Þannig fær mbl.is eina beiðni frá einni manneskju í stað einnar í hvert sinn sem einhver ýtir á git push.

Aðeins textinn er geymdur — engar myndir. robots.txt bannar /frimg/ og /myndir/, og við sækjum hvorugt: mynstrin lesa HTML-kóðann og skila tölum.

Viljirðu sjálf tryggja að þú vinnir alltaf úr sama eintaki er Internet Archive leiðin. Vistun þar hefur fasta slóð með tímastimpli og breytist ekki. Það er handhægt þegar tveir bera saman niðurstöður — en athugaðu að endurspilun á forsíðum fréttamiðla er oft brotin, því vistaða JavaScriptið nær ekki í sín gögn.

Sækjum síðuna

library(httr2)

svar <- request("https://www.mbl.is/") |>
  req_user_agent("IDN302G kennsluefni (netfang@hi.is)") |>
  req_timeout(60) |>
  req_retry(max_tries = 3) |>
  req_perform()

html <- resp_body_string(svar)
sott <- resp_date(svar)

nchar(html)
[1] 367576
import re
import requests

svar = requests.get(
    "https://www.mbl.is/",
    headers={"User-Agent": "IDN302G kennsluefni (netfang@hi.is)"},
    timeout=60,
)
svar.raise_for_status()

html = svar.text
len(html)
367576

Þetta er ein skrá upp á mörg hundruð þúsund stafi. Við ætlum ekki að lesa hana — við ætlum að spyrja hana spurninga.

Hvaða útgáfa af síðunni er þetta?

Forsíða fréttamiðils er ekki fast skjal. Talan hér að neðan var rétt þegar bókin var þýdd og er líklega orðin röng þegar þú lest hana, svo við festum nákvæmlega hvað við unnum úr — tíma, stærð og gátsummu:

hratt <- resp_body_raw(svar)

skra <- tempfile()
writeBin(hratt, skra)

data.frame(
  sott      = format(sott, "%Y-%m-%d %H:%M:%S", tz = "UTC"),
  staerd_kb = round(length(hratt) / 1024),
  md5       = unname(tools::md5sum(skra))
)
                 sott staerd_kb                              md5
1 2026-08-25 22:53:49       363 05f1246985426fc473cae4c31b1c2af6
import hashlib

{
    "sott": svar.headers.get("Date", "óþekkt"),
    "staerd_kb": round(len(svar.content) / 1024),
    "md5": hashlib.md5(svar.content).hexdigest(),
}
{'sott': 'Tue, 25 Aug 2026 22:53:55 GMT', 'staerd_kb': 363, 'md5': '5d8fe6cd63634a811d0c56accf351f98'}

Taktu eftir að við hashum hráu bætin úr svarinu — resp_body_raw() og svar.content — en ekki textann sem við lásum úr því. Það skiptir máli: um leið og bætunum er breytt í streng koma stafatöflur og línuskil við sögu, og málin tvö fara ekki eins að. Sömu bæti gefa sömu summu í báðum flipum; sami texti gerir það ekki endilega.

Berðu flipana tvo saman: summurnar stemma ekki, og tímarnir muna nokkrum sekúndum. Þar sérðu vandann svart á hvítu. R og Python sendu sína beiðnina hvor, og á þeim sekúndum sem liðu á milli breyttist forsíðan. Gátsumman gerir það sýnilegt sem annars hefði farið framhjá okkur.

Viljirðu prófa þetta sjálf skiptir máli hvernig þú þýðir. Þessi síða er fryst, svo þýðing á bókinni allri les tölurnar hér að ofan úr geymdri niðurstöðu og sendir enga beiðni. Þýðirðu hins vegar þessa einu skrá — quarto render regex/mbl.qmd — keyrir Quarto bútana upp á nýtt og þú færð þínar eigin tölur, aðrar en þessar, því forsíðan hefur breyst síðan.

Gátsumman er þess vegna gagnleg: hún segir þér strax hvort þú sért að vinna úr sama eintaki og einhver annar, í stað þess að þið rífist um hvor hafi talið rétt.

Unnið dæmi: hvaða fréttaflokkar eru á forsíðunni?

Fyrst skoðum við hvernig fréttahlekkur lítur út. Þeir eru allir eins uppbyggðir:

/frettir/innlent/2026/08/25/eitthvad_gerdist/
 └─ 1 ─┘ └─ 2 ─┘ └── 3 ──┘ └────── 4 ──────┘
  1. fastur upphafshluti,
  2. flokkurinn — það sem við viljum,
  3. dagsetning í þremur liðum,
  4. slóðarnafn fréttarinnar.

Flokkurinn er því annar liður í slóðinni, alltaf lágstafir, alltaf á undan ártali. Það dugar til að skrifa mynstur — við grípum flokkinn með sviga og krefjumst þess að fjórar tölur komi strax á eftir, svo við lendum ekki á öðrum hlekkjum sem byrja eins.

mynstur <- 'href="/frettir/([a-z]+)/\\d{4}/'

fundid <- regmatches(html, gregexpr(mynstur, html, perl = TRUE))[[1]]
flokkar <- sub(mynstur, "\\1", fundid, perl = TRUE)

sort(table(flokkar), decreasing = TRUE)
flokkar
innlent  erlent kynning  knippi   veidi 
     52      17      15       8       2 
from collections import Counter

mynstur = r'href="/frettir/([a-z]+)/\d{4}/'
flokkar = re.findall(mynstur, html)

Counter(flokkar).most_common()
[('innlent', 52), ('erlent', 17), ('kynning', 15), ('knippi', 8), ('veidi', 2)]

Berðu flipana tvo saman. Þeir geta skilað ólíkum tölum, og þá er það ekki villa í mynstrinu.

Það er ekki munur á R og Python. Sama mynstur á sama texta gefur alltaf sömu niðurstöðu, í hvoru málinu sem er. Munurinn liggur í inntakinu: flipparnir senda sína beiðnina hvor, og forsíða fréttamiðils getur breyst á þeim sekúndum sem líða á milli.

Þess vegna eru tvær leiðir til að fá endurtakanlega niðurstöðu:

  • Sæktu einu sinni og vistaðu. Vinndu svo alla úrvinnsluna á vistuðu skránni. Þá vinna allar aðferðirnar þínar á nákvæmlega sama inntaki.
  • Notaðu fasta vistun. Slóð á tiltekna vistun hjá Internet Archive vísar alltaf á sama eintak, ólíkt https://www.mbl.is/ sem vísar á það sem er efst í dag.

Ef þú getur ekki bent á nákvæmlega hvaða gögn niðurstaðan byggir á, getur enginn endurtekið hana — þar með talin þú sjálf í næstu viku.

ÁbendingAf hverju [a-z]+ en ekki \w+?

\w nær líka yfir tölustafi og undirstrik. Þá myndi mynstrið éta ártalið í sumum tilfellum og skila 2026 sem flokki. Þrengsta mynstrið sem virkar er alltaf besta mynstrið.

Athugaðu líka perl = TRUE í R-útgáfunni — án hennar þekkir R ekki \d. Sjá Hópar og grip.

Æfingar

Hver æfing byggir á sama html og að ofan. Leystu þær í því máli sem þú notar í hópverkefninu, og prófaðu mynstrin í regex101 áður en þú skrifar þau í kóðann.

1. Hversu margar fréttir eru á forsíðunni?

Teldu einstakar greinar, ekki hlekki. Sama fréttin birtist oft tvisvar eða þrisvar á forsíðunni: einu sinni sem mynd, einu sinni sem fyrirsögn.

Vísbending: gríptu alla slóðina, ekki bara flokkinn — dagsetningin og slóðarnafnið eru hluti af auðkenni fréttarinnar. Notaðu svo unique() (R) eða set() (Python) og berðu saman við heildarfjöldann. Munurinn kemur á óvart.

2. Hvað er hitastigið í Reykjavík?

Veðurkassinn efst á síðunni sýnir hita í gráðum.

Vísbending: skoðaðu HTML-kóðann kringum töluna. Hún og gráðumerkið eru ekki í sama taggi, svo mynstrið verður að spanna bæði — og leyfa línuskil á milli. Mundu líka að hiti getur verið neikvæður.

3. Hvert er gengi Bandaríkjadals?

Þetta er lúmskasta æfingin, og hún kennir mikilvægustu lexíuna í kaflanum.

Vísbending: leitaðu að USD í HTML-kóðanum. Reiturinn sem sýnir gengið á síðunni er tómurvalue="" — því talan er sett inn með JavaScript eftir að síðan hleðst. Gengið er samt í skránni, en neðar, inni í JavaScript en ekki í því sem birtist á síðunni. Finndu það og skrifaðu mynstur sem nær gjaldmiðilstákninu og upphæðinni saman.

Lærdómurinn: það sem þú sérð í vafranum er ekki endilega það sem þú færð í svarinu. Notaðu alltaf það sem þú sóttir sjálf til að skrifa mynstrið, ekki það sem þróunartólin sýna.

4. Hversu mörg orð eru á síðunni?

Aðeins sýnilegur texti telur.

Vísbending: þetta er hreinsun í þremur skrefum. Fyrst burt með <script> og <style>með innihaldinu, ekki bara töggunum sjálfum. Svo burt með öllum öðrum töggum. Loks burt með HTML-táknum eins og &nbsp; og &#8230;. Þá er eftir texti sem þú getur talið orð í — mundu að íslenskir stafir eru ekki í [a-z].

Hvaða tíu orð lengri en fjórir stafir eru algengust? Segir listinn eitthvað um fréttadaginn?

5. Hversu margar auglýsingar eru á síðunni?

Prófaðu að telja þær. Skrifaðu svo niður hvers vegna þú treystir ekki tölunni sem þú fékkst.

Vísbending: það er ekkert <auglysing> tagg. Þú getur leitað að augl í slóðum, að data-ad, adunit eða googletag, eða að klösum sem innihalda ad. Hver aðferð gefur sína tölu, og engin þeirra er rétt: sumar auglýsingar eru sóttar eftir á, og ad finnst líka í óskyldum orðum. Þetta er raunverulega staðan þegar gögnin eru ekki merkt fyrir þig.

MikilvægtVirkar mynstrið á öðrum útgáfum af gögnunum?

Mynstur sem gengur upp á einni síðu er ekki þar með rétt. Það gæti verið of sértækt — sniðið að því eina eintaki sem þú varst með fyrir framan þig.

Hitaæfingin er gott dæmi. Að sumri finnurðu aldrei mínustölu, svo þú kemst aldrei að því hvort mynstrið ræður við hana. En þetta er ekki bundið við neikvæðar tölur: fyrirsagnir eru mislangar, sumar fréttir eru án myndar, tölur eru stundum með aukastaf, og stundum vantar reitinn alveg.

Spurðu því um hvert mynstur sem þú skrifar:

  • Hvaða tilvik eru ekki í eintakinu sem ég er að vinna með?
  • Hvernig næ ég í annað eintak af sömu gögnum til að prófa á?
  • Hvað á að gerast þegar reiturinn er tómur eða vantar — á kóðinn að þegja eða láta vita?

Prófaðu mynstrin þín á fleiri en einu eintaki af forsíðunni áður en þú telur þau tilbúin.

AðvörunMynstur sem virkar í dag getur brotnað í næstu viku

mbl.is breytir uppbyggingu síðunnar sinnar án þess að láta neinn vita. Það er eðlismunur á því að lesa API — sem lofar ákveðnu sniði — og að lesa HTML, sem lofar engu.

Þess vegna: skrifaðu prófanir sem segja til ef mynstrið hættir að finna neitt. Mynstur sem skilar skyndilega character(0) eða tómum lista á að vera villa í verkefninu þínu, ekki þögult núll í töflu.

Gátlisti

Þessi listi á við um alla regex-vinnu, ekki bara æfinguna hér að ofan. Farðu yfir hann áður en regex-kóði fer inn í verkefni: