Ga naar hoofdinhoud

Webscraping & NLP in Python

Leer romans van het web te scrapen en frequentieverdelingen van woorden te plotten; Je doet ervaring op met de Python-packages requests, BeautifulSoup en nltk.
· 14 min lezen

Verken met AI

nltk Python

Eerder deze week deed ik een Facebook Live Code along-sessie. Daarin gebruikten we wat basis Natural Language Processing om de meest voorkomende woorden in de roman Moby Dick te plotten. Daarbij zie je ook hoe effectief het is om te denken in termen van de volgende data science-pijplijn, met voortdurend oog voor het proces:

  1. Formuleer je vraag;
  2. Haal je data binnen;
  3. Bewaar en bewerk je data om je vraag te beantwoorden;
  4. Beantwoord je vraag;
  5. Presenteer je oplossing zodat anderen die begrijpen.

In deze post leer je hoe je een data science-pijplijn bouwt om frequentieverdelingen van woorden in Moby Dick (en vele andere romans) te plotten.

Tip: wil je de Facebook Live terugkijken, bekijk dan de volgende video; Je kunt doorspoelen naar minuut 12, waar Hugo begint en de sessie echt van start gaat.

We geven je de romans niet: je leert ze te scrapen van de website Project Gutenberg (dat in feite een grote corpus aan boeken bevat) met de Python-package requests en hoe je de romans uit deze webdata extraheert met BeautifulSoup. Daarna duik je in de analyse van de romans met de Natural Language Toolkit (nltk). Onderweg leer je over belangrijke aspecten van Natural Language Processing (NLP) zoals tokenization en stopwoorden.

Na afloop kun je frequentieverdelingen van woorden visualiseren voor elke roman die je op Project Gutenberg kunt vinden. De NLP-vaardigheden die je ontwikkelt zijn bovendien toepasbaar op veel van de data die data scientists tegenkomen, aangezien het grootste deel van de wereldwijde data ongestructureerd is en veel tekst bevat.

Uit welk werk zou bijvoorbeeld de volgende woordfrequentieverdeling afkomstig zijn?

woordfrequentieverdeling

Deze post is gegenereerd vanuit een Jupyter Notebook; Je vindt het in deze repository. Heb je gedachten, reacties en/of overpeinzingen, neem dan gerust contact met me op via Twitter: @hugobowne.

Voorstappen

Volg de instructies in de README.md om je systeem klaar te zetten.

1. Formuleer je vraag

Wat zijn de meest voorkomende woorden in de roman Moby Dick en hoe vaak komen ze voor?

2. Haal je data binnen

Je ruwe data is de tekst van Melvilles roman Moby Dick. Hoe zou je de tekst van dit boek van ~800 woorden in Python krijgen?

Er zijn meerdere manieren, maar besef eerst dat de tekst vrij beschikbaar is op Project Gutenberg. Laten we daarheen gaan, Moby Dick zoeken en de relevante URL in je Python-namespace opslaan:

# Store url
url = 'https://www.gutenberg.org/files/2701/2701-h/2701-h.htm'

Nu je de URL hebt, moet je de HTML van de website ophalen.

Opmerking: HTML staat voor Hypertext Markup Language en is de standaardopmaaktaal voor het web.

Je gaat hiervoor requests gebruiken, een van de populairste en nuttigste Python-packages. Meer info vind je in DataCamp’s Importing Data in Python (Part 2) course.

beautifulsoup Python

Volgens de website van het requests-pakket:

Requests allows you to send organic, grass-fed HTTP/1.1 requests, without the need for manual labor.

en de volgende organisaties zeggen requests intern te gebruiken:

Her Majesty's Government, Amazon, Google, Twilio, NPR, Obama for America, Twitter, Sony, and Federal U.S. Institutions that prefer to be unnamed.

Bovendien,

Requests is one of the most downloaded Python packages of all time, pulling in over 13,000,000 downloads every month. All the cool kids are doing it!

Je gaat een GET-request naar de website doen, wat betekent dat je er data van ophaalt. Dit is ook wat je via een browser doet bij het bezoeken van een webpagina. Er zijn ook andere typen requests, zoals POST-requests, maar daar houden we ons hier niet mee bezig.

requests maakt dit eenvoudig met de functie get. Doe hier de request en kijk welk objecttype wordt teruggegeven.

# Import `requests`
import requests

# Make the request and check object type
r = requests.get(url)
type(r)
requests.models.Response

