Erschlagendes Angebot im Webshop
Vor zwei Wochen wurde ich von einem oft genutzten Online-Versand, dessen Namen an ein Fluss in Südamerika erinnert, per freundlicher Info-Mail auf eine Aktion aufmerksam gemacht. Und zwar wurden mir drei Musik-CDs aus einer großen Auswahl für 15€ angeboten.
Ich erwerbe immer noch gerne, wie früher, Musik auf physischen Tonträgern und wollte mir das Angebot genauer ansehen. Nun stellte sich heraus, dass etwa 9,000 CDs offeriert wurden, und das über etwa 400 Seiten im Online-Shop. Dieser Shop bietet mir die Möglichkeit, das Angebot nach Beliebtheit oder nach Kundenbewertung zu sortieren. Wenn ich jedoch die Beliebtheit absteigend betrachte, finde ich viele Titel, die nicht mehr ganz meiner Altersklasse entsprechen. Andererseits, wenn ich nach Kundenbewertung sortiere, stellt sich heraus, dass der Shop die Bewertungen ungewichtet verarbeitet. D.h. irgendeine CD mit volkstümlichen Schlagern wird mit nur einer 5-Sterne Bewertung vor einer anderen CD mit 4.9 Sternen auf 1000 Bewertungen aufgeführt.
Web scraping
Ich hatte zunächst keine Lust mir alle 400 Seiten händisch durchzusehen, ob was von Interesse für mich dabei ist. Daher griff ich auf einen Trick zurück, den ich früher schon häufiger eingesetzt hab, und zwar den Content der Website automatisch abzuernten. Dieses Vorgehen ist alles andere als neu, jetzt aber hat das Kind im Rahmen der Data Science Community einen neuen Namen und zwar web scraping.
Es ist nicht unwahrscheinlich, dass man als Data Scientist selbst einmal Daten aus dem Netz saugen muss. Darum will ich anhand meines einfachen Problem zeigen, wie gering die Einstiegshürde hierfür mit Python sein kann.
Ran ans Werk
URL
Zum Glück verwendet der Online-Anbieter eine Request-Methode, welche die Parameter des Requests in der URL im Klartext darlegt. Hier ein Beispiel, das ich etwas anonymisiert habe:
http://www.onlineshop.de/s/ref=lp_12345_pg_1&rh=123456&page=1&ie=UTF8&qid=12345
Man erkennt, dass die Seite an zwei Stellen referenziert wird, einmal bei _pg_1 und bei &page=1&. Wenn ich also diese beiden Stellen anpasse, kann ich direkt durch alle Unterseiten iterieren.
Abrufen der Website
Um die Website nun tatsächlich zu lesen, verwenden wir ein Modul, dass in Python fest integriert ist, nämlich urllib. Das Laden der Seite sieht dann so aus (Die URL ist von mir wieder verstümmelt.):
from urllib import request
url_requested = request.urlopen('http://www.onlineshop.de/s/ref=lp_12345_pg_1&rh=[...]&page=1&ie=UTF8&qid=12345')
if 200 == url_requested.code:
html_content = str(url_requested.read())
Man verwendet das Request-Objekt, um eine Website zu öffnen. Der HTML-Code wird ausgelesen, wenn der Code des Request 200 ist, der übliche Code für das erfolgreiche Aufrufen einer Website. Wir kennen alle den 404 wenn die Seite z.B. nicht gefunden wird. Die String-Funktion um den Leseschritt ist notwendig, um auch wirklich strings und nicht bytestrings zu erhalten. Das würde uns sonst später Probleme bereiten.
Parsen des HTML-Codes
Jetzt wo wir den HTML-Code der Seite haben, können wir ihn nach CD-Titel und den Interpreten durchsuchen. Ich habe zunächst vorher mir direkt den HTML-Code über den Browser angesehen, um verdächtige Muster zu entdecken, nach denen man suchen kann. Dabei ist mir aufgefallen, dass alle Titel mit <h2 class="a-size-medium a-color-null s-inline s-access-title a-text-normal"> beginnen und schließlich mit Audio CD aufhören. Dazwischen befindet sich noch eine Menge für uns unwichter Code.
Wir schneiden aus der Seite nun die jeweiligen Code-Stellen mir Regulären Ausdrücken aus und speichern uns die Angaben in eine Liste Weg.
import re
[...]
page_content=[]
html_content = str(url_requested.read())
re_search_chunk = re.findall('<h2 class="a-size-medium a-color-null s-inline s-access-title a-text-normal">.*?Audio CD', html_content)
for result in re_search_chunk:
tag_contents = re.findall('>.*?<', result)
album = tag_contents[0][1:-1]
artist = tag_contents[13][1:-1]
page_content.append((album, artist))
re.findall gehört zum Package für Reguläre Ausdrücke. Damit Suche ich alle Stellen im HTML-Code, welche dem obigen Muster entsprechen, wobei .*? der Platzhalter für eine beliebige Kette ist. Dieser Platzhalter ist hier "not greedy", das heißt er versucht den unbekannten Bereich auf so wenig wie möglich Zeichen zu matchen, sonst würde man mehrere Alben gleichzeitig erfassen.
Für jedes der gemachten Code-Schnippsel wiederhole ich den Prozess mit dem Suchschema '>.*?<', das mir nun "not greedy" die einzelnen Inhalte der Tags zurückgibt. Durch "kritisches Betrachten des Codes" kann ich feststellen, dass der Albumtitel im ersten Tag (Index 0) und der Künstler im 14ten Tag (Index 13) gespeichert ist. Um nun die Klammern der Tags noch loszuwerden, indizieren wir die Ergebnisse auf das zweite bis vorletzte Zeichen ([1:-1])
Das Ergebnis wird als Tupel an meine Ergebnisliste page_content angehängt.
Wegschreiben in eine CSV
with open('offers.txt', 'w') as f:
for row in page_content:
print('\t'.join(row), file=f)
Am Schluss kommt der einfache Part. Wir öffnen einen "file handle" und schreiben jedes Album-Artist-Tuple mit Tabulator getrennt in das File. Ich verwende hier das print-Literal aus Python3, welches für jeden Print-Befehl von selbst einen Zeilenumbruch einfügt und welches den file handle als Ziel-Parameter für die Ausgabe akzeptiert.
Gesamt-Script
Den Part mit dem Auslesen der Website muss man natürlich in einer Schleife abwickeln und dabei die Seitenzahl parametrisieren. Hier wäre das Gesamtscript:
import re
from urllib import request
page_content=[]
for page_number in range(400):
url_requested = request.urlopen('http://www.onlineshop.de/s/ref=lp_12345_pg_{page_number}&rh=[...]&page={page_number}&ie=UTF8&qid=12345'.format(page_number=page_number+1)
if 200 == url_requested.code:
html_content = str(url_requested.read())
re_search_chunk = re.findall('<h2 class="a-size-medium a-color-null s-inline s-access-title a-text-normal">.*?Audio CD', html_content)
for result in re_search_chunk:
tag_contents = re.findall('>.*?<', result)
album = tag_contents[0][1:-1]
artist = tag_contents[13][1:-1]
page_content.append((album, artist))
with open('offers.txt', 'w') as f:
for row in page_content:
print('\t'.join(row), file=f)
Zack fertig!
Sichtung der Daten
So, meine Datei offers.txt kann ich nun in Excel z.B. öffnen und mir mit einer Pivot-Tabelle ansehen. Das erleichtert mir nun enorm, überhaupt erstmal die am Angebot beteiligten Interpreten zu überblicken.
Ich denke, ich habe mit unter eine halben Stunde etwa solange für das Script gebraucht, wie ich für das Durchklicken der Seiten benötigt hätte, dafür hatte ich mehr Spass. Und da ich das Script in Wahrheit in Form einer Klasse mit den Teilschritten als Methoden geschrieben habe, werde ich es bei ähnlichen Szenarien wahrscheinlich wieder hernehmen können.