Modernes Web Scraping mit Python und Selenium

Veröffentlicht: 2022-03-11

Web Scraping wurde fast seit der Geburt des World Wide Web zum Extrahieren von Daten aus Websites verwendet. In den frühen Tagen wurde Scraping hauptsächlich auf statischen Seiten durchgeführt – solchen mit bekannten Elementen, Tags und Daten.

In jüngerer Zeit haben fortschrittliche Technologien in der Webentwicklung die Aufgabe jedoch etwas erschwert. In diesem Artikel werden wir untersuchen, wie wir beim Scraping von Daten vorgehen könnten, falls neue Technologien und andere Faktoren das Standard-Scraping verhindern.

Herkömmliches Data Scraping

Da die meisten Websites Seiten erstellen, die eher für die menschliche Lesbarkeit als für das automatische Lesen gedacht sind, bestand das Web-Scraping hauptsächlich darin, die Markup-Daten einer Webseite programmatisch zu verdauen (denken Sie an einen Rechtsklick, Quelle anzeigen) und dann statische Muster in diesen Daten zu erkennen, die das Programm ermöglichen würden verschiedene Informationen zu „lesen“ und in einer Datei oder Datenbank zu speichern.

Daten-Scraping

Wenn Berichtsdaten gefunden werden, sind die Daten häufig zugänglich, indem entweder Formularvariablen oder Parameter mit der URL übergeben werden. Zum Beispiel:

 https://www.myreportdata.com?month=12&year=2004&clientid=24823

Python ist zu einer der beliebtesten Web-Scraping-Sprachen geworden, teilweise aufgrund der verschiedenen Webbibliotheken, die dafür erstellt wurden. Eine beliebte Bibliothek, Beautiful Soup, wurde entwickelt, um Daten aus HTML- und XML-Dateien herauszuziehen, indem sie das Suchen, Navigieren und Ändern von Tags (dh dem Parse-Baum) ermöglicht.

Browserbasiertes Scraping

Kürzlich hatte ich ein Scraping-Projekt, das ziemlich einfach schien, und ich war voll und ganz darauf vorbereitet, traditionelles Scraping zu verwenden, um es zu handhaben. Aber als ich mich weiter damit befasste, stieß ich auf Hindernisse, die mit traditionellen Methoden nicht überwunden werden konnten.

Drei Hauptprobleme haben mich von meinen Standard-Scraping-Methoden abgehalten:

  1. Zertifikat. Für den Zugriff auf den Teil der Website, auf dem sich die Daten befanden, musste ein Zertifikat installiert werden. Beim Zugriff auf die Startseite wurde eine Eingabeaufforderung angezeigt, in der ich aufgefordert wurde, das richtige Zertifikat der auf meinem Computer installierten auszuwählen und auf OK zu klicken.
  2. IFrames. Die Seite verwendete Iframes, was mein normales Scraping durcheinander brachte. Ja, ich könnte versuchen, alle Iframe-URLs zu finden und dann eine Sitemap zu erstellen, aber das schien unhandlich zu werden.
  3. JavaScript. Der Zugriff auf die Daten erfolgte nach dem Ausfüllen eines Formulars mit Parametern (z. B. Kunden-ID, Zeitraum usw.). Normalerweise würde ich das Formular umgehen und einfach die Formularvariablen (über URL oder als versteckte Formularvariablen) an die Ergebnisseite übergeben und die Ergebnisse anzeigen. Aber in diesem Fall enthielt das Formular JavaScript, wodurch ich nicht normal auf die Formularvariablen zugreifen konnte.

Also beschloss ich, meine traditionellen Methoden aufzugeben und nach einem möglichen Tool für browserbasiertes Scraping zu suchen. Dies würde anders als normal funktionieren – anstatt direkt zu einer Seite zu gehen, den Analysebaum herunterzuladen und Datenelemente herauszuziehen, würde ich mich stattdessen „wie ein Mensch verhalten“ und einen Browser verwenden, um zu der Seite zu gelangen, die ich brauche, und dann die Seite kratzen Daten - und umgeht so die Notwendigkeit, sich mit den genannten Hindernissen auseinanderzusetzen.

Selen

Im Allgemeinen ist Selenium als Open-Source-Testframework für Webanwendungen bekannt, das es QA-Spezialisten ermöglicht, automatisierte Tests durchzuführen, Wiedergaben auszuführen und Fernsteuerungsfunktionen zu implementieren (was viele Browserinstanzen für Lasttests und mehrere Browsertypen zulässt). In meinem Fall schien dies nützlich zu sein.

Meine bevorzugte Sprache für Web Scraping ist Python, da es gut integrierte Bibliotheken hat, die im Allgemeinen alle erforderlichen Funktionen verarbeiten können. Und tatsächlich existiert eine Selenium-Bibliothek für Python. Auf diese Weise könnte ich einen „Browser“ – Chrome, Firefox, IE usw. – instanziieren und dann so tun, als würde ich den Browser selbst verwenden, um Zugriff auf die gesuchten Daten zu erhalten. Und wenn ich nicht wollte, dass der Browser tatsächlich erscheint, könnte ich den Browser im „headless“-Modus erstellen, wodurch er für jeden Benutzer unsichtbar wird.

Projektaufbau

Um mit dem Experimentieren zu beginnen, musste ich mein Projekt einrichten und alles bekommen, was ich brauchte. Ich habe einen Windows 10-Computer verwendet und sichergestellt, dass ich eine relativ aktuelle Python-Version habe (es war v. 3.7.3). Ich habe ein leeres Python-Skript erstellt und dann die Bibliotheken geladen, von denen ich dachte, dass sie erforderlich sein könnten, indem ich PIP (Paketinstallationsprogramm für Python) verwendet habe, wenn ich die Bibliothek nicht bereits geladen hatte. Dies sind die wichtigsten Bibliotheken, mit denen ich angefangen habe:

  1. Anfragen (um HTTP-Anfragen zu stellen)
  2. URLLib3 (URL-Handhabung)
  3. Schöne Suppe (falls Selenium nicht alles bewältigen konnte)
  4. Selenium (für browserbasierte Navigation)

Ich habe dem Skript auch einige Aufrufparameter hinzugefügt (unter Verwendung der argparse-Bibliothek), damit ich mit verschiedenen Datensätzen herumspielen und das Skript von der Befehlszeile mit verschiedenen Optionen aufrufen konnte. Dazu gehörten Kunden-ID, Von-Monat/Jahr und Bis-Monat/Jahr.

Problem 1 – Das Zertifikat

Die erste Wahl, die ich treffen musste, war, welchen Browser ich Selenium mitteilen wollte. Da ich im Allgemeinen Chrome verwende und es auf dem Open-Source-Chromium-Projekt basiert (das auch von Edge-, Opera- und Amazon Silk-Browsern verwendet wird), dachte ich, ich würde das zuerst versuchen.

Ich konnte Chrome im Skript starten, indem ich die benötigten Bibliothekskomponenten hinzufügte und dann ein paar einfache Befehle ausgab:

 # Load selenium components from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException # Establish chrome driver and go to report site URL url = "https://reportdata.mytestsite.com/transactionSearch.jsp" driver = webdriver.Chrome() driver.get(url)

Da ich den Browser nicht im Headless-Modus gestartet habe, erschien der Browser tatsächlich und ich konnte sehen, was er tat. Es forderte mich sofort auf, ein Zertifikat auszuwählen (das ich zuvor installiert hatte).

Das erste Problem, das angegangen werden musste, war das Zertifikat. Wie wähle ich die richtige aus und akzeptiere sie, um auf die Website zu gelangen? Bei meinem ersten Test des Skripts erhielt ich diese Eingabeaufforderung:

Daten-Scraping

Das war nicht gut. Ich wollte nicht jedes Mal manuell auf die Schaltfläche OK klicken, wenn ich mein Skript ausführe.

Wie sich herausstellte, konnte ich dafür einen Workaround finden - ohne Programmierung. Ich hatte zwar gehofft, dass Chrome beim Start einen Zertifikatsnamen übergeben könnte, aber diese Funktion war nicht vorhanden. Chrome hat jedoch die Möglichkeit, ein Zertifikat automatisch auszuwählen, wenn ein bestimmter Eintrag in Ihrer Windows-Registrierung vorhanden ist. Sie können es so einstellen, dass es das erste Zertifikat auswählt, das es sieht, oder genauer sein. Da ich nur ein Zertifikat geladen hatte, habe ich das generische Format verwendet.

Daten-Scraping

Wenn ich also Selenium mit diesem Satz sagte, Chrome zu starten, und eine Zertifikatsaufforderung angezeigt wurde, würde Chrome das Zertifikat automatisch auswählen und fortfahren.

Problem 2 – IFrames

Okay, jetzt war ich auf der Website und ein Formular erschien, in dem ich aufgefordert wurde, die Kunden-ID und den Datumsbereich des Berichts einzugeben.

Daten-Scraping

Bei der Untersuchung des Formulars in den Entwicklertools (F12) ist mir aufgefallen, dass das Formular in einem Iframe dargestellt wurde. Bevor ich also mit dem Ausfüllen des Formulars beginnen konnte, musste ich zu dem richtigen Iframe „wechseln“, in dem das Formular vorhanden war. Dazu habe ich die Switch-to-Funktion von Selenium wie folgt aufgerufen:

 # Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)

Gut, jetzt im rechten Frame konnte ich die Komponenten bestimmen, das Kunden-ID-Feld ausfüllen und die Datums-Dropdowns auswählen:

 # Find the Customer ID field and populate it element = driver.find_element_by_name("custId") element.send_keys(custId) # send a test id # Find and select the date drop-downs select = Select(driver.find_element_by_name("fromMonth")) select.select_by_visible_text(from_month) select = Select(driver.find_element_by_name("fromYear")) select.select_by_visible_text(from_year) select = Select(driver.find_element_by_name("toMonth")) select.select_by_visible_text(to_month) select = Select(driver.find_element_by_name("toYear")) select.select_by_visible_text(to_year)

Aufgabe 3 – JavaScript

Das einzige, was auf dem Formular übrig blieb, war, auf die Schaltfläche „Suchen“ zu „klicken“, damit die Suche beginnen würde. Dies war ein wenig knifflig, da die Schaltfläche „Suchen“ anscheinend von JavaScript gesteuert wurde und keine normale Schaltfläche vom Typ „Senden“ war. Als ich es in den Entwicklertools untersuchte, fand ich das Schaltflächenbild und konnte den XPath davon abrufen, indem ich mit der rechten Maustaste klickte.

Daten-Scraping

Dann, bewaffnet mit diesen Informationen, fand ich das Element auf der Seite und klickte darauf.

 # Find the 'Find' button, then click it driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()

Und voila, das Formular wurde abgeschickt und die Daten erschienen! Jetzt könnte ich einfach alle Daten auf der Ergebnisseite zusammenkratzen und nach Bedarf speichern. Oder könnte ich?

Abrufen der Daten

Zuerst musste ich den Fall bearbeiten, wo die Suche nichts ergab. Das war ziemlich einfach. Es würde eine Nachricht im Suchformular anzeigen, ohne es zu verlassen, etwa „Keine Datensätze gefunden“. Ich habe einfach nach dieser Zeichenfolge gesucht und genau dort aufgehört, wenn ich sie gefunden habe.

Aber wenn Ergebnisse kamen, wurden die Daten in Divs mit einem Pluszeichen (+) dargestellt, um eine Transaktion zu öffnen und alle Details anzuzeigen. Eine geöffnete Transaktion zeigte ein Minuszeichen (-), das beim Anklicken das Div schließen würde. Durch Klicken auf ein Pluszeichen würde eine URL aufgerufen, um ihr div zu öffnen und jedes geöffnete zu schließen.

Daten-Scraping

Daher war es notwendig, alle Pluszeichen auf der Seite zu finden, die URL neben jedem zu sammeln und dann alle zu durchlaufen, um alle Daten für jede Transaktion zu erhalten.

 # Loop through transactions and count links = driver.find_elements_by_tag_name('a') link_urls = [link.get_attribute('href') for link in links] thisCount = 0 isFirst = 1 for url in link_urls: if (url.find("GetXas.do?processId") >= 0): # URL to link to transactions if isFirst == 1: # already expanded + isFirst = 0 else: driver.get(url) # collapsed +, so expand # Find closest element to URL element with correct class to get tran type tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text # Get transaction status status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text # Add to count if transaction found if (tran_type in ['Move In','Move Out','Switch']) and (status == "Complete"): thisCount += 1

Im obigen Code waren die von mir abgerufenen Felder der Transaktionstyp und der Status, die dann zu einer Zählung hinzugefügt wurden, um zu bestimmen, wie viele Transaktionen den angegebenen Regeln entsprechen. Ich hätte jedoch andere Felder innerhalb der Transaktionsdetails abrufen können, wie Datum und Uhrzeit, Subtyp usw.

Für dieses Projekt wurde die Zählung an eine aufrufende Anwendung zurückgegeben. Es und andere abgekratzte Daten hätten jedoch auch in einer Flatfile oder einer Datenbank gespeichert werden können.

Zusätzliche mögliche Hindernisse und Lösungen

Beim Scrapen moderner Websites mit Ihrer eigenen Browserinstanz können zahlreiche andere Hindernisse auftreten, aber die meisten können gelöst werden. Hier sind ein paar:

  • Versuchen, etwas zu finden, bevor es erscheint

    Wie oft stellen Sie beim Surfen fest, dass Sie darauf warten, dass eine Seite erscheint, manchmal viele Sekunden lang? Nun, dasselbe kann beim programmgesteuerten Navigieren auftreten. Sie suchen nach einer Klasse oder einem anderen Element – ​​und es ist nicht da!

    Glücklicherweise hat Selenium die Fähigkeit zu warten, bis es ein bestimmtes Element sieht, und kann eine Zeitüberschreitung verursachen, wenn das Element nicht erscheint, wie folgt:

 element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))


  • Durch ein Captcha kommen

    Einige Websites verwenden Captcha oder ähnliches, um unerwünschte Roboter zu verhindern (die sie möglicherweise für Sie halten). Dies kann das Kratzen von Bahnen dämpfen und verlangsamen.

Bei einfachen Eingabeaufforderungen (wie „Was ist 2 + 3?“) können diese im Allgemeinen leicht gelesen und verstanden werden. Für fortgeschrittenere Barrieren gibt es jedoch Bibliotheken, die helfen können, sie zu knacken. Einige Beispiele sind 2Captcha, Death by Captcha und Bypass Captcha.

  • Strukturelle Änderungen der Website

    Websites sollen sich verändern – und das tun sie oft. Deshalb ist es am besten, dies beim Schreiben eines Scraping-Skripts im Hinterkopf zu behalten. Sie sollten darüber nachdenken, welche Methoden Sie verwenden, um die Daten zu finden, und welche nicht. Erwägen Sie partielle Matching-Techniken, anstatt zu versuchen, einen ganzen Satz zu matchen. Beispielsweise könnte eine Website eine Meldung von „Keine Datensätze gefunden“ in „Keine Datensätze gefunden“ ändern – aber wenn Ihr Match auf „Keine Datensätze“ steht, sollten Sie in Ordnung sein. Überlegen Sie auch, ob Sie nach XPATH, ID, Name, Linktext, Tag- oder Klassenname oder CSS-Selektor suchen – und was sich am wenigsten ändern wird.

Zusammenfassung: Python und Selen

Dies war eine kurze Demonstration, um zu zeigen, dass fast jede Website gescraped werden kann, unabhängig davon, welche Technologien verwendet werden und welche Komplexität damit verbunden ist. Wenn Sie die Website selbst durchsuchen können, kann sie grundsätzlich gescraped werden.

Nun, als Einschränkung bedeutet dies nicht, dass jede Website gekratzt werden sollte . Einige haben legitime Einschränkungen, und es gab zahlreiche Gerichtsverfahren, in denen über die Rechtmäßigkeit des Scrapings bestimmter Websites entschieden wurde. Auf der anderen Seite begrüßen und fördern einige Websites den Abruf von Daten von ihrer Website und stellen in einigen Fällen eine API zur Verfügung, um die Dinge zu vereinfachen.

In jedem Fall ist es am besten, die Allgemeinen Geschäftsbedingungen zu überprüfen, bevor Sie mit einem Projekt beginnen. Aber wenn Sie weitermachen, können Sie sicher sein, dass Sie die Arbeit erledigen können.

Empfohlene Ressourcen für komplexes Web Scraping:

  • Fortgeschrittenes Python Web Scraping: Best Practices & Problemumgehungen
  • Skalierbares Do-it-yourself-Scraping: So bauen und betreiben Sie Scraper im großen Maßstab