Automatisierte Extraktion von Rohstoffpreisen aus HTML basierten Dokumenten
Ein im ETL-Kontext häufiger Anwendungsfall ist die periodische Extraktion beliebiger Zeichenketten aus heterogenen Datenquellen. Ziel dieses Artikel ist, am Beispiel der beiden Industriemetalle Aluminium und Kupfer zu demonstrieren, wie mit vergleichsweise geringem Aufwand ein Monitoring von Rohstroffpreisen realisiert werden kann. Die tragende Technologie im Hinblick des Extraktionsprozesses wird hierbei die vielseitige Programmiersprache PHP sein. Die Speicherung der Rohstoffpreise wird MongoDB übernehmen und zur Koordinierung der einzelnen Elemente findet ein wenige Zeilen umfassendes Bash-Script, welches periodisch vom cron Daemon gestartet wird, Verwendung.
Vor dem Hintergrund, dass bekanntlich viele Wege nach Rom führen, wird einigen Entwicklern bei der beschriebenen Aufgabe wahrscheinlich die Verwendung von Regulären Ausdrücken zur Extraktion der Preise in den Sinn kommen. Dies mag in vielen Fällen auch durchaus zielführend sein. Allerdings wird früher oder später immer der Moment eintreten, in dem die schmerzhafte Einsicht, dass HTML nicht durch reguläre Ausdrücke zu parsen ist, unvermeidlich ist.
Spaß beiseite – ein Weg zur Extraktion einzelner Werte aus HTML Dokumenten, der weniger Kopfzerbrechen bereitet und in der Regel nicht minder bequem sein dürfte, ist die Verwendung eines hierfür wirklich geeigneten Tools – eines DOM Parsers. Praktischerweise verfügt PHP mit der meist per default vorhandenen DOM Extension über einen bequem einsetzbaren, auf libxml basierenden, DOM Parser inklusive XPath Unterstützung. XPath ist zur Adressierung respektive Lokaliserung einzelner Elemente innerhalb des DOM-Trees geradezu prädestiniert.
Der erste Schnritt auf dem Weg zum simplen Rohstoffpreis-Monitoring ist die formale Definition der einzelnen zu extrahierenden Werte und ihrer entsprechenden Quell-Dokumente. Wie eingangs erwähnt dienen die Preise der beiden Industriemetalle Aluminium und Kupfer als Beispiele für zu extrahierende Daten.
Eine geeigente Quelle für aktuelle Preise verschiedener Industriemetalle sind die entsprechenden Unterseiten des Projektes finanzen.net. Der Kupferpreis kann zum Beispiel hier und der Aluminiumpreis hier entnommen werden.
Die Aufgabe, die gewünschten Elemente per XPath zu selektieren, kann zum Beispiel – analog zu Abbildung 1 – mit Chromiums Dev-Tools erledigt werden. Damit die von Chromium erzeugten XPath Ausdrücke in Kombination mit PHPs DOM/XPath Klassen allerdings wirklich die gewünschten Werte hervorbringen, kann es notwendig sein, eventuell im Query vorhandene ‘tbody’ Elemente zu entfernen.
Der von Chromium erzeugte XPath Ausduck zur Selektion des Kupferpreises und des Aluminiumpreises weist ebenfalls ein tbody Element auf und muss vor dem genannten Hintergrund für ein korrektes Zusammenspiel bereinigt werden. Der bereinigte XPath Ausdruck zur Selektion der beiden Rohstoffpreise ist nachfolgend zu sehen.
1 2 |
//*[@id="mainWrapper"]/div[2]/div[1]/div[2]/table/tr/td[1]/text() |
Um formal festzulegen, welcher Ausdruck auf welchem Dokument angewendet werden soll, bietet sich JSON aufgrund der guten Maschinenlesbarkeit in Kombination mit der für Menschen relativ einfachen Editierbarkeit an. Der nachfolgende JSON-Block stellt die Konfiguration für die Extraktion der Rohstoffpreise dar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[ { "id": "al_finanzen_net", "source": { "url": "http://www.finanzen.net/rohstoffe/aluminiumpreis", "xpath": "Ly8qW0BpZD0ibWFpbldyYXBwZXIiXS9kaXZbMl0vZGl2WzFdL2RpdlsyXS90YWJsZS90ci90ZFsxXS90ZXh0KCk=", "verify": "Ii9bMC05XXsxLH1cLlswLTldezEsfVwsWzAtOV17MSwyfS8i" } }, { "id": "cu_finanzen_net", "source": { "url": "http://www.finanzen.net/rohstoffe/kupferpreis", "xpath": "Ly8qW0BpZD0ibWFpbldyYXBwZXIiXS9kaXZbMl0vZGl2WzFdL2RpdlsyXS90YWJsZS90ci90ZFsxXS90ZXh0KCk=", "verify": "Ii9bMC05XXsxLH1cLlswLTldezEsfVwsWzAtOV17MSwyfS8i" } } ] |
Um zu vermeiden, dass eventuelle Quotes innerhalb der XPath-Ausdrücke das JSON ungültig machen, wurden die xpath-Properties base64 kodiert. Dies gilt ebenfalls für die in den verify-Properties hinterlegten regulären Ausdrücke zur Plausibilitätsprüfung der extrahierten Werte.
Die vorliegende Konfiguration wird von dem kleinen PHP-Beispiel-Tool xtractr verarbeitet und ausgeführt. Hierbei fordert xtractr die konfigurierten URLs an, generiert für jedes Dokument einen DOM-Tree und evaluiert die definierten XPath-Ausdrücke. Die in diesem Schritt extrahierten Werte werden anschließend noch einer Plausibilitätsprüfung in Gestalt eines Regex-Matches unterzogen und im letzten Schritt für die Speicherung in MongoDB in ein geeignetes Format transformiert.
Die Transformation zur Speicherung in MongoDB wird von dem eingangs erwähnten, nachfolgend abgebildeten Bash-Script durch den in Zeile 4 definierten und in Zeile 5 ausgeführten PHP-Einzeiler vorgenommen. Das Ergebnis der Transformation sind Objekte mit den Properties “_id”, “name”, “value” und “unixtime”. Jedes Objekt dieser Art repräsentiert jeweils die Momentaufnahme eines der überwachten Rohstoffpreise.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash _mongoJS(){ local t='/code/cm/xtractr/prices.xtr'; local c='$r=[];foreach(json_decode(file_get_contents("php://stdin"))as$k=>$v){$r[]=(object)["_id"=>hash("ripemd160",uniqid("",true)),"name"=>$k,"value"=>$v,"unixtime"=>microtime(true)];}printf("%s",json_encode($r));' local j=$(/code/cm/xtractr/xtractr "${t}" | "$(which php)" -r "${c}"); echo -n "db.prices.insert(${j});"; } declare -x _mongo='127.0.0.1:40866/etl'; declare -x _js="$(_mongoJS)"; "$(which mongo)" "${_mongo}" >/dev/null 2>&1 <<WTF ${_js} WTF exit $?; |
Um die automatisierte Extraktion zu vervollständigen bedarf es letztlich nur noch eines Eintrages in der crontab. Im Folgenden Beispiel wird das Bash-Script und hierdurch das xtractr-Tool minütlich vom cron daemon ausgeführt. Der Source-Code des xtractr-Tools sowie die weiteren Files des Beispiel-Projektes sind in diesem Repository zu finden und können bei Bedarf nach Belieben modifiziert werden.
1 2 |
* * * * * /bin/bash /code/cm/xtractr/prices.job >/var/log/etl.log 2>&1 |
Leave a Reply
Want to join the discussion?Feel free to contribute!