Um zu verstehen, was Sessions und Cookies eigentlich sind, müssen wir nochmal zum HTTP-Protokoll zurückkehren. HTTP ist nämlich “zustandslos”, es übermittelt keine Informationen über vergangene Seitenaufrufe. Das heißt, dass ein Webserver nicht anhand einer simplen HTTP-Anfrage erkennen kann, ob derselbe Client bereits zuvor eine Anfrage gestellt hat. Etwas formeller ausgedrückt:
HTTP is stateless: there is no link between two requests being successively carried out on the same connection. This immediately has the prospect of being problematic for users attempting to interact with certain pages coherently, for example, using e-commerce shopping baskets. But while the core of HTTP itself is stateless, HTTP cookies allow the use of stateful sessions. Using header extensibility, HTTP Cookies are added to the workflow, allowing session creation on each HTTP request to share the same context, or the same state.
Damit der Webserver denselben Client wiedererkennen kann, ist also eine zusätzliche Information notwendig: ein sogenannter Cookie, den der Server dem Client beim ersten Seitenaufruf übermittelt, und den der Client beim wiederholten Seitenaufruf im Header einer HTTP-Anfrage dem Server wieder zusendet. So kann der Client dem Webserver bei einer HTTP-Anfrage zusätzliche Informationen übermitteln. Formell ausgedrückt:
An HTTP cookie (web cookie, browser cookie) is a small piece of data that a server sends to a user’s web browser. The browser may store the cookie and send it back to the same server with later requests. Typically, an HTTP cookie is used to tell if two requests come from the same browser—keeping a user logged in, for example. It remembers stateful information for the stateless HTTP protocol.
Beim Aufruf einer Webseite werden meist mehrere Cookies als Schlüssel-Wert-Paare übermittelt. Darunter befindet sich insbesondere bei Loginvorgängen häufig ein Cookie, der eine ID für die aktuelle Session, also für eine Benutzersitzung, enthält. Anhand des Session-ID Cookies erkennt der Webserver, dass derselbe Client bereits zuvor die Webseite aufgerufen hat, und die in anderen Cookies gespeicherten Informationen können so beim wiederholten Seitenaufruf wiederverwendet werden. Je nach der Art der Dienste, für die auf einer Webseite Cookies verwendet werden, haben Sessions eine unterschiedliche Gültigkeitsdauer: Beim Login in ein Onlinebanking-Account sind das oft nur wenige Minuten, während Sessions beim Login in ein soziales Netzwerk sogar wochenlang aufrechterhalten werden können. Wenn die Gültigkeit einer Session erlischt, dann wird die Session-ID gelöscht und ein:e Nutzer:in wird beim nächsten Seitenaufruf zum erneuten Login aufgefordert.
Softwareentwickler Valentin Despa hat einen meiner Meinung nach ganz passenden Vergleich zum Verständnis von Cookies vorgeschlagen. Das Prinzip von Session-ID-Cookies ist laut Despa vergleichbar mit einer Garderobe: Bei der Abgabe eines Kleidungsstücks bekommt man einen Abholschein mit einer einzigartigen Nummer, anhand derer die Person an der Garderobe später das abgegebene Kleidungsstück wieder der richtigen Person zuordnen kann. Wenn die Person das Kleidungsstück erhalten hat, hat der Abholschein keine Bedeutung mehr und wird in den Müll geworfen.
Cookies können ebenfalls über die Browser-Entwicklertools eingesehen werden. Nachdem beim Aufruf des Onlineshops conrad.com die Option “Decline Cookies” im Pop-Up-Cookiebanner ausgewählt wurde, sind in den Entwicklertools nur noch die funktionalen Cookies sichtbar, darunter auch ein Cookie mit dem Namen “session-id-cookie” und einer Session-ID als Wert.
Das Beispiel zeigt auch noch einmal, dass Cookiebanner häufig missverständlich formuliert sind: Mit “Cookies ablehnen” sind meist nicht die funktionalen Cookies gemeint, also Cookies, welche für die Funktionalität der Webseite erforderlich sind, sondern nur solche, die zum Verfolgen des Nutzerverhaltens (Tracking) und zu Werbezwecken dienen.
Note
Cookies können auch über die Browser-Entwicklertools gelöscht werden: und zwar mit Ctrl bzw. Cmd + Shift + P und Suche nach “Clear site data”.
Soweit zum Verständnis von Cookies und Sessions. Aber wie können wir denn beim Web Scraping mit Sessions, Cookies und Cookiebannern umgehen?
10.2.1. Cookie und Session Handling mit Selenium: Beispiel Login#
In Selenium haben wir ganz automatisch Sessions verwendet: Jedes Mal, wenn mit webdriver.Chrome() ein neues Webdriver-Objekt initialisiert wird, wird auch eine neue Session, also eine Nutzersitzung gestartet. Im Folgenden schauen wir uns einen Anwendungsfall an, bei dem beim Web Scraping mit Selenium Cookies verwendet werden: ein Login-Vorgang.
Angenommen, wir wollen eine Seite in mehreren Sessions scrapen. Dann müssten wir uns jedes Mal neu einloggen und jedes Mal das Inputfeld mit find_element() suchen, die Inputdaten mit send_keys() eingeben und anschließend die Eingabe mit click() bestätigen. Das können wir vereinfachen, indem wir beim ersten Login die Login-Cookies speichern und bei der nächsten Anfrage direkt die Cookies mitsenden. Wir schauen uns wieder das Code-Beispiel von jemand anderes an:
# Lösung von https://www.youtube.com/watch?v=xbjMnuLIilw# Session starten und Seite abrufen# from selenium import webdriver# from selenium.webdriver.common.selenium_manager import SeleniumManager# import json # alternativ: pickle# url = "https://twitter.com"# cookies_file = "twitter_cookies.json"# driver = webdriver.Chrome()# driver.get(url)
An diesem Punkt öffnet sich das Loginfenster und wir können entweder unsere Logindaten manuell eingeben oder mithilfe von Selenium. Zur Einfachheit halber nehmen wir an, dass wir die Logindaten manuell eingeben.
# Cookies speichern# cookies = driver.get_cookies()# cookies = json.dumps(cookies)# with open(cookies_file, 'w') as f:# f.write(cookies)# driver.quit()
Da Cookies bei der HTTP-Anfrage als Schlüssel-Wert-Paare übergeben werden, bietet es sich an, die Daten im JSON-Format zu speichern. Es gibt aber auch andere Möglichkeiten, zum Beispiel das Python-spezifische Dateiformat pickle, welches nur zum Zwischenspeichern von Python-Objekten konzipiert wurde. Die Datei mit den Login-Cookies kann dann in der nächsten Session eingelesen und die Cookies an den Server gesendet werden:
# Neue Session starten und Login-Cookies übergeben# with open(cookies_file, 'r') as f:# cookies = json.load(f)# driver.get(url)# for cookie in cookies:# driver.add_cookie(cookie)
10.2.2. Cookie und Session Handling mit requests: Beispiel Login#
Bei der Verwendung von requests sind wir bisher nicht mit Sessions und Cookies in Berührung gekommen. Aber auch requests bietet die Möglichkeit, eine Session zu initialisieren und bei einer Anfrage Cookies zu übermitteln.
Ein Session-Objekt kann mit requests wie folgt erstellt werden:
importrequestssession=requests.Session()
Da beim Web Scraping mit requests anders als beim Scrapen mit Selenium nicht automatisch eine Session erstellt wird, muss vor einem Login-Vorgang eine Session initialisiert werden. Nur so können nach der erfolgreichen Anemldung über die Loginseite die Anmeldedaten beim Aufruf der zu scrapenden Seite an den Server übermittelt werden. Das wird am folgenden Beispiel deutlich. Ein Login-Vorgang auf der Seite realpython.com sieht mit requests zum Beispiel so aus:
Der Login-Vorgang wird hier mithilfe einer HTTP-POST-Anfrage vorgenommen. Im Abschnitt “Statisch vs. Dynamisch?” haben wir diese Art der Anfrage bereits am Beispiel der Suche auf projekt-gutenberg.de kennengelernt. Die Logindaten werden dabei als Argument params der post-Methode übergeben. Die post-Methode wird (und das ist wichtig!) aus der aktuellen Nutzersitzung, also der Session, heraus gestellt: Es heißt im Beispiel session.post() und nicht requests.post()!
Zu beachten ist auch, dass in dem Beispiel oben neben den Logindaten noch ein CSRF-Token für den Loginvorgang benötigt wird. CSRF steht für Cross-Site Request Forgery und beschreibt eine bestimmte Art eines Angriffs, der verhindert werden soll, indem der Server bei jeder Anfrage überprüft, ob der Client einen CSRF-Token mitsendet, der dem Token entspricht, den der Server dem Client beim Login übermittelt hat. Es wird damit also einfach gesagt überprüft, ob ein Link, auf den ein:e angemeldete:r Nutzer:in zugreifen will, schädlich ist und dem Client womöglich durch einen Dritten (“Hacker”) untergejubelt wurde: In diesem Fall würde bei der Anfrage nämlich kein gültiges Token übermittelt werden. Eine ausführlichere Erläuterung findet ihr hier.
Wie genau das CSRF-Token und die Loginparameter bei der Anfrage heißen müssen, kann nach der manuellen Anmeldung auf der Seite wieder dem Network-Tab in den Browser-Entwicklertools entnommen werden. Dazu müssen einfach die Anfrageparameter zur Anfrage für die Seite https://realpython.com/account/login/ unter “Payload” und “Cookies” betrachtet werden.
Nachdem der Login-Vorgang erfolgt ist, kann regulär eine HTTP-GET-Anfrage für die gesuchten Inhalte durchgeführt werden, in diesem Fall für eine Seite mit Notifications, die nur eingeloggte Nutzer:innen sehen können:
Wichtig ist dabei allerdings, dass die Anfrage wieder aus der aktuellen Nutzersitzung heraus gestellt wird, also wieder mit session.get() anstelle von requests.get():
Der Beispiel-Loginvorgang auf realpython.com ist auf viele Anwendungsfälle übertragbar. Verschiedene Webseiten nutzen aber verschiedene Authentifizierungsverfahren und ein Blick in die Entwicklertools ist beim Schreiben des Codes unerlässlich. Und nicht immer muss zum Login eine HTTP-POST-Methode angewandt werden. Für den Login auf Webseiten, welche Authentifizierungsverfahren wie beispielsweise OAuth verwenden, ist die Anmeldung in requests vorimplementiert. Dazu mehr hier: https://requests.readthedocs.io/en/latest/user/authentication/.
Wenn die Seite google.com mithilfe von Selenium und dem Chrome Webdriver aufgerufen wird, dann öffnet sich ein Cookiebanner, also ein Pop-Up-Fenster, das Nutzer:innen auffordert, der Verwendung von Cookies durch die Seite zuzustimmen:
Um mithilfe von Selenium die optionalen Cookies abzulehnen, muss einfach nur der “Reject All”-Button geklickt werden, und dazu suchen wir den Button und simulieren den Mausklick mit der bereits bekannten Methode click():
# from selenium import webdriver# from selenium.webdriver.common.selenium_manager import SeleniumManager# from selenium.webdriver.common.by import By# driver = webdriver.Chrome()# driver.get("https://www.google.com/")# driver.find_element(By.ID, "W0wltc").click()# driver.quit()
Zur Anzeigen von Cookiebannern wird JavaScript verwendet. Das heißt, dass bei einer Anfrage mit requests die Seite ohne den Cookiebanner angezeigt wird: das Pop-Up-Fenster öffnet sich gar nicht erst. Heißt das aber, dass beim Web Scrapen mit requests und BeautifulSoup Cookiebanner gar nicht relevant sind? Nicht ganz, denn es kommt auch vor, dass beim erstmaligen Aufruf einer Seite eine Weiterleitung auf eine Cookie-Consent-Seite erfolgt. Anders ausgedrückt: eine Anfrage mit requests, die keine Cookies im Anfrage-Header mit übergibt, wird auf eine Seite umgeleitet, welche die Einwilligung in die Verwendung von Cookies abfragt. Erst, wenn ein:e Nutzer:in der Verwendung von Cookies zugestimmt hat, wird beim erneuten Seitenaufruf die ursprünglich angefragte Seite angezeigt. Das ist beispielsweise der Fall beim Aufruf von google.com mit deaktiviertem JavaScript. Mit deaktiviertem JavaScript wird anstatt der Anzeige des Cookiebanner-Pop-Up-Fensters die Anfrage auf eine separate Cookie-Consent-Seite weitergeleitet:
Entsprechend wird beim Versuch, Suchergebnisse mit requests und BeautifulSoup zu scrapen, die Cookie-Consent-Seite zurückgegeben und nicht die Seite mit den Suchergebnissen:
In den Browser-Entwicklertools unter Network -> Cookies können die Cookies, die beim Seitenaufruf im Header der HTTP-Anfrage an den Webserver geschickt werden, eingesehen werden:
Beim erstmaligen Aufruf der Seite wird ein Cookie mit dem Namen “CONSENT” und dem Wert “PENDING+612” (die Zahl kann jedoch variieren) mitgesandt. Diesen Cookie können wir bei der Anfrage mit Requests im Anfrage-Header mit angeben. Dabei müssen wir den Wert des CONSENT-Cookies allerdings auf “YES+” setzen. Die Anfrage gibt dann die angefragte Seite zurück und nicht die Cookie-Consent-Seite:
Wie genau der Consent-Cookie heißt und welcher Wert eingesetzt werden muss, variiert, aber das Prinzip ist auch auf andere Webseiten übertragbar. Um herauszufinden, wie der Cookie heißt, helfen euch wie in diesem Beispiel die Entwicklertools weiter.