Auslesen / Auswerten einer Webadresse im Heimnetz

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
hjliedtke
User
Beiträge: 4
Registriert: Sonntag 10. November 2019, 20:49

Moin,
ich bin ein absoluter Neuling in Sachen Python :o
Möchte folgende Aufgabe lösen:
Ich habe einen Datenlogger von Photon, der an mein Netzwerk angeschlossen ist. Früher hat er die Daten an einen Server übertragen. Leider ist der Server down.
Auf den Logger kann ich über die IP-Adresse zugreifen (192.168.178.197). Nach der Passwortabfrage zeigt er die Einspeisedaten und u.a. die aktuelle Zeit an ebenso den Seiteninhalt. Auszug nachstehend.
<form action="/cgi-bin/menu.cgi" method="get">
<table border="0" summary="">
<input type="hidden" name="mode" value="data"><table border="0"><tr><td>Current Data ( all view only )</td></tr></table><br><br><table border="0">
<tr><td>Feed-in (kWh):</td><td><input type="text" size="15" value="00033308.02" readonly> </td></tr>

wie kann ich den Wert in der unteren Zeile nach "value" automatisiert auslesen (analog die Uhrzeit, die weiter unten steht) und speichern.
Weiter müsste die Routine z.B. alle 10 Minuten den Seiteninhalt aktualisieren.
Danke im voraus
Hans-Jürgen
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Für HTTP-Anfragen gibt es requests, zum parsen von HTML Beautiful-Soup.
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

Kleines Beispiel:

Code: Alles auswählen

from bs4 import BeautifulSoup

test = """<form action="/cgi-bin/menu.cgi" method="get">
<table border="0" summary="">
<input type="hidden" name="mode" value="data"><table border="0"><tr><td>Current Data ( all view only )</td></tr></table><br><br><table border="0">
<tr><td>Feed-in (kWh):</td><td><input type="text" size="15" value="00033308.02" readonly> </td></tr>"""

soup = BeautifulSoup(test, "html.parser")
value = soup.find_all("input")[1].get('value')
print(value)
hjliedtke
User
Beiträge: 4
Registriert: Sonntag 10. November 2019, 20:49

Moin,
danke für die schnellen Tipps. Habe es zwischenzeitlich ganz gut mit "requests" gelöst. Daten werden regelmäßig ausgelesen und gespeichert (die auskommentierten Zeilen sind während des Testens entstanden, ebenso die Print-Ausgaben):
import requests
import time
import csv
index = 0
# range zeit in sekunden, 1 Jahr hat 1.314.000 sekunden
for i in range(100000):
# sleep anpassen sekunden
time.sleep(60)
r = requests.get('http://192.168.178.197/cgi-bin/menu.cgi?mode=data' , auth=('root' , 'xxxx'))
# die drei zeilen können gelöscht werden; der Bereich [xxxx:yyyy] muss für andere Daten angepasst werden
# bei 0 bis 2000 wird der gesamte Seiteninhalt angezeigt
# print (r.text[1208:1218])
# print (r.text[1852:1860])
# print (r.text[1865:1873])
kwH = (r.text[1208:1218])
Datum = (r.text[1852:1860])
Zeit = (r.text[1865:1873])
print ('kwH' ,kwH)
print ('Datum' , Datum)
print ('Zeit' , Zeit)
print (' ')
index = index + 1
print ('index' , index)
row = [index, Datum , Zeit, kwH]
with open('werte.csv', 'a') as csvFile:
writer = csv.writer(csvFile)
writer.writerow(row)
csvFile.close()

jetzt fehlt mir nur noch ein Tipp, wie ich ich die kwH gleich als Zahl (mit Dezimalkomma statt ...punkt) und nicht als Textstring bekomme. Dann kann ich den Nachbearbeitungsbedarf reduzieren.
Hans-Jürgen
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@hjliedtke: `index` hat immer den gleichen Wert wie `i` und `i` wird nirgends verwendet, die ``for``-Schleife sollte also `index` als Laufvariable verwenden und die Initialisierung von `index` mit 0 sowie das manuelle hochzählen kann man sich dann sparen.

Der Kommentar für die ``for``-Schleife erscheint mir sehr willkürlich weil der nicht wirklich etwas mit dem Code zu tun hat. Hatte er vielleicht mal, aber weder die Zahl noch Sekunden hat irgendetwas mit der tatsächlichen Anzahl der Schleifendurchläufe noch mit der Wartezeit für einen Schleifendurchlauf zu tun.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Namen sollten auch nicht nur einen Buchstaben lang sein, oder aus Abkürzungen bestehen. Wenn man `response` meint, sollte man nicht nur `r` schreiben.

Wenn man ``with`` verwendet braucht man kein `close()` mehr auf der Datei aufrufen. Genau deswegen verwendet man ja ``with``.

Das ausschneiden per Indexwerte ist extrem fragil. Bei HTML sollte man sich, wie ja schon vorgeschlagen wurde, mit einem HTML-Parser an der Struktur des Dokuments orientieren.

Datum und Zeit sind eigentlich *ein* Wert, der nicht in zwei getrennten Spalten gespeichert werden sollte.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import csv
import time

import requests


