13 februari 2024 Erwin Matijsen
Geplaatst in Tutorial
In het vorige bericht heb je geleerd hoe je met requests data kunt ophalen van een URL. In dat voorbeeld kwam de data in de vorm van een downloadbaar tekstbestand.
In veel gevallen zal je ook API's aan kunnen roepen, die bijvoorbeeld json teruggeven.
Maar wat als je wilt werken met gegevens van een website zelf? In deze tutorial leer je werken met Beautiful Soup,
een package om gegevens uit HTML en XML te halen.
Het project
In dit project download je weer gegevens van het KNMI, maar dit keer direct van hun website zelf. Als je naar KNMI gaat om het weerbericht te lezen, moet je eerst op "Bekijk weersverwachtingen" klikken. Vervolgens klik je op "Bekijk het volledige weerbericht" en tot slot klik je op "Lees het hele weerbericht".
Met requests en beautifulsoup sla je deze stappen over en download en verwerk je de uiteindelijke pagina direct, zodat je snel het weerbericht kunt lezen.
Voordat je begint
Maak een nieuw project aan, met een nieuwe virtual environment. Installeer daarin de benodigde packages:
shell
cdprojects
mkdirscrape_knmi&&cdscrape_knmi
python-mvenv.venv
source.venv/bin/activate# .venv\Scripts\activate.bat voor Windows
python-mpipinstallrequestsbeautifulsoup4
python-mpipfreeze>requirements.txt
De pagina downloaden
De eerste stap is het downloaden van de pagina. Maak in main.py een eenvoudige functie aan:
main.py
importrequestsURL="https://www.knmi.nl/nederland-nu/weer/verwachtingen"defget_html(refresh=True):ifnotrefresh:try:withopen("knmi.html",encoding="utf-8")asf:text=f.read()exceptFileNotFoundError:print("Bestand niet gevonden, downloaden")# Roep deze functie aan, met `refresh=True`returnget_html(refresh=True)else:response=requests.get(URL)# Als de HTTP-code 400 of hoger is, zal er een uitzondering worden opgeworpenresponse.raise_for_status()text=response.text# Sla bestand opwithopen("knmi.html","w",encoding="utf-8")asf:f.write(text)returntext
De functie zorgt ervoor dat je het bestand opslaat als knmi.html, zodat je tijdens het ontwikkelen niet steeds de URL hoeft aan te roepen. Wil je de gegevens verversen, roep dan get_html aan met refresh=True.
De soep maken
Nu je de HTML hebt, wil je ermee kunnen werken. Hier komt beautifulsoup om de hoek kijken. Met beautifulsoup 'ontleed' je de HTML en maak je het navigeerbaar en doorzoekbaar, zodat je de elementen eruit kunt halen die je nodig hebt.
Met de eerste regel zal beautifulsoup de HTML ontleden en bruikbaar maken. Hiervoor gebruik je in dit geval de standaard meegeleverde html.parser. Met print(soup.prettify()) print je het HTML-document op een (redelijk) leesbare manier.
In deze soep ga je op zoek naar de juiste informatie!
Speuren
beautifulsoup maakt het eenvoudig gegevens op te zoeken in een groot HTML-document, maar dan moet je wel weten waar je naar op zoek bent. In het opgeslagen HTML-bestand kun je zelf zoeken naar de juiste elementen. Zoek bijvoorbeeld op "Vandaag & morgen", de kop van het bericht:
Je zult al snel zien dat hele weerbericht, inclusief de tekst die onder de 'uitklap' zit, in een div met de klasse weather__text media__body aanwezig is.
DIV?
Werken met HTML is werken met verschillende elementen, zoals containers (div), titels (h1, h2) en paragrafen (p). Deze elementen kunnen ook een klasse hebben, en een id. Een goed startpunt om te leren over hoe HTML werkt
is MDN.
Nu je weet waar de voor jou interessante gegevens zich bevinden, kun je die uit de HTML halen. Vanuit daar kun je dan verder werken. Pas get_text aan:
main.py
defget_text(html):soup=BeautifulSoup(html,"html.parser")# Zoek de relevante tekstweerbericht=soup.find("div",class_="weather__text media__body")print(weerbericht.prettify())returnweerbericht
Met find zoek je in de soep naar de eerste div met de klasse weather__text media__body. De parameter heet class_ (met een streepje erachter) om conflicten met het ingebouwde woord class te voorkomen.
Je kunt de tekst nu iets beter bestuderen. Wat opvalt is dat het begin eenvoudig is. De titel staat in een h2 en de samenvatting heeft een eigen paragraaf (p) met klasse intro. Daarna wordt het ingewikkelder. Alles van 'Waarschuwingen' tot 'Komende nacht' staat in één paragraaf (p).
Vervolgen staat het weerbericht voor komende nacht ook weer in een eigen paragraaf. Daarna volgt het weerbericht van morgen ook weer met een eigen paragraaf. Aan jou de taak om deze structuur te doorgronden en een parser te schrijven die precies de juiste teksten uit de soep haalt.
Ontleden
beautifulsoup maakt het navigeren en zoeken in HTML eenvoudig. Met het vorige stukje code heb je de variabele weerbericht aangemaakt. Dit bevat nu alle relevante HTML die je kunt gaan ontleden.
Er zijn verschillende manieren om door de tekst te navigeren. De eerste manier - zoeken - heb je al gebruikt bij het vinden van de relevante delen in het geheel. Je kunt zoeken ook gebruiken om de titel en de samenvatting op te halen, omdat deze vrij duidelijk afgebakend zijn.
main.py
defontleed(weerbericht):# Sla hier de resultaten in opresult={}# De titeltitel=weerbericht.find("h2")# De samenvattingsamenvatting=weerbericht.find("p",class_="intro")result["titel"]=titel.textresult["samenvatting"]=samenvatting.textprint(result)
Met .find("h2") zoek je de titel op. Met .find("p") zoek je de eerste paragraaf die je tegenkomt. Alhoewel er meerdere paragrafen (p) zijn, haal je met find altijd alleen het eerste resultaat op dat beautifulsoup tegenkomt. Met class_="intro" maak je de zoekopdracht nog specifieker. In beide gevallen wil je de tekst hebben en niet de tags. Je haalt de tekst in de tags op met .text.
Het weerbericht voor de huidige dag bevindt zich in een eigen paragraaf. Om dit op te halen zoek je weer naar paragrafen, maar in plaats van find gebruik je find_all. Dit zal namelijk álle elementen teruggeven die aan het zoekcriteria voldoen, en niet alleen de eerste. Je weet dat je de tweede paragraaf nodig hebt, dus je code wordt:
main.py
defontleed(weerbericht):# ...# Haal nu eerst de volgende paragraaf op# Zoek alle paragrafen in `weerbericht`, en sla de tweede opp_vandaag=weerbericht.find_all("p")[1]
De volgende stap is het zoeken van de waarschuwingen. Als je in de HTML kijkt, zie je ongeveer:
<strong>
<a href="https://www.knmi.nl/nederland-nu/weer/waarschuwingen">Waarschuwingen</a>
</strong>
<br/>
Er zijn geen waarschuwingen.
Je ziet dat de kop van de waarschuwing een link is (a), dit is eenvoudig zoeken. Als je de link vindt, kun je vervolgens find_next gebruiken om de eerste br te vinden. Je gebruikt find_next om ná het huidige element te zoeken. Met find zou je ín het huidige element zoeken.
Het element ná de br is de tekst die je nodig hebt. Met beautifulsoup gebruik je find, find_next en next_element om te zoeken en te navigeren naar wat je nodig hebt:
main.py
defontleed(weerbericht):# ...# Haal de waarschuwing op. Laat de link achterwege# Vind eerst de link. Zoek dan de `br` en haal tot slot het volgende element opwaarschuwing=p_vandaag.find("a").find_next("br").next_element.textresult["waarschuwingen"]=waarschuwing
In de huidige paragraaf blijven nu de berichten per dagdeel over. De algemene structuur is:
<strong>Dagdeel</strong>
rest van de tekst
Je kunt daarom vanaf waarschuwing op zoek naar de eerste drie strong-tags in de HTML en vanuit daar de titel en de tekst ophalen:
main.py
defontleed(weerbericht):# ...# Haal de dagdelen van de huidige dag opfordagdeelinwaarschuwing.find_all_next("strong",limit=3):periode=dagdeel.text# De eerste `next_element` is de tekst binnen de `strong`-tags (titel)# De tweede `next_element` is de tekst die je nodig hebt. Haal de# non-breaking space (\xa0) weg.tekst=dagdeel.next_element.next_element.text.strip("\xa0")result[periode]=f"{periode}{tekst}"
Waarschijnlijk begin je door te krijgen hoe je beautifulsoup gebruikt om de juiste elementen te vinden door een combinatie van zoeken en navigeren. Het is soms even speuren naar wat je nodig hebt en in welk element het verstopt is. Klap de code hieronder uit om de complete ontleed-functie te bekijken. Probeer eerst zelf de rest te schrijven!
main.py
defontleed(weerbericht):# Sla hier de resultaten in opresult={}# De titeltitel=weerbericht.find("h2")# De samenvattingsamenvatting=weerbericht.find("p",class_="intro")result["titel"]=titel.textresult["samenvatting"]=samenvatting.text# Haal nu eerst de volgende paragraaf op# Zoek alle paragrafen in `weerbericht`, en sla de tweede opp_vandaag=weerbericht.find_all("p")[1]# Haal de waarschuwing op. Laat de link achterwegewaarschuwing=p_vandaag.find("a").find_next("br").next_elementresult["waarschuwingen"]=waarschuwing.text# Haal de dagdelen van de huidige dag opfordagdeelinwaarschuwing.find_all_next("strong",limit=3):periode=dagdeel.text# De eerste `next_element` is de tekst binnen de `strong`-tags (titel)# De tweede `next_element` is de tekst die je nodig hebt. Haal de# non-breaking space (\xa0) weg.tekst=dagdeel.next_element.next_element.text.strip("\xa0")result[periode]=f"{periode}{tekst}"# Haal de tweede paragraaf opp_komende_nacht=weerbericht.find_all("p")[2]# Haal de titel en de tekst voor komende nacht opkomende_nacht_titel=p_komende_nacht.find("strong")komende_nacht_text=komende_nacht_titel.next_element.next_element.text.strip("\xa0")result[komende_nacht_titel.text]=f"{komende_nacht_titel.text}{komende_nacht_text}"# Haal de dagdelen voor morgen op# De structuur is hetzelfde als de huidige dagmorgen=p_komende_nacht.next_elementfordagdeelinmorgen.find_all_next("strong",limit=3):periode=dagdeel.texttekst=dagdeel.next_element.next_element.text.strip("\xa0")result[periode]=f"{periode}{tekst}"returnresult
Met result kun je nu doen wat je wilt, maar dat is voor een volgende keer!
Conclusie
Met beautifulsoup kun je eenvoudig HTML-pagina's doorzoeken en navigeren, om daar zo de elementen uit te halen die je nodig hebt. Dit is handig als er geen API beschikbaar is, bijvoorbeeld. Let wel op: het structureel scrapen van een website is grijs gebied. Kijk dus altijd of het binnen redelijke grenzen blijft en past binnen de voorwaarden van de website waar je mee werkt.
Over de auteur
Erwin Matijsen
Erwin is de oprichter van python-cursus.nl. In allerlei rollen heeft hij Python ingezet, van het eenvoudiger maken van zijn werk tot het opleveren van complete (web)applicaties. Met vrouw en kinderen woont hij in Havelte (Drenthe), midden in de prachtige natuur. Daar wandelt hij graag, zeker ook omdat de beste ingevingen tijdens een wandeling - weg van de computer - lijken te komen.
Vragen, opmerkingen?
Heb je vragen, opmerkingen, suggesties of tips naar aanleiding van deze blog?
Neem dan contact met ons op, of laat het weten via
Mastodon of
LinkedIN.