So kratzen Sie Websites mit Python 3

Beim Web-Scraping werden Daten von Websites extrahiert.

Bevor Sie versuchen, eine Website zu kratzen, sollten Sie sicherstellen, dass der Anbieter dies in seinen Nutzungsbedingungen zulässt. Sie sollten auch prüfen, ob Sie stattdessen eine API verwenden können.

Massives Scraping kann einen Server stark belasten, was zu einem Denial-of-Service führen kann. Und das willst du nicht.

Wer sollte das lesen?

Dieser Artikel richtet sich an fortgeschrittene Leser. Es wird davon ausgegangen, dass Sie bereits mit der Programmiersprache Python vertraut sind.

Zumindest sollten Sie das Listenverständnis, den Kontextmanager und die Funktionen verstehen. Sie sollten auch wissen, wie Sie eine virtuelle Umgebung einrichten.

Wir führen den Code auf Ihrem lokalen Computer aus, um einige Websites zu erkunden. Mit einigen Verbesserungen können Sie es auch auf einem Server ausführen.

Was Sie in diesem Artikel lernen werden

Am Ende dieses Artikels erfahren Sie, wie Sie eine Webseite herunterladen, nach interessanten Informationen analysieren und zur weiteren Verarbeitung in einem verwendbaren Format formatieren. Dies wird auch als ETL bezeichnet.

In diesem Artikel wird auch erläutert, was zu tun ist, wenn diese Website JavaScript zum Rendern von Inhalten verwendet (z. B. React.js oder Angular).

Voraussetzungen

Bevor ich anfangen kann, möchte ich sicherstellen, dass wir bereit sind zu gehen. Bitte richten Sie eine virtuelle Umgebung ein und installieren Sie die folgenden Pakete darin:

  • beautifulsoup4 (Version 4.9.0 zum Zeitpunkt des Schreibens)
  • Anfragen (Version 2.23.0 zum Zeitpunkt des Schreibens)
  • Wortwolke (Version 1.17.0 zum Zeitpunkt des Schreibens, optional)
  • Selen (Version 3.141.0 zum Zeitpunkt des Schreibens, optional)

Den Code für dieses Projekt finden Sie in diesem Git-Repository auf GitHub.

In diesem Beispiel werden wir das Grundgesetz für die Bundesrepublik Deutschland kratzen. (Keine Sorge, ich habe die Nutzungsbedingungen überprüft. Sie bieten eine XML-Version für die maschinelle Verarbeitung an, aber diese Seite dient als Beispiel für die Verarbeitung von HTML. Es sollte also in Ordnung sein.)

Schritt 1: Laden Sie die Quelle herunter

Das Wichtigste zuerst: Ich erstelle eine Datei urls.txtmit allen URLs, die ich herunterladen möchte:

//www.gesetze-im-internet.de/gg/art_1.html //www.gesetze-im-internet.de/gg/art_2.html //www.gesetze-im-internet.de/gg/art_3.html //www.gesetze-im-internet.de/gg/art_4.html //www.gesetze-im-internet.de/gg/art_5.html //www.gesetze-im-internet.de/gg/art_6.html //www.gesetze-im-internet.de/gg/art_7.html //www.gesetze-im-internet.de/gg/art_8.html //www.gesetze-im-internet.de/gg/art_9.html //www.gesetze-im-internet.de/gg/art_10.html //www.gesetze-im-internet.de/gg/art_11.html //www.gesetze-im-internet.de/gg/art_12.html //www.gesetze-im-internet.de/gg/art_12a.html //www.gesetze-im-internet.de/gg/art_13.html //www.gesetze-im-internet.de/gg/art_14.html //www.gesetze-im-internet.de/gg/art_15.html //www.gesetze-im-internet.de/gg/art_16.html //www.gesetze-im-internet.de/gg/art_16a.html //www.gesetze-im-internet.de/gg/art_17.html //www.gesetze-im-internet.de/gg/art_17a.html //www.gesetze-im-internet.de/gg/art_18.html //www.gesetze-im-internet.de/gg/art_19.html

Als nächstes schreibe ich ein bisschen Python-Code in eine Datei, die aufgerufen wird scraper.py, um den HTML- Code dieser Dateien herunterzuladen.

In einem realen Szenario wäre dies zu teuer und Sie würden stattdessen eine Datenbank verwenden. Um die Sache einfach zu halten, lade ich Dateien in dasselbe Verzeichnis neben dem Geschäft herunter und verwende ihren Namen als Dateinamen.

from os import path from pathlib import PurePath import requests with open('urls.txt', 'r') as fh: urls = fh.readlines() urls = [url.strip() for url in urls] # strip `\n` for url in urls: file_name = PurePath(url).name file_path = path.join('.', file_name) text = '' try: response = requests.get(url) if response.ok: text = response.text except requests.exceptions.ConnectionError as exc: print(exc) with open(file_path, 'w') as fh: fh.write(text) print('Written to', file_path)

Durch das Herunterladen der Dateien kann ich sie lokal so oft verarbeiten, wie ich möchte, ohne von einem Server abhängig zu sein. Versuchen Sie, ein guter Web-Bürger zu sein, okay?

Schritt 2: Analysieren Sie die Quelle

Nachdem ich die Dateien heruntergeladen habe, ist es Zeit, ihre interessanten Funktionen zu extrahieren. Daher gehe ich zu einer der heruntergeladenen Seiten, öffne sie in einem Webbrowser und drücke Strg-U, um die Quelle anzuzeigen. Wenn ich es inspiziere, sehe ich die HTML-Struktur.

In meinem Fall dachte ich, ich möchte den Gesetzestext ohne Aufschlag. Das Element, das es umschließt, hat eine ID von container. Mit BeautifulSoup kann ich sehen, dass eine Kombination von findund get_texttun wird, was ich will.

Da ich jetzt einen zweiten Schritt habe, werde ich den Code ein wenig umgestalten, indem ich ihn in Funktionen einfüge und eine minimale CLI hinzufüge.

from os import path from pathlib import PurePath import sys from bs4 import BeautifulSoup import requests def download_urls(urls, dir): paths = [] for url in urls: file_name = PurePath(url).name file_path = path.join(dir, file_name) text = '' try: response = requests.get(url) if response.ok: text = response.text else: print('Bad response for', url, response.status_code) except requests.exceptions.ConnectionError as exc: print(exc) with open(file_path, 'w') as fh: fh.write(text) paths.append(file_path) return paths def parse_html(path): with open(path, 'r') as fh: content = fh.read() return BeautifulSoup(content, 'html.parser') def download(urls): return download_urls(urls, '.') def extract(path): return parse_html(path) def transform(soup): container = soup.find(id='container') if container is not None: return container.get_text() def load(key, value): d = {} d[key] = value return d def run_single(path): soup = extract(path) content = transform(soup) unserialised = load(path, content.strip() if content is not None else '') return unserialised def run_everything(): l = [] with open('urls.txt', 'r') as fh: urls = fh.readlines() urls = [url.strip() for url in urls] paths = download(urls) for path in paths: print('Written to', path) l.append(run_single(path)) print(l) if __name__ == "__main__": args = sys.argv if len(args) is 1: run_everything() else: if args[1] == 'download': download([args[2]]) print('Done') if args[1] == 'parse': path = args[2] result = run_single(path) print(result) 

Jetzt kann ich den Code auf drei Arten ausführen:

  1. Ohne Argumente, um alles auszuführen (dh alle URLs herunterzuladen und zu extrahieren und dann auf der Festplatte zu speichern) über: python scraper.py
  2. Mit einem Argument von downloadund einer URL zum Herunterladen python scraper.py download //www.gesetze-im-internet.de/gg/art_1.html. Dadurch wird die Datei nicht verarbeitet.
  3. Mit einem Argument von parseund einem Dateipfad zum Parsen : python scraper.py art_1.html. Dadurch wird der Download-Schritt übersprungen.

Damit fehlt noch eine letzte Sache.

Schritt 3: Formatieren Sie die Quelle für die weitere Verarbeitung

Angenommen, ich möchte für jeden Artikel eine Wortwolke generieren. Dies kann eine schnelle Möglichkeit sein, sich ein Bild davon zu machen, worum es in einem Text geht. Installieren Sie dazu das Paket wordcloudund aktualisieren Sie die Datei wie folgt:

from os import path from pathlib import Path, PurePath import sys from bs4 import BeautifulSoup import requests from wordcloud import WordCloud STOPWORDS_ADDENDUM = [ 'Das', 'Der', 'Die', 'Diese', 'Eine', 'In', 'InhaltsverzeichnisGrundgesetz', 'im', 'Jede', 'Jeder', 'Kein', 'Sie', 'Soweit', 'Über' ] STOPWORDS_FILE_PATH = 'stopwords.txt' STOPWORDS_URL = '//raw.githubusercontent.com/stopwords-iso/stopwords-de/master/stopwords-de.txt' def download_urls(urls, dir): paths = [] for url in urls: file_name = PurePath(url).name file_path = path.join(dir, file_name) text = '' try: response = requests.get(url) if response.ok: text = response.text else: print('Bad response for', url, response.status_code) except requests.exceptions.ConnectionError as exc: print(exc) with open(file_path, 'w') as fh: fh.write(text) paths.append(file_path) return paths def parse_html(path): with open(path, 'r') as fh: content = fh.read() return BeautifulSoup(content, 'html.parser') def download_stopwords(): stopwords = '' try: response = requests.get(STOPWORDS_URL) if response.ok: stopwords = response.text else: print('Bad response for', url, response.status_code) except requests.exceptions.ConnectionError as exc: print(exc) with open(STOPWORDS_FILE_PATH, 'w') as fh: fh.write(stopwords) return stopwords def download(urls): return download_urls(urls, '.') def extract(path): return parse_html(path) def transform(soup): container = soup.find(id='container') if container is not None: return container.get_text() def load(filename, text): if Path(STOPWORDS_FILE_PATH).exists(): with open(STOPWORDS_FILE_PATH, 'r') as fh: stopwords = fh.readlines() else: stopwords = download_stopwords() # Strip whitespace around stopwords = [stopword.strip() for stopword in stopwords] # Extend stopwords with own ones, which were determined after first run stopwords = stopwords + STOPWORDS_ADDENDUM try: cloud = WordCloud(stopwords=stopwords).generate(text) cloud.to_file(filename.replace('.html', '.png')) except ValueError: print('Could not generate word cloud for', key) def run_single(path): soup = extract(path) content = transform(soup) load(path, content.strip() if content is not None else '') def run_everything(): with open('urls.txt', 'r') as fh: urls = fh.readlines() urls = [url.strip() for url in urls] paths = download(urls) for path in paths: print('Written to', path) run_single(path) print('Done') if __name__ == "__main__": args = sys.argv if len(args) is 1: run_everything() else: if args[1] == 'download': download([args[2]]) print('Done') if args[1] == 'parse': path = args[2] run_single(path) print('Done')

Was hat sich geändert? Zum einen habe ich eine Liste deutscher Stoppwörter von GitHub heruntergeladen. Auf diese Weise kann ich die häufigsten Wörter aus dem heruntergeladenen Gesetzestext entfernen.

Dann instanziiere ich eine WordCloud-Instanz mit der Liste der heruntergeladenen Stoppwörter und dem Gesetzestext. Es wird in ein Bild mit demselben Basisnamen umgewandelt.

Nach dem ersten Durchlauf stelle ich fest, dass die Liste der Stoppwörter unvollständig ist. Also füge ich zusätzliche Wörter hinzu, die ich aus dem resultierenden Bild ausschließen möchte.

Damit ist der Hauptteil des Web Scraping abgeschlossen.

Bonus: Was ist mit SPAs?

SPAs - oder Single Page Applications - sind Webanwendungen, bei denen die gesamte Erfahrung von JavaScript gesteuert wird, das im Browser ausgeführt wird. Das Herunterladen der HTML-Datei bringt uns daher nicht weit. Was sollen wir stattdessen tun?

Wir werden den Browser benutzen. Mit Selen. Stellen Sie sicher, dass Sie auch einen Treiber installieren. Laden Sie das .tar.gz-Archiv herunter und entpacken Sie es in den binOrdner Ihrer virtuellen Umgebung, damit es von Selenium gefunden wird. In diesem Verzeichnis finden Sie das activateSkript (auf GNU / Linux-Systemen).

Als Beispiel verwende ich hier die Angular-Website. Angular ist ein beliebtes SPA-Framework, das in JavaScript geschrieben wurde und vorerst garantiert von diesem gesteuert wird.

Da der Code langsamer sein wird, erstelle ich eine neue Datei, die dafür aufgerufen crawler.pywird. Der Inhalt sieht folgendermaßen aus:

from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from wordcloud import WordCloud def extract(url): elem = None driver = webdriver.Firefox() driver.get(url) try: found = WebDriverWait(driver, 10).until( EC.visibility_of( driver.find_element(By.TAG_NAME, "article") ) ) # Make a copy of relevant data, because Selenium will throw if # you try to access the properties after the driver quit elem = { "text": found.text } finally: driver.close() return elem def transform(elem): return elem["text"] def load(text, filepath): cloud = WordCloud().generate(text) cloud.to_file(filepath) if __name__ == "__main__": url = "//angular.io/" filepath = "angular.png" elem = extract(url) if elem is not None: text = transform(elem) load(text, filepath) else: print("Sorry, could not extract data")

Hier öffnet Python eine Firefox-Instanz, durchsucht die Website und sucht nach einem Element. Es kopiert seinen Text in ein Wörterbuch, das im transformSchritt ausgelesen und währenddessen in eine WordCloud umgewandelt wird load.

Wenn Sie mit JavaScript-lastigen Websites arbeiten, ist es oft nützlich, Waits zu verwenden und möglicherweise sogar auszuführen execute_script, um bei Bedarf auf JavaScript zurückzugreifen.

Zusammenfassung

Danke, dass du so weit gelesen hast! Fassen wir zusammen, was wir jetzt gelernt haben:

  1. So kratzen Sie eine Website mit Pythons requestsPaket.
  2. Wie man es mit in eine sinnvolle Struktur übersetzt beautifulsoup.
  3. So verarbeiten Sie diese Struktur zu etwas, mit dem Sie arbeiten können.
  4. Was tun, wenn die Zielseite auf JavaScript basiert?

Weiterführende Literatur

Wenn Sie mehr über mich erfahren möchten, können Sie mir auf Twitter folgen oder meine Website besuchen.

Ich bin nicht der erste, der hier auf freeCodeCamp über Web Scraping geschrieben hat. Yasoob Khalid und Dave Gray haben dies auch in der Vergangenheit getan:

Eine Einführung in Web Scraping mit lxml und Python von Timber.io Eine Einführung in Web Scraping mit lxml und PythonPhoto von Fabian Grohs [// unsplash.com/photos/dC6Pb2JdAqs?utm_source=unsplash&utm_medium=referral&utm_content=creditCashT .com / search / photos / web? utm_source = unsplash & utm_medium = referral & utm_content = creditCopyText… freeCodeCamp.org freeCodeCamp.org Besseres Web-Scraping in Python mit Selenium, Beautiful Soup und Pandas von Dave Gray Web ScrapingMit der Programmiersprache Python ist dies möglich Daten schnell und effizient aus dem Internet „kratzen“. Web Scraping ist definiert als:> ein Tool zum Umwandeln der unstrukturierten Daten im Web in maschinenlesbare, strukturierte Daten, die zur Analyse bereit sind. (sou… freeCodeCamp.org freeCodeCamp.org