Webscraping med PHP

Webscraping, eller sitescraping som jeg har kalt det i alle år, går ut på å «rappe» innhold fra andre nettsteder, gjerne automatisert.

Jeg har egentlig ikke lært denne teknikken noe sted, så det jeg gjør er muligens helt på jordet rent teknisk sett. Men det har fungert for meg.

Man kan hente hele nettsider, eller man kan hente et avgrenset område på en nettside.

Bruksområder

Det søkemotorer gjør når de indekserer innhold på nettet dreier seg egentlig om akkurat det samme. Man henter ut innhold og lagrer det hos seg selv.

For min egen del har jeg for det meste brukt det til internt bruk i for eksempel Liernett, slik som «overvåking» av hva som postes på andre sine nettsteder. Hvis et nettsted for et lokalt idrettslag publiserer nyheter, så kan man hente ut dette og vise det i en mer konsentrert form et annet sted, eller kanskje varsle på e-post når det kommer nytt innhold.

Jeg har også brukt det i forbindelse med visning av valgresultater, som jeg har hentet fra de offisielle valgsidene til Kommunaldepartementet under opptellingene.

Bruk av denne teknologien kan være litt i grenseland juridisk sett, men så lenge man gjør det for eget bruk bør det være innenfor. Nå er det jo også slik at flere og flere utgivere av offentlig informasjon tilbyr åpne API-er for uthenting av slik informasjon, noe som gjør webscraping overflødig.

Sylling-Snakk

I mangel av noe bedre eksempel tar jeg for meg tjenesten Sylling-Snakk på nettstedet Sylling.no.

Sylling Snakk på Sylling.no

Nettstedet Sylling.no startet jeg sommeren 2000, og de senere årene har det vært lite aktivitet der. På midten av 2000-tallet, da min generasjon begynte å bevege seg utenbygds i forbindelse med studier, jobb og førstegangstjeneste, hadde tjenesten Sylling-Snakk sin storhetstid. Her kunne man fra hvor som helst i verden dele med de andre hvor man var, hva man gjorde og hva man skulle gjøre. For eksempel ble det dealet mange turer med pirattaxi (les: vennekjøring) via Sylling-Snakk, da en taxitur hjem fra byen kostet 800 kroner.

Faktisk kan man gå så langt som å si at Sylling-Snakk er forløperen til Twitter. Ok, det er kanskje å gå litt langt, men hør nå her: Det kan kun postes tekstinnlegg på inntil 200 tegn. Twitter har som sagt en grense på 140 tegn. Det er ingen innlogging per i dag, men tidligere var det en del reserverte navn som kun «stamgjestene» kunne bruke. Allerede rundt 2004 var det en egen mobilversjon av Sylling-Snakk. Med mobilversjon i denne sammenhengen snakker vi om WAP.

Sylling-Snakk som eksempel

Sylling-Snakk er egnet som et eksempel på webscraping av følgende grunner:

  1. Ingen problemer med juridiske overtramp.
  2. Kodestrukturen på Sylling.no endrer seg sjelden. Svært sjelden.

Og så en liten advarsel: Koden på Sylling.no er veldig rotete. Nettstedet er så gammelt at det faktisk er tenåring nå, og koden bærer noe preg av dette. Siste opprydning var i år 2010, og den var veldig rask og overfladisk... Veldig mye inline CSS!

Fremgangsmåte

Kort fortalt pleier jeg å bruke denne fremgangsmåten:

  1. Hente nettsiden med file_get_contents() i PHP (eller cURL)
  2. Finne ut hvor i koden området vi skal hente ut starter, er det noe unik kode der som skiller seg ut?
  3. Finne tilsvarende sted for hvor området slutter
  4. Gjøre eventuelle tilpasninger med str_replace()
  5. Evt. splitte opp i flere delområder (hvis man henter en liste med innhold)

Hente nettsiden

Jeg gjør det så enkelt som dette og henter forsiden til Sylling.no, hvor de siste meldingene på Sylling-Snakk er listet opp:

$data = file_get_contents("http://www.sylling.no/");

syllingsnakk2.png

Finne startposisjonen for området

Her er jeg ute etter å finne hvor det området jeg er ute etter starter. Denne posisjonen vil jeg ha som antall tegn ut i koden regnet fra begynnelsen.

Jeg finner noe kode som bare står ett sted, og det er rett før Sylling-Snakk på forsiden:

$startposisjon = strpos($data, '<div style="padding:0 5px; font-size: 8pt;"><div style="margin-left: 10px; text-indent:-10px;">');

Finne startposisjon

Nå vil nok kanskje Sylling.no endre seg noe fra tid til annen, men per i dag får jeg nå oppgitt at dette området begynner ved tegn 13 280. Det er altså 13 280 tegn før det området jeg vil ha.

Finne sluttposisjonen for området

Jeg gjør dette på samme måte som med startposisjonen:

$sluttposisjon = strpos($data, '<a href="/snakk/">Snakk ut på nye Sylling-Snakk</a>');

Jeg får oppgitt at området slutter ved tegn 14 360.

Avgrense området

Nå har jeg altså funnet ut at området begynner ved $startposisjon og slutter ved $sluttposisjon, og da kan jeg avgrense $data til kun dette området.

Det gjør jeg med substr() (substring):

$data = substr($data, $startposisjon, ($sluttposisjon-$startposisjon));

Det andre argumentet, i lysgrønn skrift, er hvor området begynner.

Det tredje argumentet, i oransje skrift, er lengden på området jeg vil ha ut. Dette blir da sluttposisjon minus startposisjon. Som sagt fikk jeg startposisjon 13 280 og sluttposisjon 14 360, hvilket betyr at lengden på området er 1080 tegn.

Jeg pleier også å sjekke underveis hvordan mine utdata ser ut ved å vise det i et stort <textarea>:

echo '<textarea style="width: 98%; height: 98%;">'.$data.'</textarea>';

Nå sitter jeg igjen med kun dette (jeg har lagt til linjeskift her for å illustrere innholdet bedre):

<div style="padding:0 5px; font-size: 8pt;">
 <div style="margin-left: 10px; text-indent:-10px;"><b>13.11.13 15:30 :):</b> Alle bør vurdere å flytte ut til slike steder som Sylling. Pendling intet problem, det er mange som jobber f.eks i Oslo. (men kollektivtilbudet er dessverre elendig den veien)</div>
 <div style="margin-left: 10px; text-indent:-10px;"><b>27.10.13 20:35 klopp:</b> Ingen problem å pendle. Kjør via Solihøgda. Flere barnehager i Sylling</div>
 <div style="margin-left: 10px; text-indent:-10px;"><b>22.08.13 21:54 Karoline:</b> Er det noen som Leier ut hus/ leilighet i Syllingområdet? Lanlig belligenhet til en enslig dame i 40 årene med fast jobb?Send meg en mail hvis noen vet om noen :) Karz@live.no</div>
 <div style="margin-left: 10px; text-indent:-10px;"><b>07.07.13 21:29 Daniel:</b> So beautiful and peaceful place!)</div>
 <div style="margin-left: 10px; text-indent:-10px;"><b>08.06.13 21:55 AnneHege:</b> Hei! Vi vurderer å kjøpe hus i Sylling. Er det noen som pendler mtp til Oslo? Midt i rushet... Hvordan fungerer det? Har barn i barnehage...</div>
</div>

Tilpasninger

Når jeg ser på dataene overfor slår det meg at jeg kan fjerne første og siste linje, så sitter jeg kun igjen med de forskjellige innleggene. Verken mer eller mindre.

Jeg tar nå i bruk substr() igjen. Først fjerner jeg evt whitespace (mellomrom) fra begynnelse og slutt, så fjerner jeg de første 44 tegnene, deretter de siste 6 tegnene:

$data = trim($data);
$data = substr($data, 44);
$data = substr($data, 0, -6);

Nå har jeg dette innholdet å jobbe med videre:

<div style="margin-left: 10px; text-indent:-10px;"><b>13.11.13 15:30 :):</b> Alle bør vurdere å flytte ut til slike steder som Sylling. Pendling intet problem, det er mange som jobber f.eks i Oslo. (men kollektivtilbudet er dessverre elendig den veien)</div>
<div style="margin-left: 10px; text-indent:-10px;"><b>27.10.13 20:35 klopp:</b> Ingen problem å pendle. Kjør via Solihøgda. Flere barnehager i Sylling</div>
<div style="margin-left: 10px; text-indent:-10px;"><b>22.08.13 21:54 Karoline:</b> Er det noen som Leier ut hus/ leilighet i Syllingområdet? Lanlig belligenhet til en enslig dame i 40 årene med fast jobb?Send meg en mail hvis noen vet om noen :) Karz@live.no</div>
<div style="margin-left: 10px; text-indent:-10px;"><b>07.07.13 21:29 Daniel:</b> So beautiful and peaceful place!)</div>
<div style="margin-left: 10px; text-indent:-10px;"><b>08.06.13 21:55 AnneHege:</b> Hei! Vi vurderer å kjøpe hus i Sylling. Er det noen som pendler mtp til Oslo? Midt i rushet... Hvordan fungerer det? Har barn i barnehage...</div>

Kan jeg forenkle det noe mer? Ja, jeg kan fjerne <div> og <b>. Og </div> kan jeg erstatte med noe helt unikt (slik at det ikke tilfeldigvis dukker opp i koden andre steder), for eksempel |innleggslutt|, slik at jeg ser hvor hvert innlegg begynner og slutter. Jeg kan også erstatte </b> med |nickslutt|:

$data = str_replace('<div style="margin-left: 10px; text-indent:-10px;"><b>', '', $data);
$data = str_replace('</div>', '|innleggslutt|', $data);
$data = str_replace('</b>', '|nickslutt|', $data);

Det gir meg dette:

13.11.13 15:30 :):|nickslutt| Alle bør vurdere å flytte ut til slike steder som Sylling. Pendling intet problem, det er mange som jobber f.eks i Oslo. (men kollektivtilbudet er dessverre elendig den veien)|innleggslutt|
27.10.13 20:35 klopp:|nickslutt| Ingen problem å pendle. Kjør via Solihøgda. Flere barnehager i Sylling|innleggslutt|
22.08.13 21:54 Karoline:|nickslutt| Er det noen som Leier ut hus/ leilighet i Syllingområdet? Lanlig belligenhet til en enslig dame i 40 årene med fast jobb? Send meg en mail hvis noen vet om noen :) Karz@live.no|innleggslutt|
07.07.13 21:29 Daniel:|nickslutt| So beautiful and peaceful place!)|innleggslutt|
08.06.13 21:55 AnneHege:|nickslutt| Hei! Vi vurderer å kjøpe hus i Sylling. Er det noen som pendler mtp til Oslo? Midt i rushet... Hvordan fungerer det? Har barn i barnehage...|innleggslutt|

Nå har jeg faktisk fått ut de dataene jeg vil ha, og det på et format jeg selv kontrollerer.

Mer struktur

Men jeg gir meg ikke her, jeg vil ha enda litt mer struktur på dataene.

Jeg splitter opp hvert innlegg med explode(). Da vil jeg få en array med alle innleggene:

$innlegg = explode("|innleggslutt|", $data);

Det neste jeg gjør skal jeg ikke forklare i detalj, men grovt oppsummert: Jeg looper gjennom alle innleggene, bruker mye av den samme teknologien jeg har brukt tidligere med startposisjon og sluttposisjon, og får skilt ut dato, navn og selve innlegget.

Her kan du for eksempel putte dataene inn i en database eller skrive de til en fil. Eller som jeg gjør i dette tilfellet, skriver det ut som noe enkel og oversiktlig HTML:

for ($i = 0; $i < (count($innlegg)-1); $i++)
 {
  $dato = substr($innlegg[$i], 0, 14);
  $nicksluttposisjon = strpos($innlegg[$i], '|nickslutt|');
  $nick = substr($innlegg[$i], 14, ($nicksluttposisjon-15));
  $tekst = substr($innlegg[$i], ($nicksluttposisjon+11));
  echo "<div class=\"innlegg\">";
  echo "<div class=\"dato\">".$dato."</div>";
  echo "<div class=\"nick\">".$nick."</div>";
  echo "<div class=\"tekst\">".$tekst."</div>";
  echo "</div>";
 }

Hvis jeg nå legger til følgende CSS:

<style>
.innlegg { background: #eee; border: 1px solid #ccc; margin: 10px 0; padding: 5px; }
.innlegg .dato { font-style: italic; font-weight: bold; font-size: 0.8em; }
.innlegg .nick { font-weight: bold; }
</style>

Så får jeg dette som sluttresultat:

13.11.13 15:30
:)
Alle bør vurdere å flytte ut til slike steder som Sylling. Pendling intet problem, det er mange som jobber f.eks i Oslo. (men kollektivtilbudet er dessverre elendig den veien)
27.10.13 20:35
klopp
Ingen problem å pendle. Kjør via Solihøgda. Flere barnehager i Sylling
22.08.13 21:54
Karoline
Er det noen som Leier ut hus/ leilighet i Syllingområdet? Lanlig belligenhet til en enslig dame i 40 årene med fast jobb? Send meg en mail hvis noen vet om noen :) Karz@live.no
07.07.13 21:29
Daniel
So beautiful and peaceful place!)
08.06.13 21:55
AnneHege
Hei! Vi vurderer å kjøpe hus i Sylling. Er det noen som pendler mtp til Oslo? Midt i rushet... Hvordan fungerer det? Har barn i barnehage...

Selvkritikk

En ting jeg åpenbart burde gjort her er å oversette datoene til et mer brukbart format, for eksempel YYYY-MM-DD. På grunn av den klønete måten det er skrevet på måtte jeg her brukt substr() tre ganger for å få skilt hhv. år, måned og dato.

Svend Asbjørn Sylling, 19. desember 2013

Bloggen fra Sylling Hardcode