Dit is een Response-object. In de requests kickstart guide zie je dat een Response-object een attribuut text heeft waarmee je de HTML kunt ophalen! Laten we dit doen en de HTML printen om te kijken:

# Extract HTML from Response object and print
html = r.text
#print(html)

OK! Deze HTML is niet precies wat je wilt. Maar hij bevat wel wat je wilt: de tekst van Moby Dick. Wat je nu moet doen is deze HTML bewerken om de roman eruit te halen.

3. Bewerk de data om de vraag te beantwoorden

Deel 1: Haal de tekst uit de HTML

Hiervoor gebruik je het pakket BeautifulSoup. Op de website van het pakket staat:

beautifulsoup package website

Dat ziet er veelbelovend uit!

Eerst een woord over de naam: Beautiful Soup? In webontwikkeling verwijst de term "tag soup" naar structureel of syntactisch incorrecte HTML-code van een webpagina. Wat Beautiful Soup het beste doet, is tag soup weer mooi maken en er eenvoudig informatie uit halen! Het hoofdobject dat je met dit pakket maakt en bevraagt, heet dan ook BeautifulSoup. Nadat je de soup hebt gemaakt, kun je met de methode .get_text() de tekst eruit halen.

# Import BeautifulSoup from bs4
from bs4 import BeautifulSoup


# Create a BeautifulSoup object from the HTML
soup = BeautifulSoup(html, "html5lib")
type(soup)
bs4.BeautifulSoup

Uit deze soup-objecten kun je allerlei interessante informatie over de website halen die je scraped, zoals de titel:

# Get soup title
soup.title
<title>
      Moby Dick; Or the Whale, by Herman Melville
    </title>

Of de titel als string:

# Get soup title as string
soup.title.string
'\n      Moby Dick; Or the Whale, by Herman Melville\n    '

Of alle URL’s die je op een pagina vindt binnen de < a >-tags (hyperlinks):

# Get hyperlinks from soup and check out first several
soup.findAll('a')[:8]
[<a href="#link2H_4_0002"> ETYMOLOGY. </a>,
 <a href="#link2H_4_0003"> EXTRACTS (Supplied by a Sub-Sub-Librarian).
         </a>,
 <a href="#link2HCH0001"> CHAPTER 1. Loomings. </a>,
 <a href="#link2HCH0002"> CHAPTER 2. The Carpet-Bag. </a>,
 <a href="#link2HCH0003"> CHAPTER 3. The Spouter-Inn. </a>,
 <a href="#link2HCH0004"> CHAPTER 4. The Counterpane. </a>,
 <a href="#link2HCH0005"> CHAPTER 5. Breakfast. </a>,
 <a href="#link2HCH0006"> CHAPTER 6. The Street. </a>]

Wat je wilt doen is de tekst uit de soup halen, en daar is een superhandige methode .get_text() precies voor bedoeld.

Haal de tekst op, print hem en bekijk hem. Is dit wat je wilt?

# Get the text out of the soup and print it
text = soup.get_text()
#print(text)

Merk op dat dit nu bijna is wat je wilt.

Het is de tekst van de roman met wat ongewenste dingen aan het begin en wat ongewenste dingen aan het eind. Je zou dat kunnen verwijderen. Deze content is echter zoveel kleiner dan de tekst van Moby Dick dat het voor een eerste benadering prima is om het te laten staan, en zo pakken we het hier aan. Voor robuuste resultaten zou ik aanraden het te verwijderen.

Nu je de gewenste tekst hebt, is het tijd om te tellen hoe vaak elk woord voorkomt en om het frequentiehistogram te plotten dat je wilt: Natural Language Processing to the rescue!

Deel 2: Haal woorden uit je tekst met NLP

Je gaat nu nltk, de Natural Language Toolkit, gebruiken om

  1. Te tokenizen (mooi woord voor opsplitsen in tokens, zoals woorden);
  2. Stopwoorden te verwijderen (woorden zoals 'a' en 'the' die heel vaak voorkomen in ~ vrijwel alle Engelstalige teksten).

Stap 1: Tokenizen

Je wilt je tekst tokenizen, dus opsplitsen in een lijst met woorden. In feite wil je de delen van de tekst afsplitsen die gescheiden worden door spaties.

Om dit te doen gebruik je een krachtig hulpmiddel: reguliere expressies. Een reguliere expressie, of kort regex, is een tekenreeks die een zoekpatroon definieert. Ze staan bekend als verwarrend en zijn het best te introduceren met een voorbeeld.

  • Je hebt de string 'peter piper picked a peck of pickled peppers' en je wilt uit de lijst met alle woorden daarin die woorden halen die met een 'p' beginnen.

