mit Python nur die letzte Zeile einer CSV Datei behalten

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
wfog3435
User
Beiträge: 11
Registriert: Donnerstag 8. August 2019, 08:46

Hallo,

ich bin fast am verzweifeln, ich habe eine CSV- Datei mit folgendem Inhalt.

Code: Alles auswählen

time,msg,codes,model,id,channel,battery,button,temperature_F,humidity,mic
2019-08-13 09:16:10,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,57.600,91,CRC
2019-08-13 09:17:01,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,57.700,91,CRC
2019-08-13 09:17:51,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,57.700,91,CRC
2019-08-13 09:18:41,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,57.700,91,CRC
2019-08-13 09:19:31,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,57.800,91,CRC
2019-08-13 09:20:21,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,57.800,91,CRC
2019-08-13 09:21:11,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,57.900,91,CRC
2019-08-13 09:22:01,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.000,91,CRC
2019-08-13 09:22:51,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.000,91,CRC
2019-08-13 09:23:41,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.000,91,CRC
2019-08-13 09:24:31,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.000,91,CRC
2019-08-13 09:25:21,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.100,91,CRC
2019-08-13 09:27:01,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.100,91,CRC
2019-08-13 09:27:51,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.100,91,CRC
2019-08-13 09:28:41,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.100,91,CRC
2019-08-13 09:29:30,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.200,91,CRC
2019-08-13 09:30:21,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.200,91,CRC
2019-08-13 09:31:11,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,58.200,91,CRC
Der Inhalt ist nicht immer gleich und die Größe der Datei auch nicht.
Nun will ich aber immer nur die letzte Zeile und die Kopfzeile behalten.

Kann hier jemand helfen?
Danke
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

csv Modul, alle Zeilen einlesen, nur erste und letzte behalten.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Code: Alles auswählen

#!/usr/bin/env python3
import csv

from more_itertools import last


def main():
    encoding = 'utf8'
    with open('input.csv', encoding=encoding) as input_file:
        with open('ouput.csv', encoding=encoding) as ouput_file:
            reader = csv.reader(input_file)
            writer = csv.writer(ouput_file)
            try:
                writer.writerow(next(reader))
            except StopIteration:
                pass  # Intentionally ignored.
            else:
                try:
                    writer.writerow(last(reader))
                except ValueError:
                    pass  # Intentionally ignored.


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

... oder quick and (very) dirty:

Code: Alles auswählen

with open('input.csv') as fobj:
    first_row = next(fobj)
    last_row = fobj.readlines()[-1]
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kbr: Iiih, das `readlines()` liest ja alles in den Arbeitsspeicher. Wenn schon schnell und dreckig, dann bitte mit `collections.deque`:

Code: Alles auswählen

    last_row = deque(fobj, 1)[0]
Wobei `row` nur gilt wenn sichergestellt ist das `row` und `line` hier synonym sind, was bei CSV nicht zwingend der Fall ist. (Bei den Beispieldaten aber wohl schon.)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
wfog3435
User
Beiträge: 11
Registriert: Donnerstag 8. August 2019, 08:46

Erst mal danke für die schnellen Antworten, hat wunderbar geklappt herzlichen Danke!
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

__blackjack__ hat geschrieben: Dienstag 13. August 2019, 16:31 @kbr: Iiih, das `readlines()` liest ja alles in den Arbeitsspeicher.
... mit deque ist es aber nicht mehr (very) dirty ... 8)
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Spricht ja nichts dagegen, den csv.reader() an deque() zu übergeben:

Code: Alles auswählen

from collections import deque
import csv

def last_item(iterable):
    return deque(iterable, 1).pop()

def main():
    with open(FILENAME) as stream:
        last_row = last_item(csv.reader(stream))
    print(last_row)

if __name__ == '__main__':
    main()
Ist dann nur schade um die vielen geparsten Datensätze, die weggeworfen werden. Dafür ist es aber robuster...
wfog3435
User
Beiträge: 11
Registriert: Donnerstag 8. August 2019, 08:46

Hallo,

danke für die schnellen guten Antworten!
Mein Code funktioniert jetzt super, jetzt würde ich es gern über crontab alle 15 min ausführen klappt aber leider nicht.

Code: Alles auswählen

crontab -e 

Code: Alles auswählen

# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command

*/15 * * * * /usr/bin/python /home/pi/S3318P-RTL433/S3318PtoDB.py


dann hab ich der Datei S3318PtoDB.py Rechte gegeben

Code: Alles auswählen

sudo chmod a+rwx S3318PtoDB.py
ich habe sogar vollen Zugriff kein Erfolg

Code: Alles auswählen

sudo chmod 777 S3318PtoDB.py
hier die S3318PtoDB.py

Code: Alles auswählen

#!/usr/bin/python
import sys
import mysql.connector
import csv
import pandas as pd
from sets import Set
from more_itertools import last

# nur Kopfzeile und letzte Zeile
def main():
    encoding = 'utf8'
    with open('S3318P.csv',"rb") as input_file:
        with open('S3318P-2.csv',"wb") as ouput_file:
            reader = csv.reader(input_file)
            writer = csv.writer(ouput_file)
            try:
                writer.writerow(next(reader))
            except StopIteration:
                pass
            else:
                try:
                    writer.writerow(last(reader))
                except ValueError:
                    pass


if __name__ == '__main__':
    main()

# S3318P.csv leeren bis auf Kopfzeile
f = pd.read_csv("S3318P.csv")
f = f.drop(f.index[0:5000])
keep_col = ['time','msg','codes','model','id','channel','battery','button','temperature_F','humidity','mic']
new_f = f[keep_col]
new_f.to_csv("S3318P.csv", index=False)

# S3318P-2.csv unwichtige Spalten loeschen
f = pd.read_csv("S3318P-2.csv")
keep_col = ['time','model','battery','temperature_F','humidity']
new_f = f[keep_col]
new_f.to_csv("S3318P-2.csv", index=False)

# Verbindung zur Datenbank
try:
    connection = mysql.connector.connect (host = "localhost", user = "root", passwd = "pw", db = "db")
except:
    print ("Keine Verbindung zum Server")
    sys.exit(0)

cursor = connection.cursor()

# Daten aus csv in DB einfuegen
with open('S3318P-2.csv', "rt") as ifile:
    ifile.next()
    read = csv.reader(ifile)
    for row in read:
        cursor.execute("INSERT INTO db_tb(time,model,battery,temperature_F,humidity) VALUES(%s, %s, %s, %s, %s)", row)

connection.commit()

cursor.close()
die Datei liegt in /home/pi/S3318P-RTL433
Raspberry rebootet ohne Erfolg.

was mache ich falsch?
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Relative Pfade zu verwenden. Weil dein Skript von cron nicht da ausgeführt wird wo es liegt. Sondern irgendwo anders (kann das gerade nicht nachschlagen).

Also absolute Pfade verwenden oder relative per __file__ Variable zu solchen machen.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Warum hast Du außerhalb von ›main‹ und sogar nach ›if __name__ …‹ noch weiteren Code stehen??

Warum liest Du zweimal "S3318P.csv" komplett ein? Du behauptest, alles bis auf die Kopfzeile zu leeren, in Wirklichkeit löschst Du aber nur die ersten 5000 Zeilen. Solche Sachen mögen jetzt funktionieren, aber irgendwann hast Du doch mehr als diese Zeilen und wunderst Dich, dass seltsame Dinge passieren.

Dann definierst Du auch noch einen festen Satz an Spalten, so dass es wirklich keinen Grund mehr gibt, die Datei einfach neu zu schreiben.

Bei "S3318P-2.csv" gehst Du einen ähnlich komplizierten Weg. Erst schreibst Du alles, um es danach wieder zu lesen und nur noch bestimmte Spalten zu behalten, anstatt gleich nur diese Spalten zu schreiben.

Nackte excepts sind böse, hier verhinderst Du auch effektiv, herauszufinden, was denn an der Verbindung zur Datenbank nicht geklappt hat, lösch einfach diese unsinnige „Fehlerbehandlung” komplett.

Jetzt liest Du zum zweiten Mal die Datei 'S3318P-2.csv' und hoffst, dass die Spalten in der richtigen Reihenfolge gespeichert wurden, was aber schwierig zu garantieren ist, weil Du die Spaltennamen an mehreren Stellen direkt im Code stehen hast, so dass Du immer alle Stellen finden mußt, wenn Du daran mal was änderst. Es ist sogar noch unbegreiflicher, da es doch eine Pandas-Funktion zum Schreiben in eine Datenbank gibt.

Wenn Du nochmal neu anfängst, brauchst Du folgende Schritte:
1) Lesen von S3318P.csv
2) extrahieren der lezten Zeile data.iloc[-1]
3) schreiben in die Datenbank
4) neues Pandas-Dataframe mit gewünschten Spalten erzeugen und als S3318P.csv speichern.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

snafu hat geschrieben: Dienstag 13. August 2019, 19:55 Ist dann nur schade um die vielen geparsten Datensätze, die weggeworfen werden. Dafür ist es aber robuster...
Nicht, dass dies jetzt noch wichtig wäre, aber die folgende Möglichkeit kam mir verzögert in den Sinn und führt ohne deque auch zum Ziel:

Code: Alles auswählen

with open('data.csv') as fobj:
    first_row = next(fobj)
    for last_row in fobj: pass
wfog3435
User
Beiträge: 11
Registriert: Donnerstag 8. August 2019, 08:46

Erst mal danke für die Antworten und Kommentare.
@ Sirius3 -> habe mir mal deine Worte zu Herzen genommen und versucht es einfacher zu machen.
Hier das neue Script.

Code: Alles auswählen

#!/usr/bin/python
import sys
import mysql.connector
import csv
import pandas as pd
from sets import Set
from more_itertools import last
# öffnen der CSV datei
df = pd.read_csv("/home/pi/datenlogger/rtl_433/S3318P.csv")  
# nur gewünschte Spalten auswählen und kopfzeile und letzte Zeile in neues CSV schreiben
keep_cols = ['time','model','battery','temperature_F','humidity']
df = df.iloc[[-1]]
new_df = df[keep_cols]
new_df.to_csv("/home/pi/datenlogger/rtl_433/S3318P-2.csv", index=False)
# Zeilen löschen und speichern damit der rtl_433 -R 47 weiter in diese Datei schreiben kann
df = df.drop(df.index[0:5000])
df.to_csv("/home/pi/datenlogger/rtl_433/S3318P.csv", index=False)
# Verbindung zur Datenbank
try:
    connection = mysql.connector.connect (host = "localhost", user = "root", passwd = "pw", db = "db_name")
except:
    print ("Keine Verbindung zum Server")
    sys.exit(0)
cursor = connection.cursor()
# Daten aus csv in DB einfuegen
with open('/home/pi/datenlogger/rtl_433/S3318P-2.csv', "rt") as ifile:
    ifile.next()
    read = csv.reader(ifile)
    for row in read:
        cursor.execute("INSERT INTO S3318P_tb(time,model,battery,temperature_F,humidity) VALUES(%s, %s, %s, %s, %s)", row)
connection.commit()
cursor.close()
habe es irgendwie nicht hinbekommen die CSV zu leeren bis auf die Kopfzeile deswegen immer noch df = df.drop(df.index[0:5000]) aber es reicht mir auch da die CSV alle 15 Min überschrieben wird, für eine bessere Lösung währe ich aber dankbar.

Das ich die S3381P.csv in die S3381P-2.csv schreibe und dann die Zeile bis auf die Kopfzeile lösche hat einen Hintergrund, der befehl der die Daten vom Außensensoren holt läuft ständig auf dem RasPi und bricht sonst ab.

Code: Alles auswählen

rtl_433 -R 47 -F csv:S3318P.csv
Auch hier würde ich mich über neue Anregungen freuen, auch wenn das Script tut was es soll.

Jetzt wollte ich mehrere Sensoren abfragen und von jedem Sensor den letzten Eintrag und die Kopfzeile in eine neue csv scheiben bekomme ich aber irgendwie nicht hin.
hier die 1. CSV

Code: Alles auswählen