def main():
    for index in range(100_000):
        time.sleep(60)
        response = requests.get(
            "http://192.168.178.197/cgi-bin/menu.cgi?mode=data",
            auth=("root", "xxxx"),
        )
        response.raise_for_status()
        #
        # TODO Robuster mit einem HTML-Parser lösen.
        # TODO Datum und Zeit sind *ein* Wert, der Zeitpunkt der Messung, und
        #   sollte in einer Spalte gespeichert werden.
        #
        verbrauch = response.text[1208:1218]
        datum = response.text[1852:1860]
        zeit = response.text[1865:1873]
        print("kwH", verbrauch)
        print("Datum", datum)
        print("Zeit", zeit)
        print()
        print("index", index)
        with open("werte.csv", "a") as csv_file:
            csv.writer(csv_file).writerow([index, datum, zeit, verbrauch])


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
hjliedtke
User
Beiträge: 4
Registriert: Sonntag 10. November 2019, 20:49

@blackjack
Vielen Dank für den Kurzkursus ;-)) - wie geschrieben, ich bin absoluter Neuling.

Die vermeintliche Fragilität der Indexwerte besteht nicht, sie stehen immer an der gleichen Stelle. Insofern wäre es auch für mich recht leicht, die anderen Werte nach dem gleichen Schema zu extrahieren. Dazu brauche ich mir dann nicht noch das Parsing anlesen.
Datum und Zeit: letztlich ist es die Frage, ob und wie ich die Daten weiter verarbeiten möchte. Ist doch letztlich egal.

Meine eigentliche Frage ging aber dahin, wie ich die kwH in eine Dezimalzahl - möglichst mit Komma und nicht mit Punkt umwandeln und speichern kann.
Wenns nicht mit relativ wenig Aufwand gehen sollte, wandle ich sie in eine int- Zahl um und begnüge mich damit.

Hans-Jürgen

NS
Eigentlich für die Fragestellung unerheblich, aber wie bekomme ich den Code so schön kopiert wie in Deinem Beispiel. Mein Copy u. Paste sieht ja längst nicht so gut aus.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Zur Formatierung gibt es das Code-Tag, das du ueber den </>-Knopf im vollstaendigen Editor nutzen kannst. Oder dann auch einfach selbst tippen.
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@hjliedtke: Das die immer an der gleichen Stelle stehen ist halt genau das was man nicht wirklich als gegeben ansehen sollte.

Für eine Umwandlung in eine Zahl ist der Dezimalpunkt schon das richtige, denn ein Komma versteht Python's `float()` oder `decimal.Decimal()` an der Stelle nicht. Ich würde das einmal mit `float()` parsen, dann wieder in eine Zeichenkette umwandlen und den Dezimalpunkt durch ein Komma ersetzen.

Und für Datum und Zeit gibt es was im `datetime`-Modul.

Code: Alles auswählen

#!/usr/bin/env python3
import csv
import time
from datetime import datetime as DateTime

import requests


def main():
    for index in range(100_000):
        time.sleep(60)
        response = requests.get(
            "http://192.168.178.197/cgi-bin/menu.cgi?mode=data",
            auth=("root", "xxxx"),
        )
        response.raise_for_status()
        #
        # TODO Robuster mit einem HTML-Parser lösen.
        #
        verbrauch = float(response.text[1208:1218])
        datum = DateTime.strptime(response.text[1852:1860], "%d.%m.%Y")
        zeit = DateTime.strptime(response.text[1865:1873], "%H:%M")
        zeitstempel = DateTime.combine(datum.date(), zeit.time())
        print("kwH", verbrauch)
        print("Datum", datum)
        print("zeitstempel", zeitstempel)
        print()
        print("index", index)
        with open("werte.csv", "a") as csv_file:
            csv.writer(csv_file).writerow(
                [
                    index,
                    zeitstempel.isoformat(),
                    format(verbrauch, ".2f").replace(".", ","),
                ]
            )


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
hjliedtke
User
Beiträge: 4
Registriert: Sonntag 10. November 2019, 20:49

Das mit dem Zeitstempel brauche ich nicht - funktioniert bei mir auch nicht richtig (es braucht aber auch nicht korrigiert werden). Diese Fehlermeldung ist mir auch zu kryptisch.

Traceback (most recent call last):
File "/home/pi/testblackjack.py", line 18, in <module>
zeit = DateTime.strptime(response.text[1865:1873], "%H:%M")
File "/usr/lib/python3.7/_strptime.py", line 577, in _strptime_datetime
tt, fraction, gmtoff_fraction = _strptime(data_string, format)
File "/usr/lib/python3.7/_strptime.py", line 362, in _strptime
data_string[found.end():])
ValueError: unconverted data remains: :55

Aber die Formatierung mit
format(verbrauch, ".2f").replace(".", ","),
ist genau das, was ich meinte.

Nochmals vielen Dank und einen schönen Abend
Hans-Jürgen
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@hjliedtke: Na offensichtlich konnte von der Zeit ":55" am Ende nicht konvertiert werden. Ist also nicht "%H:%M" sondern "%H:%M:%S". Die Formate waren ja auch nur geraten, das gab's ja keine Beispiele.

Was Du alles so nicht brauchst — es ist so halt nicht robust, prüft die Daten nicht, und schreibt ein Format das Daten nicht in einer Form enthält die man gut weiterverarbeiten kann.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten