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.
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° | 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° | 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° | 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° | 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.
Veröffentlicht am 5. Februar 2016, in Uncategorized. Setze ein Lesezeichen auf den Permalink. 16 Kommentare.
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.
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.
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
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
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?
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
Eigentlich sollte da ein Foto zu sehen sein. Hat wohl nicht funktioniert.
Hallo Thomas,
das freut mich, dass du erfolgreich warst.
Du kannst ein Foto auf imgur ablegen und hier einen Link darauf posten.
Gruß
Reinhard
Hallo Reinhard
Wie genau funktioniert dies nun mit einem Bild von einem PHP-Server?
Gruss
Tim
Hallo Tim,
was genau meinst du ? Ich verstehe deine Frage nicht.
Reinhard
Hallo Reinhard,
hoffe du bist hier noch aktiv. Habe die Seite gefunden und das Projekt sehr aufmerksam verfolgt da es genau dem entspricht was ich suche;-)
Ich will von einer Homepage die aktuellen Tankpreise filtern bzw raussuchen um diese auf einem Display anzeigen zu können.
Leider habe ich irgendwie einen Fehler und komme nicht weiter. Da ich mich erst recht neu mit dem Node board bzw Arduino beschäftige kann ich den Fehler auch nicht einkreisen.
ich habe flgenden Code wie in der Anleitung verwendet:
/*
* Basic ESP Client example, based on the ESP libraries examples
*/
#include
const char* ssid = „WLAN“;
const char* password = „PW“;
const char* host = „clever-tanken.de“;
void setup() {
Serial.begin(115200);
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 = „/tankstelle_details/11683“;
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 =0) { // Searchstring exists?
Serial.println();
Serial.print(line);
}
Serial.print(line);
}
delay(5000);
// Read all the lines of the reply from server and print them to Serial
//while(client.available()){
//char readchar = client.read();
//Serial.print(readchar);
}
//Serial.println();
//Serial.print(„closing connection“);
//delay(5000);
—–
Die Ausgabe der Server Infos klappt nur leider nicht die HTML Zeile. Hast du da eine Idee?
Gruß
Simon