time,msg,codes,model,id,channel,battery,button,temperature_F,humidity,mic,temperature_C
2019-08-19 17:17:29,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:17:29,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:17:29,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:07,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:07,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:07,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:07,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:07,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:19,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,68.400,61,CRC,
2019-08-19 17:18:38,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:38,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:38,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:38,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:18:38,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:19:10,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,68.500,61,CRC,
2019-08-19 17:19:13,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:19:13,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:19:13,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:19:13,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:19:13,,,inFactory sensor,31,,,,68.000,89,,
2019-08-19 17:19:50,,,inFactory sensor,31,,,,68.200,89,,
2019-08-19 17:19:50,,,inFactory sensor,31,,,,68.200,89,,
2019-08-19 17:19:50,,,inFactory sensor,31,,,,68.200,89,,
2019-08-19 17:19:50,,,inFactory sensor,31,,,,68.200,89,,
2019-08-19 17:19:50,,,inFactory sensor,31,,,,68.200,89,,
2019-08-19 17:20:02,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,68.600,61,CRC,
2019-08-19 17:20:25,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:20:25,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:20:25,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:20:26,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:20:26,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:20:26,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:20:37,,,Solight TE44,18,1,,,,,CRC,27.100
2019-08-19 17:20:52,,,S3318P Temperature & Humidity Sensor,16,1,OK,0,68.600,60,CRC,
2019-08-19 17:20:59,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:21:00,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:21:00,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:21:00,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:21:00,,,inFactory sensor,31,,,,68.300,89,,
2019-08-19 17:21:00,,,inFactory sensor,31,,,,68.300,89,,
und die 2. csv sollte dann ungefähr so aussehen:

Code: Alles auswählen

time,model,id,battery,temperature_F,humidity,temperature_C
2019-08-19 17:20:37,Solight TE44,18,,,,27.100
2019-08-19 17:20:52,S3318P Temperature & Humidity Sensor,16,OK,68.600,60,
2019-08-19 17:21:00,,,inFactory sensor,31,,,,68.300,89,,
oder noch lieber eine Datei für jeden Sensor.

der rtl_433 abfrage funktioniert:

Code: Alles auswählen

rtl_433 -R 47 -R 85 -R 91 -F csv:datenlogger.CSV
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Das unnötige Exception-Handling ist immer noch drin, und Du schreibst immer noch die zweite csv-Datei, nur um sie gleich danach wieder zu lesen. Wenn Du weißt, dass da nur eine Zeile drin ist, warum benutzt Du eine for-Schleife?
Du hast Dateinamen und Spaltennamen mehrfach im Code verstreut als literale Strings stehen. Besser ist es, diese als Konstanten am Anfang zu definieren.
Bleibt also:

Code: Alles auswählen

#!/usr/bin/python
import mysql.connector
import pandas as pd

CSV_FILENAME = "/home/pi/datenlogger/rtl_433/S3318P.csv"
COLUMNS = ['time','model','battery','temperature_F','humidity']
SQL_INSERT = f"INSERT INTO S3318P_tb({','.join(COLUMNS)}) VALUES({','.join(['%s']*len(COLUMNS))})"
DB_HOST = "localhost"
DB_USER = "user"
DB_PASSWD = "pass"
DB_SCHEMA = "db_name"

def main():
    df = pd.read_csv(CSV_FILENAME)
    values = df.iloc[-1][keep_cols].values
    empty_df = df.iloc[[]]
    empty_df.to_csv(CSV_FILENAME, index=False)

    connection = mysql.connector.connect(host=DB_HOST, user=DB_USER, passwd=DB_PASSWD, db=DB_SCHEMA)
    cursor = connection.cursor()
    cursor.execute(SQL_INSERT, values)
    connection.commit()
    cursor.close()

if __name__ == '__main__':
    main()
Will man die neusten Werte aller Sensoren, muß man halt entsprechende Pandas-Funktionen nutzen:

Code: Alles auswählen

sensor_rows = df.sort_values('time', ascending=False).drop_duplicates('model')
values = sensor_rows[COLUMNS].values
`execute` muß man dann natürlich durch `execute_many` ersetzen.
Antworten