Categories
Programming

Screen Scraping med Python

Tim Berners-Lee, Internetts pappa, snakker i et inspirerende TED foredrag om the web of data, og oppfordrer alle til å dele sine data. Ideen med web of data er at webben fram til i dag hovedsakelig har vært dokumentbasert og at denne nå endres til å bli mer databasert. I webbens dokumenter ligger det mye bakenforliggende informasjon hvor det semantiske innholdet ofte kan være utfordrende å hente ut, spesielt hvis du er en datamaskin. Selv om, ideeltt sett, all informasjon som ligger ute på Internett burde være lesbar for alle er det ikke slik, så av og til må vi være litt pragmatiske og gjøre det beste ut av situasjonen. Vi må rett og slett skrape ut den informasjonen – de dataene vi trenger – fra websiden, derav navnet screen scraping.

Et av de morsomste aspektene ved screen scraping, syntes jeg, er å forstå hvordan innholds-serveren er satt sammen og dekonstruere denne. Ettersom få organisasjoner (og personer for den saks skyld) håndkoder hver HTML-side de viser til offentligheten ligger det et system bak sidene som publiseres. Hvordan hele nettstedet er organisert og hvordan hver enkelt side er lagt opp er et godt utgangspunkt for å forstå strukturen. Mange nettsteder følger i dag også en REST-arkitektur, og dette gjør at vi kan finne ut mye bare av å se på URLen vi bruker for å få tilgang til sidens forskjellige funksjoner. Mange slike sider er laget med populære rammeverk med REST-implementering slik som Ruby on Rails og Django, som også har tilrettelagt for en enkel implementasjon av data-grensesnitt slik at spørringer kan resultere i et dataformatert svar som for eksempel XML eller JSON imotsetning til et template-basert HTML svar. Hvis du kan spørre om et datasvar kan du muligens finne dataene dine uten å scrape. Uansett, i mange tilfeller et klipp-og-lim eller filopplastning fra Excel eller Word klippet inn i et web-grensesnitt eller endret programmatisk til HTML, eller data hentet fra et annet datasystem matet til publiseringsportalen også i HTML. I disse tilfellene trenger vi å tweeke og endre litt for å hente ut data.

Et slag for demokratiet (?)

Francis Boulle, kanskje mest kjent fra TV-serien Made in Chelsea, har laget siden sexymp. På denne siden blir du presentert med bilder av to representanter fra det britiske underhuset. Din oppgave er å klikke på den mest attraktive personen, og basert på dine og andres klikk rangeres representantene. Dette systemet er nærmest identisk med siden som Mark Zuckerberg setter sammen i åpningsscenen i filmen The Social Network fra 2010. Det kan være spennende nok å se på vektingsalgoritmen og prøve å forstå hvordan Chris Evans fra Islwyn blir ansett som den mest og Steve McCabe fra Birmingham den minst attraktive, men for vår undersøkelse er det mer interessant å se hvordan vi kan innhente navn og bilder dersom vi ønsker å lage en tilsvarende side for Norge.

Stortinget.no

Et bra sted å begynne dersom vi skal utvikle en norsk sexymp (eller et annet atributt som f.eks troverdighet, ærlighet, classyness osv.) er Stortinget.no. Hvis vi går inn i listen over representanter kan vi finne ut at URLen har følgende oppbygning: http://stortinget.no/no/Representanter-og-komiteer/Representantene/Representantfordeling/Representant/?perid=SMY (for Sverre Myrli fra Arbeiderpartiet) og tilsvarende for en annen representant: http://stortinget.no/no/Representanter-og-komiteer/Representantene/Representantfordeling/Representant/?perid=IME (Ine Marie Eriksen fra Høyre). Hvis du studerer disse URLene nøye, hva er det som det er verdt å merke seg? … Ja, nemlig, representantene har en ID.

La oss nå gå inn å se på hvordan Ine Marie Eriksen sin profil egentlig ser ut. Siden nettleseren din, når du åpner en side, kun laster ned et dokument med angitte referanser skjer det ikke noe magisk (okey, med web 2.0 med Ajax og masse Javascript, eller Flash, Silverlight og andre plug-ins kan det virke mer mysterisk eller være veldig vanskelig å titte inn i boksen). I alle nettlesere kan du få tilgang til råteksten som senere skaper siden, og hvis du går inn å ser på denne kan du finne ut hvordan siden er bygget opp og hvilke andre ressurser (som f.eks bilder, stylesheets, javascript-kode) som kalles. Forsøk nå å se på HTML-filen til Eriksens stortingsbio og se om du kan finne bilde-ressursen.

Der ja, på linje 472 finner vi følgende HTML-snutt:
<img id="ctl00_MainRegion_RepShortInfo_imgRepresentative" src="/Personimages/PersonImages_Large/IME_stort.jpg" alt="Søreide, Ine M. Eriksen" style="border-width:0px;"
Dersom du følger denne filbanen med stortingsdomenet slik at URLen du benytter er: http://stortinget.no/Personimages/PersonImages_Large/IME_stort.jpg vil bare bildet åpnes. Du har nå funnet bildet som hentes inn i bio-siden vi tidligere åpnet. Nå kan du forsøke å åpne bildet til Sverre Myrli ved hjelp av kun hans system-ID som er SMY og URLen til Ine Marie Eriksen.

