Furor Teutonicus blog | over | volg | teuto | lyme | archief | doneer | todo
🕰️
  ⬩  
✍️ Evert Mouw
  ⬩  
⏱️ 4 min

Web scraping met Python

Deze week heb ik ontdekt hoe je relatief eenvoudig een hele productcatalogus van een website kunt kopiëren. Handig als je van elke pagina van een website specifieke informatie wilt hebben, maar je geen toegang hebt tot hun onderliggende database.

Aanleiding

Begin deze maand ben ik gestart met het inrichten van een online fietsenwinkel voor een nieuwe (tastbare) fietsenwinkel in Harderwijk. Het idee was om alle fietsen van een leverancier in ons eigen assortiment te plaatsen. Alle namen, foto’s en kenmerken van de fietsen staan op de website van de leverancier. Maar hoe krijgen we die in onze eigen database? De leverancier gaf wel toestemming voor het gebruik van de foto’s e.d., maar had geen database export paraat voor ons.

Python logo

Web scraping en Python

Nou, dan gaan we dus systematisch de informatie van de website van de leverancier scannen en kopiëren. Dat heet ook wel web scraping.

Python is een heel gemakkelijke programmeertaal die bij systeembeheerders maar ook in de wetenschap erg populair is. Met Python kun je ook scripts bouwen. Het is soms ook een goed alternatief voor Bash. Ik had nauwelijks Python kennis en kon toch al heel snel een goed werkend script in elkaar knutselen.

Python kun je op de Windows bakkie of op je Mac installeren, maar ik beperk me even tot het meest gebruiksvriendelijke platform voor dit soort dingen: Linux. En dan met name Arch Linux.

Je kunt je benodigdheden installeren met:

pacman -S python-pip
pip install lxml
pip install requests

Als je het als script wilt gebruiken, heb je de volgende shebang notatie nodig:

#!/usr/bin/python

Vergeet daarna niet chmod +x

Enne, ik ga hier niet de mooiste meest correcte Python code presenteren met een heleboel tryexcept… enzovoorts clausules. Wel krijg je hier wat werkende, simpele code.

Je Python script gaat deze bibliotheken nodig hebben:

from lxml import html
import requests
import os # om naar bestand te schrijven
import time # voor sleep()

De website verkennen

Stel dat website XYZ zijn producten in een aantal categorieën heeft opgedeeld. Dan moeten we dus eerst een lijstje maken van die categorieën.

def AlleCatagori():
    baseurl = "http://XYZ/"
    pagehtml = requests.get(baseurl)
    tree = html.fromstring(pagehtml.text)
    catnaam = tree.xpath('/html/body/div/div/div[@id="categories"]/ul/li/a/text()')
    caturlpart = tree.xpath('/html/body/div/div/div[@id="categories"]/ul/li/a/@href')
    caturl = []
    for i in caturlpart:
        caturl.append(domein + i)
    return catnaam, caturl

Die xpath dinges, huh?

Xpath

w3xpath

Het kostte me ongeveer 10 minuten om zinvol gebruik te kunnen maken van die Xpath notatie. Het komt er op neer dat je met zo’n Xpath kunt vertellen welk element van een webpagina je wilt hebben.

Met het apenstaartje (@) geef je aan dat je de waarde van een bepaald attribuut van een HTML tag wilt hebben. In het voorbeeld hierboven vraag ik het href attribuut aan, oftewel de URL naar het linkje.

De functie text() zorgt dat je de uitgevoerde tekst van een HTML tag krijgt.

De Xpath kun je vinden met behulp van Firebug (een Firefox add-on) of met Chrome. Rechtermuisknop, “element inspecteren”, opnieuw rechtermuisknop op de code die gemarkeerd weergegeven wordt, “Copy Xpath”, tadaa. Maar nog wel zelf even goed kijken of je dat Xpath zomaar overneemt of dat je het nog wat slimmer wilt maken, zodat het gemakkelijk toekomstige wijzigingen van de site kan overleven.

Modellen per categorie

Vervolgens wil je per categorie alle producten. Of beter gezegd: alle linkjes naar de productpagina’s die onder een categorie vallen.

def ModellenPerCategorie(caturl):
    pagehtml = requests.get(pageurl)
    tree = html.fromstring(pagehtml.text)
    modellen = tree.xpath('/html/body/div/div/div[@class="producten"]/div[@class="model"]/em/a/@href')
    modelurl = []
    for i in modellen:
        modelurl.append(domein + i)
    return modelurl

De producten zelf

Nu je de linkjes naar alle producten hebt (per categorie), wil je de producten zelf natuurlijk ook hebben, want daar ging het allemaal om…

def KrijgModel(modelurl):
    pagehtml = requests.get(modelurl)
    tree = html.fromstring(pagehtml.text)
    modeltitel = tree.xpath('/html/head/meta[@name="description"]/@content')
    modeltitel = modeltitel[0].strip()
    print (modeltitel)
    modelimg = tree.xpath('//*[@id="foto"]/a/img/@src')
    modelimg = modelimg[0]
    DownloadPlaatje(modelimg) # zie hieronder

Hierboven haal ik de titel van een product uit de meta tags. Natuurlijk kun je ’t ook uit een ander deel van het HTML document halen. Ook haal ik de URL naar de foto eruit.

Foto’s downloaden

Je zou elke foto kunnen downloaden met onderstaande functie. Alleen foto’s die nog niet gedownload zijn worden binnengetrokken.

def DownloadPlaatje(imgurl):
    bname = os.path.basename(imgurl)
    localname = imgfolder + "/" + bname
    if not os.path.exists(localname):
        r = requests.get(imgurl)
        with open(localname, 'wb') as fd:
            for chunk in r.iter_content(512):
                fd.write(chunk)
    return

En nu het hele plaatje

Samen zal het er zo ongeveer zo uitzien:

categori = AlleCatagori()
for i in categori
    modellen = ModellenPerCategorie(i)
    for j in modellen
        KrijgModel(j)
        time.sleep(4)

De “sleep” is er om de webserver niet te overbelasten. Bovendien zijn er firewalls die je blokkeren als je snel achter elkaar een heleboek webpagina’s en foto’s van dezelfde webserver wil downloaden.

Dat was het. Je kunt zelf nog wat meer informatie opzoeken en wat zaken aanpassen, maar met deze basis zou je de inhoud een website wel aardig moeten kunnen benutten. Toch kan het nog beter:

Scrapy

Volgens de makers van Scrapy:

Scrapy is a fast high-level screen scraping and web crawling framework, used to crawl websites and extract structured data from their pages. It can be used for a wide range of purposes, from data mining to monitoring and automated testing.

Zelf heb ik Scrapy niet getest. Ik had de functionaliteit niet nodig. Maar voor complexere web scraping zou ’t wel eens handig kunnen zijn.

update 2013-12-13:

Beautiful Soup

Als je gemakkelijk met Python aan webscraping wilt doen, heb je misschien ook veel aan Beautiful Soup.


Deze blogpost werd in december 2022 overgezet van WordPress naar een methode gebaseerd op Markdown; het is mogelijk dat hierbij fouten of wijzigingen zijn ontstaan t.o.v. de originele blogpost.