Web scraper - ESP8266 / ESP32

Tvoříte zajímavý projekt? Pochlubte se s ním.
Pravidla fóra
Vkládejte prosím jen HOTOVÉ projekty, které chcete představit ostatním.
Odpovědět
martinius96
Příspěvky: 579
Registrován: 01 srp 2017, 19:29
Reputation: 0
Bydliště: Poprad
Kontaktovat uživatele:

Web scraper - ESP8266 / ESP32

Příspěvek od martinius96 » 10 zář 2020, 17:30

Dnes by som rád predstavil jednoduchý základný koncept pre web scraper na platforme ESP8266, ESP32, ktorý má skôr náučný ako plnefunkčný charakter. Implementácia obsahuje pár chýb, ktoré sa dajú softvérovo vyriešiť (napríklad duplicitné načítanie autora v Nedávnych tématach, prihlásených používateľov ako autorov, blank output). Je to iba minimálna implementácia, ktorá dokáže niečo nájsť, avšak nevie rozlíšiť logikou viacerých autorov (kto je autor vlákna, kto je autor posledného príspevku a podobne, stačil by aj jednoduchý counter...). Je to jednoduchý základ, ktorý je možné použiť pre vlastný projekt podobného zamerania.

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
Obrázek

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ýpis funguje hierarchicky od najnižšej CA po najvyššiu (Root CA) - Chain of Trust.
V našom prípade je Root CA - DST Root CA X3, ktorá vydáva certifikáty Let's Ecrypt.
Obrázek
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.
Obrázek
Obrázek
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)
Načítavať je možné mnoho iných informácii. URL vlákien, čas vytvorenia vlákna, čas posledného príspevku, subfóra, kategórie, počet zhliadnutí.
Obrázek
Obrázek
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:
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() {

}
Scraper postavený na týchto platformách nebude fungovať na webových stránkach, ktoré využívajú rôzne druhy overenia voči robotom prostredníctvom Javascriptu, napríklad CAPTCHA, CloudFlare a podobne. Mikrokontróler načítava iba zdrojový kód, nevie spustiť Javascript, alebo podobný client-side jazyk.

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 9 hostů