Web scraper je zariadenie, ktoré je schopné sťahovať informácie z webservera, ktoré sú zaujímavé, alebo potrebné.
Najčastejšie sú to články, recenzie, cena výrobku, telefónne čísla, e-mailové adresy, aktuálne ceny na burzách mien, cenných papierov.
Scraper je schopný získavať informácie jednorázovo, alebo za určité obdobie, čím dokáže sledovať napríklad komplexný vývoj cien na burzách, vývoj cien výrobkov a tým dokáže upozorniť napríklad na zľavy a výhodnú kúpu, či predaj. Najpoužívanejší jazyk pre scraper je Python. Nakoľko v tomto jazyku nemám žiadnu znalosť, rozhodol som sa pre implementáciu pre web scraper.
Pre demonštráciu funkčnosti web scrapera využijem hlavnú stránku tohto fóra. Načítané dáta boli uložené iba do RAM pamäte počas bežiacej aplikácie. Dáta počas pokusu neboli archivované, či uložené v ROM úložisku. Ide o experiment k článku.
Fórum funguje na HTTPS šifrovanom protokole (443) a tak si ukážeme implementáciu web scraperov pre platformy ESP8266, ESP32, ktoré tento protokol podporujú. ESP8266 využíva odtlačok certifikátu servera v SHA1 formáte a ESP32 využíva Root CA certifikát pre HTTPS spojenie. Pre implementáciu šifrovaného spojenia budeme využívať websocket z hlavičkového súboru WiFiClientSecure.h. V podstate využíva stránku tak, ako štandardný browser - prehliadač.
Ako však odtlačok (fingerprint) a Root CA certifikát získať?
Jednou z najjednoduchších možností je využiť kryptografický nástroj OpenSSL, ktorý je možné využiť aj pre tieto účely.
OpenSSL je podporovaný natívne bez nutnosti inštalácie v niektorých Linux systémoch.
Príkaz pre získanie SHA1 fingerprintu:
Kód: Vybrat vše
openssl s_client -connect forum.hwkitchen.cz:443 -showcerts < /dev/null 2>/dev/null | openssl x509 -in /dev/stdin -sha1 -noout -fingerprint
Príkaz pre získanie Root CA certifikátu v .pem formáte:
Kód: Vybrat vše
openssl s_client -showcerts -verify 5 -connect forum.hwkitchen.cz:443 < /dev/null
V našom prípade je Root CA - DST Root CA X3, ktorá vydáva certifikáty Let's Ecrypt.
Druhou možnosťou, ako získať SHA1 fingerprint je priamo v prehliadači pri zobrazení informácii o certifikáte webservera, pričom je možné pozrieť si aj certifikačnú cestu, kde je možné vidieť názov Root CA. Root Ca certifikát je možné nájsť na internete v .pem formáte na stránkach Let's Encryptu, alebo na stránkach Identrust: https://www.identrust.com/dst-root-ca-x3.
Nevýhoda fingerprintu je, že sa mení zakaždým, keď sa zmení certifikát servera. Certifikáty Let's Encrypt pre webserver je vydávaný každé 3 mesiace, obnovuje sa. Teda po troch mesiacoch je nutné zmeniť aj program pre ESP8266, nakoľko so starým fingerprintom sa spojenie nenadviaže. Použitý fingerprint po 9. Decembri 2020 z tohto článku bude neplatný. Na druhú stranu, vydavateľ sa pri vydaní Let's Encrypt certifikátov nemení a tak ESP32 s Root CA certifikátom môže fungovať až do konca platnosti Root CA certifikátu DST Root CA X3, aktuálne do 30. Septembra 2021 - platí od roku 2000.
Implementácia web scrapera:
- Webscraperom budeme načítavať informácie o dostupných názvoch hlavných fór
- počte tém obsiahnutých v jednotlivých fórach
- celkový počet príspevkov v danom fóre
- autora posledného príspevku
- v časti Nedávne témata sa načítava názov témy, autor témy, autor posledného príspevku (a znova autor témy, chyba scrapera, informácia je v zdrojovom kóde 2x, raz je vizuálne vypísaná používateľovi)
Spojenie s webserverom:
Pre spojenie s webserverom je možné využiť vstavané príklady knižnice WiFiClientSecure - WebClient pre platformu ESP8266 a ESP32 v Arduino Core. Pre ESP32: https://github.com/espressif/arduino-es ... Secure.ino Po úspešnom pripojení metódou GET je možné načítavať response webservera - najčastejšie po riadkoch. Načítava sa HTTP hlavička a následne aj payload, ktorý zasiela webserver v response - odpovedi na požiadavku. V tomto prípade je to zdrojový HTML kód hlavnej stránky fóra, ktorý načítavame.
Analýza zdrojového kódu:
Načítaný zdrojový kód je nutné analyzovať, aby bolo možné nájsť vodítka, o ktoré sa môže scraper oprieť a načítať dynamickú informáciu, ktorá sa na danom mieste v zdrojovom kóde nachádza. Čím je stránka lepšie formátovaná (má viacero id, classov), tým je výrazne možné uľahčiť prácu web scrapera, je ľahšie určiť, kde má scraper danú informáciu očakávať. Analýzu je možné urobiť aj z klasického prehliadača.
Analýzou sa zistilo, že je možné pomerne ľahko predpokladať, kde sa v HTML kóde dynamická informácia objaví:
- Názov fóra sa nachádza medzi fragmentami HTML kódu: class="forumtitle"> </a>
- Názov vlákna sa nachádza medzi fragmentami HTML kódu: class="topictitle"> </a>
- Počet tém (vlákien) sa nachádza medzi fragmentami HTML kódu: <dd class="topics"> <dfn>
- Počet príspevkov vo fóre sa nachádza medzi fragmentami HTML kódu: <dd class="posts"> <dfn>
- Autor témy (alebo autor posledného príspevku) sa nachádza medzi fragmentami HTML kódu: class="username"> </a>
Programová implementácia obsahuje jeden program pre platformu ESP8266 i ESP32 súčasne. Na základe direktív je možné kompilovať program pre cieľovú platformu zvolenú používateľom v prostredí Arduino IDE. Nakoľko hľadáme dynamické informácie o ktorých vieme iba miesto, kde sa vyskytujú, nebudeme používať regulárny výraz (ten sa hodí skôr na hľadanie e-mailových adries, telefónnych čísel...), ale zvolíme metódu parsing pre vytiahnutie potrebnej informácie medzi fragmentami zdrojového kódu (ktoré sme zistili v procese analýzy) z načítaného riadku. V podstate si vytvoríme SubString, ktorý bude reprezentovať našu informáciu a vypíšeme ho na Sériovú (UART) linku.
Zdrojový kód pre ESP8266 je navrhnutý pre Arduino Core 2.5.0, respektíve 2.5.2. S najnovšou verziou Arduino Core nemusí fungovať. Program pre ESP32 je kompatibilný pre každú verziu od stable 1.0.0.
Niektoré fóra a webové stránky majú aj RSS, či JSON výstup, ktorý je možné obdobne načítavať a parsovať z neho potrebné informácie. Takýto formát má už preddefinované premenné, ku ktorým prislúcha hodnota a je ich pomerne ľahké spracovať aj prostredníctvom scrapera. Implementácia využíva iba funkciu setup(), nakoľko pripojenie 1x je postačujúce, mikrokontróler nerobí flood na server v podobne rovnakých viacnásobných požiadaviek.
Programová implementácia:
Kód: Vybrat vše
//Webscraper - forum.hwkitchen.cz
//Autor: martinius96 - registrovaný používateľ fóra
const char * ssid = "WIFI_MENO";
const char * password = "WIFI_HESLO";
const char* host = "forum.hwkitchen.cz";
const int serverPort = 443; //http port
#if defined(ESP32)
#include <WiFi.h>
//DST ROOT CA X3 - .pem
const static char* test_root_ca PROGMEM = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\n" \
"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \
"DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\n" \
"PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\n" \
"Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" \
"AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\n" \
"rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\n" \
"OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\n" \
"xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n" \
"7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\n" \
"aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\n" \
"HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\n" \
"SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\n" \
"ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\n" \
"AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\n" \
"R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\n" \
"JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\n" \
"Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n" \
"-----END CERTIFICATE-----\n";
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
const char fingerprint[] PROGMEM = "10 f6 06 df 7a 2b f3 f3 08 ed a5 8c e1 9b 15 e5 3f 3f d3 32"; //SHA1 Fingerprint
#endif
#include <WiFiClientSecure.h>
String midString(String str, String start, String finish) {
int locStart = str.indexOf(start);
if (locStart == -1) return "";
locStart += start.length();
int locFinish = str.indexOf(finish, locStart);
if (locFinish == -1) return "";
return str.substring(locStart, locFinish);
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi uspesne pripojene");
Serial.println("IP adress: ");
Serial.println(WiFi.localIP());
Serial.println("Ready");
WiFiClientSecure client;
#if defined(ESP32)
client.setCACert(test_root_ca);
Serial.println("Using DST ROOT CA X3");
#elif defined(ESP8266)
client.setFingerprint(fingerprint);
Serial.printf("Using fingerprint '%s'\n", fingerprint);
#endif
String url = "/";
if (client.connect(host, serverPort)) {
Serial.println("Pripojenie uspesne");
client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: ESPBoard\r\n" + "Connection: close\r\n\r\n");
while (client.connected()) {
String line = client.readStringUntil('\n');
//Serial.println(line);
if (line.indexOf("class=\"forumtitle\">") > 0) {
Serial.println();
Serial.println("Fórum: " + midString(line, "class=\"forumtitle\">", "</a>"));
}
if (line.indexOf("class=\"topictitle\">") > 0) {
Serial.println();
Serial.println("Vlákno: " + midString(line, "class=\"topictitle\">", "</a>"));
}
if (line.indexOf("<dd class=\"topics\">") > 0) {
Serial.println("Počet tém: " + midString(line, "<dd class=\"topics\">", " <dfn>"));
}
if (line.indexOf("<dd class=\"posts\">") > 0) {
Serial.println("Počet príspevkov: " + midString(line, "<dd class=\"posts\">", " <dfn>"));
}
if (line.indexOf("class=\"username\">") > 0) {
Serial.println("Autor vlákna: " + midString(line, "class=\"username\">", "</a>"));
}
}
} else {
Serial.println("Problem s pripojenim na stránku ");
}
client.stop();
}
void loop() {
}