Tutorial: Website parsen mit dem ESP8266 – Teil 2

Einleitung

Im ersten Teil dieses Tutorials habe ich gezeigt, wie man Informationen nach einem Suchschema aus einer Website extrahiert. In diesem Teil des Tutorial möchte ich zunächst auch darauf eingehen, was man beachten sollte, wenn man eine Website auswählt, aus der Daten extrahiert werden sollen. Später werden wir dann ein zweites Beispiel genauer betrachten.

Der Inhalt einer Website

Die heutigen Websites werden immer umfangreicher und bieten eine Fülle von Informationen, was leider auch eine Menge von Daten zur Folge hat. Zusätzlich werden Animationen, Grafiken oder Werbung mit übertragen, was die Datenmenge zusätzlich erhöht. Je mehr Daten empfangen werden, umso mehr muss der ESP analysieren, um die gesuchte Information zu finden.

Nehmen wir als Beipiel an, wir wollen die aktuelle Temperatur an unserem Wohnort aus einer Website auslesen. Es gibt eine große Zahl von Websites, die diese Information bereit stellen. Um zu bewerten, ob eine Website geeignet ist, diese Information für den ESP verträglich zu liefern, sollte man sich den Quelltext der Website mal ansehen. Ich habe als Beispiel mal eine Website ausgewählt, die die aktuelle Temperatur für sehr viele Orte liefern kann. Auf der Seite http://www.wunderground.com/ kann man sich eine Messstation in der Nähe seines Wohnortes suchen. Die url für die Messstation am Kölner Flughafen wäre dann http://www.wunderground.com/global/stations/10513.html.

Eine durchaus auch sinnvolle Alternative wäre es, eine Website auszuwählen, die weniger Daten transferiert. Eine gute Adresse sind in einem solchen Fall z.B. private Websites, die Informationen ihrer Wetterstationen online stellen.

Auf jeden Fall sollte man sich jetzt mal den Quelltext dieser Website ansehen, am einfachsten geht das z.B. im Firefox über den Menuepunkt Entwicklerwerkzeuge->Seitenquelltext anzeigen oder die Kurztaste Strg+u. Man muss kein HTML verstehen, um zu erkennen, dass hier Massen an Daten übertragen werden.

OpenWeather1

In diesem Fall von wunderground.com hat man, was die aktuelle Temperatur angeht Glück, die steht nämlich im Quelltext ganz oben als Meta-Information.

    <meta property="og:title" content="Koeln, Germany | 3.6&deg; | Mostly Cloudy" /> 

Um jetzt einen eindeutigen Suchbegriff in der Zeile zu finden, testen wir, ob bestimmte Buchstabenketten nur in dieser Zeile des Quelltextes vorkommen. Ich entscheide mich für den Suchstring „og:title„. Das hat den Vorteil, dass ich ohne den Suchstring zu ändern auch andere Städte suchen könnte.

Für einen ersten Test sollte es reichen den folgenden Codeblock im Code aus dem ersten Teil des Tutorials einzusetzen

  
while (client.available()) {
  String line = client.readStringUntil('\n');
  if (line.indexOf("og:title") >= 0) { // Searchstring exists?
    Serial.println();
    Serial.print(line);
  }
}

Natürlich muss der Server und die URL ebenfalls eingetragen werden

 
... 
const char* host = "www.wunderground.com"; 
... 
String url = "/global/stations/10513.html?MR=1";
... 

Die Ausgabe sieht jetzt so aus:


Connecting to TP-LINK_360E60
............
WiFi connected
IP address:
192.168.0.100

connecting to www.wunderground.com
Requesting URL: /global/stations/10513.html?MR=1

         <meta property="og:title" content="Koeln, Germany | 3.5&deg; | Scattered Clouds" />
closing connection

Die richtige Zeile habe ich also schon gefunden, jetzt gehts darum, den Temperaturwert aus der Zeile zu extrahieren. Der Wert steht zwischen den Zeichen „| “ und „&“. Wir suchen also das erste Vorkommen des ersten Suchstrings und extrahieren von da bis zum zweiten Suchstring. Der Code wird also folgendermaßen ergänzt. Als kleines Extra habe ich auch noch die Umwandlung der Temperatur in float eingebaut.


while (client.available()) {
  String line = client.readStringUntil('\n');
  if (line.indexOf("og:title") >= 0) { // Searchstring exists?
    Serial.println();
    Serial.println(line);
    int vonPos = line.indexOf("| "); 
    int bisPos = line.indexOf("&");
    Serial.print("die aktuelle Temperatur ist ");
    Serial.print(line.substring(vonPos+2, bisPos));
    Serial.println(" Grad C");
   // Umandlung in float
   String temp=line.substring(vonPos+2, bisPos);
   char char1[8];
   temp.toCharArray(char1, temp.length()+1);
   float tempWert=atof(char1);
   Serial.println(tempWert);
  }
}