Hvis du nå har byttet “IME_stort.jpg” med “SMY_stort.jpg” har du forstått hvordan vårt detektivarbeid har bært frukter, og at vi nå kan få tak i alle bildene dersom vi har alle IDene. Vi trenger nå to ting: En liste over alle representantene med deres respektive IDer, og en måte å laste ned bildene til vår lokale maskin. Det er på tide å scripte litt.

Python

Det vi skal gjøre nå kan du gjøre i alle multi-purpose programmeringsspråk, men jeg har valgt å bruke Python. Jeg syntes Python er et flott språk å bruke til å løse små oppgaver hvor det er mye prøving og feiling involvert. Det finnes også veldig mye dokumentasjon og litteratur om Python i forskjellige roller, det kommer med mange nyttige biblioteker som kan brukes “right out of the box”, og det er allerede installert i de fleste Linux-distribusjoner og på alle Macer.

Jeg har lagt ut forskjellige kodeeksempler på GitHub, og du kan finne kildekoden vi bruker videre i denne lille artikkelen der. Dersom du ikke har installert Python eller mangler noen av bibliotekene jeg benytter finnes det mange gode guider på nettet på hvordan du kan få fikset dette.

Som tidligere nevnt er en av de første tingene vi trenger en liste over alle representantene. Heldigvis for oss finnes det en side med en slik liste. På siden med URL http://stortinget.no/no/Representanter-og-komiteer/Representantene/Representantfordeling/ finner vi en komplett liste over alle representantene med navn, parti, stortingsnummer og ID. Hvis vi åpner siden i en nettleser får vi ikke opp IDen, men denne finner vi dersom vi istedet ser på kildekoden. Dette er nok fordi IDen ikke er så viktig for de fleste besøkende, men for stortingssystemet er essensiell og derfor er den viktig for oss også. For å finne den informasjonen vi trenger før vi kan gå videre må vi gå inn å ta en nærmere titt på HTML-kilden.

Her kan du se at representantlista er lagt inn i en tabellstruktur. tr-taggen er table row og td-taggen er table data. Nedover kan vi se at tabellstrukturen er fast så vi kan ta utgangspunkt i denne, men i scriptet findPerId.py  har vi tatt utgangspunkt i et annet gjengående mønster. Alle representantoppføringene har en link-struktur som følger et bestemt mønster.
Amundsen, Per-Willy

Det at vi har et bestemt mønster bestående av “Representanter-og-komiteer/Representantene/Representantfordeling/Representant/”, etterfulgt av et spørsmålstegn som igjen følges av en HTML closing bracket (“>) og så to tekststrenger separert med et komma (,) og at dette mønsteret ikke finnes på andre steder enn akkurat der hvor vi ønsker å finne data er bra for oss. Dette kan vi nemlig representere med regulære utrykk

(r"Representanter-og-komiteer/Representantene/Representantfordeling/Representant/\?perid=(\w*)\">([a-å]*), ([a-å]*)", re.IGNORECASE)

Vi kan selvsagt hente hele HTML filen fra Stortingets nettsider og kjøre scriptet på all tekst, men hey, nettleseren din parser HTML så det kan jo vi gjøre også. Etter vi har lastet ned dokumentet via HTTP-protokollen med urllib2 sender vi HTML-teksten til BeautifulSoup som gjør at vi kan hente ut alle anchor-taggene og begrense RegEx sjekken vår til disse. Mange av linkene vil feile mønsteret vårt, og det er jo litt av poenget ettersom verken “tilbake til forsiden” eller “rettighetsinformasjon” er representanter vi ønsker å hente ut fra systemet. Siden vi har gruppert treffene i tre grupper ved hjelp av paranteser vil vi kunne hente ut henholdsvis brukernavn, etternavn og fornavn. Etter vi har hentet ut all informasjonen vi trenger skriver vi denne til en kommaseparert fil (populært referert til som CSV) som vi da kan hente inn i vårt neste script.

Laste ned bilder programmatisk

Etter å ha kjørt findPerId.py i forrige avsnitt har vi nå en lagret CSV-fil med fornavn, etternavn og ID på stortingsrepresentantene. Nå trenger vi bare å iterere igjennom denne listen og laste ned bilder for våre folkevalgte. Scriptet som gjør denne jobben heter downloadPictures.py og dette benytter metoden urlretrieve fra urllib biblioteket til å forespørre hvert enkelt bilde, laste dette ned og plassere det i en folder vi oppretter med os.makedirs (hvis ikke folderen vår allerede eksisterer). Bildene vi skal laste ned har før og etter representant IDen følgende tekst:

path_big_pre = "http://stortinget.no/Personimages/PersonImages_Large/"
path_big_post = "_stort.jpg"
path_little_pre = "http://stortinget.no/Personimages/PersonImages_Small/"
path_little_post = "_lite.jpg"

Som du sikkert kan forstå fra disse variabelnavnene og tekststrengene er at det for hver representant er både et stort og et lite bilde. Metoden urlretrive tar to argumenter hvorav det første må være den absolutte filbanen, altså med IDen samt teksten før og etter i URLen, outfolderen kan benytte en relativ filbane så her kan du endre filnavnet dersom du ønsker det.

Veien videre

Når du har kjørt begge scriptene har du både navn, id og bilder av alle stortingsrepresentantene, og hvis du har lest teksten og googlet ukjente navn og fremgangsmåter har du også lært noe nytt. Lykke til med videre bruk og andre eksperimenter.

Dette innlegget er skrevet på UiOs stand under The Gathering 2012 på Hamar, en myldreplass for kreativitet. 

2 replies on “Screen Scraping med Python”

Leave a Reply

Your email address will not be published. Required fields are marked *