De reguliere expressie die alle woorden matcht die met 'p' beginnen is 'p\w+'. Laten we dit ontrafelen:

  • de 'p' aan het begin van de regex betekent dat je alleen reeksen tekens matcht die met een 'p' beginnen;
  • de '\w' is een speciaal teken dat elk alfanumeriek teken A-z, a-z, 0-9, plus underscores matcht;
  • De '+' geeft aan dat het vorige teken in de regex zo vaak als je wilt mag voorkomen in strings die je probeert te matchen. Dit betekent dat '\w+' willekeurige reeksen van alfanumerieke tekens en underscores matcht.

Alles bij elkaar zal de regex 'p\w+' alle substrings matchen die met een 'p' beginnen en gevolgd worden door alfanumerieke tekens en underscores. In de meeste zinvolle Engelstalige teksten komt dit overeen met woorden die met 'p' beginnen.

Je gaat nu het ingebouwde Python-pakket re gebruiken om alle woorden die met 'p' beginnen uit de zin 'peter piper picked a peck of pickled peppers' te extraheren als opwarmertje.

# Import regex package
import re

# Define sentence
sentence = 'peter piper pick a peck of pickled peppers'

# Define regex
ps = 'p\w+'


# Find all words in sentence that match the regex and print them
re.findall(ps, sentence)
['peter', 'piper', 'pick', 'peck', 'pickled', 'peppers']

Dit ziet er goed uit. Als 'p\w+' de regex is die woorden matcht die met 'p' beginnen, wat is dan de regex die alle woorden matcht?

Het is nu jouw taak om dit te doen voor onze speelgoedzin van Peter Piper hierboven.

# Find all words and print them
re.findall('\w+', sentence)
['peter', 'piper', 'pick', 'a', 'peck', 'of', 'pickled', 'peppers']

Nu kun je hetzelfde doen met text, de string die Moby Dick bevat:

# Find all words in Moby Dick and print several
tokens = re.findall('\w+', text)
tokens[:8]
['Moby', 'Dick', 'Or', 'the', 'Whale', 'by', 'Herman', 'Melville']

Opmerking: er is ook een manier om dit te doen met nltk, de Natural Language Toolkit:

# Import RegexpTokenizer from nltk.tokenize
from nltk.tokenize import RegexpTokenizer

# Create tokenizer
tokenizer = RegexpTokenizer('\w+')



# Create tokens
tokens = tokenizer.tokenize(text)
tokens[:8]
['Moby', 'Dick', 'Or', 'the', 'Whale', 'by', 'Herman', 'Melville']

Mooi! Je bent er bijna. Let er wel op dat hierboven 'Or' met een hoofdletter 'O' staat en elders misschien niet, maar zowel 'Or' als 'or' wil je als hetzelfde woord tellen. Daarom moet je een lijst maken van alle woorden in Moby Dick waarin alle hoofdletters zijn omgezet naar kleine letters. De stringmethode .lower() komt hierbij van pas:

# Initialize new list
words = []


# Loop through list tokens and make lower case
for word in tokens:
    words.append(word.lower())


# Print several items from list as sanity check
words[:8]
['moby', 'dick', 'or', 'the', 'whale', 'by', 'herman', 'melville']

Stap 2: Verwijder stopwoorden

Het is gebruikelijk om woorden te verwijderen die heel vaak voorkomen in het Engels, zoals 'the', 'of' en 'a' (zogenaamde stopwoorden), omdat ze minder interessant zijn. Voor meer over deze technieken, bekijk onze cursus Natural Language Processing Fundamentals in Python.

Het pakket nltk heeft een lijst met Engelse stopwoorden die je nu opslaat als sw en waarvan je de eerste paar elementen print.

Krijg je hier een foutmelding, voer dan het commando nltk.download('stopwords') uit om de stopwoorden op je systeem te installeren.

# Import nltk
import nltk

# Get English stopwords and print some of them
sw = nltk.corpus.stopwords.words('english')
sw[:5]
['i', 'me', 'my', 'myself', 'we']

Je wilt de lijst met alle woorden in words die niet in sw staan. Een manier om deze lijst te krijgen is om over alle elementen van words te loopen en ze aan een nieuwe lijst toe te voegen als ze niet in sw staan:

# Initialize new list
words_ns = []

# Add to words_ns all words that are in words but not in sw
for word in words:
    if word not in sw:
        words_ns.append(word)

# Print several list items as sanity check
words_ns[:5]
['moby', 'dick', 'whale', 'herman', 'melville']

4. Beantwoord je vraag

Onze vraag was: 'Wat zijn de meest voorkomende woorden in de roman Moby Dick en hoe vaak komen ze voor?'

Je kunt nu in twee regels code met nltk een frequentiehistogram van woorden in Moby Dick plotten. Hiervoor

  • Maak je een frequentieverdelingsobject met de functie nltk.FreqDist();
  • Gebruik je de methode plot() van het resulterende object.
#Import datavis libraries
import matplotlib.pyplot as plt
import seaborn as sns

# Figures inline and set visualization style
%matplotlib inline
sns.set()

# Create freq dist and plot
freqdist1 = nltk.FreqDist(words_ns)
freqdist1.plot(25)

NLP-pijplijn Python

5. Presenteer je oplossing

Het mooie is dat we, door nltk te gebruiken om onze vraag te beantwoorden, onze oplossing eigenlijk al op een manier hebben gepresenteerd die je kunt communiceren: een plot van de frequentieverdeling! Je kunt de meest voorkomende woorden aflezen, samen met hun frequentie. Zo is 'whale' het meest voorkomende woord in de roman (what else), stopwoorden uitgezonderd, en het komt meer dan 1200 keer voor!

BONUSMATERIAAL

Omdat je hebt gezien dat er veel romans op Project Gutenberg staan waarvoor we deze woordfrequentiehistogrammen kunnen maken, is het logisch om je eigen functie te schrijven die dit allemaal doet:

 def plot_word_freq(url):
    """Takes a url (from Project Gutenberg) and plots a word frequency
    distribution"""
    # Make the request and check object type
    r = requests.get(url)
    # Extract HTML from Response object and print
    html = r.text
    # Create a BeautifulSoup object from the HTML
    soup = BeautifulSoup(html, "html5lib")
    # Get the text out of the soup and print it
    text = soup.get_text()
    # Create tokenizer
    tokenizer = RegexpTokenizer('\w+')
    # Create tokens
    tokens = tokenizer.tokenize(text)
    # Initialize new list
    words = []
    # Loop through list tokens and make lower case
    for word in tokens:
        words.append(word.lower())
    # Get English stopwords and print some of them
    sw = nltk.corpus.stopwords.words('english')
    # Initialize new list
    words_ns = []
    # Add to words_ns all words that are in words but not in sw
    for word in words:
        if word not in sw:
            words_ns.append(word)
    # Create freq dist and plot
    freqdist1 = nltk.FreqDist(words_ns)
    freqdist1.plot(25)

Gebruik de functie nu om woordfrequentieverdelingen te plotten van andere teksten op Project Gutenberg:

  • Pride and Prejudice:
plot_word_freq('https://www.gutenberg.org/files/42671/42671-h/42671-h.htm')

webscraping Python

  • Robinson Crusoe
plot_word_freq('https://www.gutenberg.org/files/521/521-h/521-h.htm')

woordverdeling tokenizen

  • De King James Bible
plot_word_freq('https://www.gutenberg.org/files/10/10-h/10-h.htm')

woordfrequentie

Conclusie

In deze post heb je geleerd hoe je een data science-pijplijn bouwt om frequentieverdelingen van woorden in Moby Dick (en vele andere romans) te plotten. Je leerde ze te scrapen van de website Project Gutenberg (groot corpus aan boeken) met de Python-package requests en hoe je de romans uit deze webdata extraheert met BeautifulSoup. Daarna ben je de romans gaan analyseren met de Natural Language Toolkit (nltk). Onderweg leerde je over belangrijke aspecten van Natural Language Processing (NLP) zoals tokenization en stopwoorden. Je kunt nu woordfrequentieverdelingen visualiseren voor elke roman die je op Project Gutenberg kunt vinden. De NLP-vaardigheden die je hebt ontwikkeld zijn ook toepasbaar op veel van de data waar data scientists mee te maken krijgen, omdat het grootste deel van de wereldwijde data ongestructureerd is en veel tekst bevat.

Deze post is gegenereerd vanuit een Jupyter Notebook; Je vindt het in deze repository. Heb je gedachten, reacties en/of overpeinzingen, neem dan gerust contact met me op via twitter: @hugobowne. Laat me weten welke toffe projecten je bouwt.

Bekijk ook DataCamp’s tutorials Web Scraping using Python (and Beautiful Soup) en How to Use Python to Scrape Amazon.

Onderwerpen

Gerelateerd