Die Ausgabe sieht jetzt so aus


Connecting to TP-LINK_360E60
........
WiFi connected
IP address:
192.168.0.100

connecting to www.wunderground.com
Requesting URL: /global/stations/10513.html?MR=1

<meta property="og:title" content="Koeln, Germany | 3.6&deg; | Mostly Cloudy" />
die aktuelle Temperatur ist 3.6 Grad C
3.60

closing connection

Als zweiten Parameter hätte ich gerne den Luftdruck, hier wird es schon schwieriger, den im Quelltext zu finden. Ich suche im Quelltext nach „Druck“, das sollte ja in der Nähe des eigentlichen Wertes zu finden sein, wie mir die Website selbst verrät. Der Suchstring „Druck“ kommt 3x vor und ich sehe, dass der gesuchte Messwert des Luftdrucks 2x auf der Website ausgegeben wird. In beiden Fällen steht der Messwert aber nicht in der selben Zeile. Jetzt heißt es also zusätzlich Zeilen abzählen.

Ich suche also zunächst nach „<td><dfn>Druck“ und dann nach „wx-value“. In dieser Zeile findet sich dann mein Luftdruckwert. So dachte ich! Es passiert hier etwas seltsames.

Nachdem ich den Suchstring „Druck“ nicht finde konnte, obwohl er ja im Firefox im Quelltext war, habe ich in der Linux Command Line mit

wget http://www.wunderground.com/global/stations/10513.html?MR=1

den Quelltext in einen File geschrieben. Bei der Untersuchung dieses Files fiel mir dann auf, dass hier „Pressure“ statt „Druck“ verwendet wird. Die Website scheint zu erkennen, dass mein Firefox deutsch spricht, was mein ESP sicher nicht zu erkennen gibt.

Der Code wird jetzt schon etwas umfangreicher, ich habe zusätzliche Flags eingefügt, die mir zeigen, ob ich den ersten Suchstring gefunden habe, dann , ob ich den zweiten Suchstring gefunden habe. Zusätzlich ein Flag, damit ich den selben Suchstring nicht noch ein zweites mal auswerte, da er ja auf der Seite 2x vorkommt.


  // Read all the lines of the reply from server and print them to Serial
  boolean markF = false;
  boolean foundF = false;
  while (client.available()) {
    String line = client.readStringUntil('\n');
    if (line.indexOf("og:title") >= 0) { // Searchstring exists?
      Serial.println();
      Serial.println(line);
      int vonPos = line.indexOf("| ");
      int bisPos = line.indexOf("&");
      Serial.print("die aktuelle Temperatur ist ");
      Serial.print(line.substring(vonPos + 2, bisPos));
      Serial.println(" Grad C");
      // Umandlung in float
      String temp = line.substring(vonPos + 2, bisPos);
      char char1[8];
      temp.toCharArray(char1, temp.length() + 1);
      float tempWert = atof(char1);
      Serial.println(tempWert);
    }
    if (line.indexOf(">Pressure") >= 0 && !foundF) {  
      Serial.println();
      Serial.println(line);
      markF = true; // first time found
    } 
    if (line.indexOf("wx-value") >=0 && markF && !foundF) {   // search only the first 
      Serial.println();
      Serial.println(line);
      int vonPos = line.indexOf(">");
      int bisPos = line.indexOf("<",vonPos+1);
      Serial.print("der aktuelle Luftdruck ist ");
      Serial.print(line.substring(vonPos + 1, bisPos));
      Serial.println(" hPa");
      // Umandlung in float
      String druck = line.substring(vonPos + 1, bisPos);
      char char1[8];
      druck.toCharArray(char1, druck.length() + 1);
      float druckWert = atof(char1);
      Serial.println(druckWert);
      markF=false;
      foundF=true;      // don't search the second 
    }
  }

[/code]

Und zum Schluß noch die Ausgabe

[code]
Connecting to TP-LINK_360E60 
........... 
WiFi connected 
IP address:  
192.168.0.100 
 
connecting to www.wunderground.com 
Requesting URL: /global/stations/10513.html?MR=1 
 
    <meta property="og:title" content="Koeln, Germany | 3.9&deg; | Mostly Cloudy" /> 
die aktuelle Temperatur ist 3.9 Grad C 
3.90 
 
        <td><dfn>Pressure</dfn></td> 
 
        <span class="wx-value">1025</span> 
der aktuelle Luftdruck ist 1025 hPa 
1025.00 
 
closing connection 

Ich hoffe, dass mein Beispiel zeigt, wie einfach es ist, Informationen aus Websites zu parsen. Die Weiterverwendung dieser Information ist eurer Fantasie überlassen. Im nächsten und letzten Teil dieses Tutorials werde ich ein Beispiel für eine Steuerung vorstellen.

Aufgabe ist: Die Klappe eines Hühnerstalls soll bei Sonnenaufgang geöffnet werden und bei Sonnenuntergang wieder geschlossen werden.Die Informationen dazu – ihr könnt es euch denken – hole ich mir von einer Website.

Advertisements

Veröffentlicht am 5. Februar 2016, in Uncategorized. Setze ein Lesezeichen auf den Permalink. 13 Kommentare.

  1. Grundsätzlich sollte man sich fragen, ob man die Daten nicht anderweitig bekommen kann. Gerade für das Wetterbeispiel gibt es deutlich einfachere Wege um an die entsprechenden Daten zu kommen. Beispiele: Wetter.com oder auch Openweathermap stellen eine Web-API zur Verfügung, die auf ein HTTP-Request die Daten entweder in XML oder besser sogar in JSON liefern. Ich bin gerade nicht sicher, ob es für Arduino eine entsprechende Lib zum Deserialisieren von JSON gibt, aber zur Not parst man den JSON-String. Da es sich hierbei aber nicht um einen View handelt ist die Gefahr gering, dass sich die Struktur ändert. Die Webseite muss nur mal ihr Layout ändern und schon kann es zu Problemen beim Parsen geben.

    Um Grundsätzlich das Parsen zu erklären, ist das Tutorial okay. Um Wetter-Daten zu bekommen finde ich verfolgt es den falschen Ansatz.

  2. Vollkommen richtig. Mir ging es um das Prinzip des Parsens, nicht um den Inhalt als solchen. Ich habe lange überlegt, welchen Inhalt ich für das Beispiel nehme. Alternativ hatte ich noch die Messwerte der Luftqualitätsmessung in Betracht gezogen (z.B. http://www.lanuv.nrw.de/fileadmin/lanuv/luft/temes/heut/VKCL ).
    Vielleicht werde ich aber in einem späteren Tutorial den Zugriff auf openweathermap per API vorstellen. Arduino stellt in der Tat ein json Library zur Verfügung.

  3. Hallo Reinhard,

    ich war froh, dass ich deinen Blog gefunden habe, weil ich genau das brauche für meine Wetterstation.
    Leider kriege ich es nicht hin, keine Ahnung was ich falsch mache.
    Könntest du mal den kompletten Code posten?
    Vielleicht mache ich einfach beim zusammensetzen einen Fehler.

    Gruß Thomas

    Sketch:
    #include
    const char* ssid = „meinWlan“;
    const char* password = „meinPasswort“;
    const char* host = „www.wunderground.com“;

    void setup() {
    Serial.begin(9600);
    delay(10);

    // We start by connecting to a WiFi network

    Serial.println();
    Serial.println();
    Serial.print(„Connecting to „);
    Serial.println(ssid);

    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(„.“);
    }

    Serial.println(„“);
    Serial.println(„WiFi connected“);
    Serial.println(„IP address: „);
    Serial.println(WiFi.localIP());
    Serial.println();
    }
    void loop() {
    Serial.print(„connecting to „);
    Serial.println(host);
    // Use WiFiClient class to create TCP connections
    WiFiClient client;
    const int httpPort = 80;
    if (!client.connect(host, httpPort)) {
    Serial.println(„connection failed“);
    return;
    }

    // We now create a URI for the request
    String url = „/global/stations/10513.html?MR=1“;

    Serial.print(„Requesting URL: „);
    Serial.println(url);

    // This will send the request to the server
    client.print(String(„GET „) + url + “ HTTP/1.1\r\n“ +
    „Host: “ + host + „\r\n“ +
    „Connection: close\r\n\r\n“);
    // start waiting for the response
    unsigned long lasttime = millis();
    while (!client.available() && millis() – lasttime < 1000) {delay(1);} // wait max 1s for data
    // Read all the lines of the reply from server and print them to Serial
    while (client.available()) {
    String line = client.readStringUntil('\n');
    //Serial.print("* ");Serial.println (line); // was kommt an?
    if (line.indexOf("og:title")) { // Searchstring exists?
    Serial.println();
    Serial.print(line);
    }
    }
    Serial.println();
    Serial.println("closing connection");
    delay(5000);
    }

    ______________________________________________________________________
    Ausgabe:
    WiFi connected
    IP address:
    192.168.0.116

    connecting to http://www.wunderground.com
    Requesting URL: /global/stations/10513.html?MR=1

    HTTP/1.1 301 Moved Permanently

    Server: AkamaiGHost

    Content-Length: 0

    Location: https://www.wunderground.com/global/stations/10513.html?MR=1

    Cache-Control: max-age=0

    Expires: Thu, 11 Jan 2018 07:04:39 GMT

    Date: Thu, 11 Jan 2018 07:04:39 GMT

    Connection: close

    Property-id: drupal-prod

    X-RequestSource: AkamaiProdProxy

    WU-Device-Class: desktop

    WU-Locale-Group: GLS+

    WU-Connection-Speed: 4G

    X-RequestSource: AkamaiDefaultDNA

    closing connection

    • Hallo Thomas,
      der Code ist schon ok. Alllerdings wird die URL, die du aufrufst automatisch umgeleitet auf:
      https://www.wunderground.com/weather/de/ahe/50.83000183%2C7.44999981?mr=1
      Der Browser kann damit umgehen, der ESP bekommt dann aber „HTTP/1.1 301 Moved Permanently“.
      Setz mal diese URL ein, damit sollte es funktionieren.

      • Hallo Reinhard,

        das hatte ich schon versucht, geht aber auch nicht.
        Ich habe den Code mit dem ESP8266 und dem ESP32 laufen lassen. Immer mit dem gleichen Ergebnis.
        Mit der Website http://www.example,com funktioniert es, da wird alles geladen.

        Es wird immer nur ein kleiner Teil der Wetter Webseite geladen, also eigentlich nur der Teil mit dem Datum und der Uhrzeit.
        In den Foren wird das Problem oft besprochen, aber ich habe auch dort kein Lösung gefunden.
        Kannst du mit deinem Programm den ganzen HTML Code der Wetter Seite laden?

        Gruß Thomas

  4. Hallo Thomas,
    vergiss es, was ich oben geschrieben habe. Richtig ist, dass die Seite umgeleitet wird. Du rufst eine Seite über http:// auf und der Browser wird gezwungen eine andere Seite über https:// zu laden. Aber https:// kann der ESP mit dieser SW gar nicht, dafür muss das Programm verändert werden. Am besten, du versuchst eine Seite zu finden, die über http:// abrufbar ist.
    openweathermap scheint auch mit http zu funktionieren.
    http://openweathermap.org/city/2842960
    Gruß
    Reinhard

    P.S.: noch besser wäre es allerdings, wenn du direkt über die API gehst und dich mit der Verarbeitung von json Daten vertraut machen würdest

    • Hallo Reinhard,

      danke für den Tipp,
      Die Seite http://openweathermap.org/find?q=esslingen konnte ich jetzt laden.
      Leider werden dort die Temperatur usw. nicht im Klartext angezeigt. Da werden wohl Variable verwendet. Ich kenne mich mit html nicht aus. Hast du eine Idee wie man da drankommt?
      Wenn ich in meinem Browser (Apple Safari) den Quelltext anschaue dann finde ich die Zeile
      temperature from 3 to 3 °С, wind 1.5 m/s. clouds 75 %, 1024 hpa
      aber in der vom ESP geladenen Seite finde ich das nicht. Da steht anstelle der Temperatur eine Variable temp: v
      ar tmax = Math.round(10*(JSONobject.list[i].main.temp_max -273.15)) / 10 ;

      Gruß Thomas

  5. Das ist vermutlich JavaScript und die Seite wird erst im Browser erstellt/zusammengebaut, lässt sich insofern also nicht parsen. Also andere Seite suchen oder die API nutzen.

    • Wieder was gelernt:-)
      Kannst du mir noch helfen wegen der API? Kann man damit die Werte aus der JavaScript Seite lesen?

  6. API steht für Application Programming Interface, also eine Schnittstelle, die dazu gemacht ist, dass sich Programme miteinander darüber unterhalten. Schau dir mal die Seite https://openweathermap.org/appid an und lade mit dem ESP die auf dieser Seite angegebene URL http://samples.openweathermap.org/data/2.5/forecast?id=524901&appid=b1b15e88fa797225412429c1c50c122a1
    Du siehst, dass nur noch die Information als solche übertragen werden, die sich dann wiederum mit dem ESP weiterverarbeit4en lässt. Sinnvollerweise solltest du dich dann auch mit dem Thema json beschäftigen.

    • Hallo Reinhard,

      der Tipp war sehr gut. Ich habe es jetzt hingekriegt die Daten auszulesen. Nicht mit dem JSON Parser, ich habe den String selbst auseinandergenommen, einfach um mal zu lernen wie das geht.

      Gruß Thomas

  7. Hallo Thomas,
    das freut mich, dass du erfolgreich warst.
    Du kannst ein Foto auf imgur ablegen und hier einen Link darauf posten.
    Gruß
    Reinhard

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: