string mit Datenbank abgleichen

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
jarhead_r
User
Beiträge: 2
Registriert: Montag 9. Juli 2018, 08:46

Montag 9. Juli 2018, 09:01

Hallo zusammen

Ich bin am versuchen ein kleines Programm zu optimieren bzw für meine zwecke abzuändern.

Vorab das Programm A liest aus einem anderen Programm B Datenzeilen aus und schreibt diese in ein String, nun möchte ich das bevor das Programm A das ganze in die DB schreibt, den String "message" auf 1:, !, <SUB> überprüft und wenn diese Vorhanden sind die Zeile verwirft und keinen Eintrag in die DB macht.

Weiters möchte ich das er denn denn kompletten String bevor er ihn in die DB schreibt, überprüft ob er in den letzten 5 min schon einmal in die DB geschrieben . Wenn ja wieder verwerfen und mit neuer Zeile fortfahren.

Code: Alles auswählen

import time
import sys
import subprocess
import os
import mysql
import mysql.connector

try:
    connection = mysql.connector.connect(host = "****", user = "****", passwd = "****", db = "POCSAG1200")
except:
    print "Keine Verbindung zum Server"
    exit(0)

def curtime():
    return time.strftime("%Y-%m-%d %H:%M:%S")

with open('Fehler.txt','a') as file:
    file.write(('#' * 20) + '\n' + curtime() + '\n')

multimon_ng = subprocess.Popen("sudo rtl_fm -f 147.320M -s 22050 | multimon-ng -a POCSAG1200 -f alpha -t raw -",
                               #stdin=rtl_fm.stdout,
                               stdout=subprocess.PIPE,
                               stderr=open('error.txt','a'),
                               shell=True)

try:
    while True:
        line = multimon_ng.stdout.readline()
        multimon_ng.poll()                                                                  # multimon wird gestartet
        if line.__contains__("Alpha:"):                                                     # Die Ausgabe wird nach dem Text "Alpha" durchsucht
            if line.startswith('POCSAG'):
                address = line[21:28].replace(" ", "")		                            # Zeichen 22 bis 28. RIC wird in die Variabel "adress" gelegt.Leerzeichen werden entfernt. Wenn kleiner als 7 Stellen wird der Rest vorne weg mit 0 aufgefüllt (zfill). Feld muss in VARCHAR vorliegen, führendene 0 wird sonst nicht geschrieben!
                subric = line[40:41].replace(" ", "").replace("3", "4").replace("2", "3").replace("1", "2").replace("0", "1") # Sub-RIC auslesen und anpassen (3=4, 2=3, 1=2, 0=1)
                message = line.split('Alpha:   ')[1].strip().rstrip('').strip()        # Die Nachricht wird nach dem Bereich Alpha in die Variabel "message" abgelegt. EOT wird entfernt.
                output=(curtime()+' '+ address+' '+ subric+' '+ message+'\n')               # Es wird ein String in die Variabel "output" gelegt. Hier: Adresse + Zeitstempel + Text
				if message line.startswith('<NUL>'):
                print curtime(), address, subric, message                                   # Die Meldung wird im Terminal angezeigt
                with open('POCSAG.txt','a') as f:                                           # Der String output wird in die Datei POCSAG.txt geschrieben.
                    f.write(output)
                #Datensatz einfügen per mySQL
                cursor = connection.cursor()
                cursor.execute("INSERT INTO DATENBANKTABELLE (time,ric,funktion,text,einsatz) VALUES (%s,%s,%s,%s,%s)",(curtime(),address,subric,message,'0',))
                cursor.close()
                connection.commit()
     
                
except KeyboardInterrupt:
    os.kill(multimon_ng.pid, 9)
freue mich auf Hilfe

Gruss Jarhead
Sirius3
User
Beiträge: 8277
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 9. Juli 2018, 10:19

Wo ist die konkrete Frage?

Zum Code:
niemals nackte except benutzen. Die Fehlerbehandlung bei connect ist sinnfrei. Wenn man sie komplett wegläßt, beendet sich das Programm an der Stelle automatisch und liefert vielleicht auch noch sinnvolle Hinweise, warum keine Verbindung zustande kommen konnte.

Das zweite exception-Handling ist auch falsch, da nicht nur bei einem Ctrl-C aufgeräumt werden soll, sondern bei jedem Fehler, daher gehört die kill-Zeile in den finally-Block. Popen-Objekte kennen auch eine kill-Methode.

Statt der while-True-Schleife und dem readline solltest Du eine for-Schleife direkt über stdout benutzen.
Was soll das `poll` bewirken?

Magische Funktionen (die mit den Doppelunterstrichen) sollten nicht direkt aufgerufen werden. __contains__ ist der in-Operator.
Magische Indizes in Zeilen sollte man vermeiden, weil nicht robust. Die .replace sehen auch seltsam aus. Dreimal hintereinander strip aufzurufen tut nichts anderes, als was nicht schon beim erstenmal passiert. Welchem Muster folgt also die Ausgabe und welche Information soll daraus extrahiert werden?
Statt Strings mit + zusammenzustückeln nimmt man die .format-Methode.

Die Zeile, die mit "if message" anfängt ist falsch eingerückt und auch keine gültige Syntax.
Zeiten sollten als Datetime-Objekt an die Datenbank übergeben werden, nicht als String. Wenn sowieso nur die aktuelle Zeit eingetragen wird, kann man auch die SQL-Funktion NOW benutzen. Warum heißt ric address, funktion subric und text message, oder umgekehrt? Was ist denn nun die richtige Bezeichnung für die Felder?
Benutzeravatar
__blackjack__
User
Beiträge: 1089
Registriert: Samstag 2. Juni 2018, 10:21

Montag 9. Juli 2018, 10:23

@jarhead_r: Das was Du da zeigst kompiliert nicht. Und ich habe jetzt keine Lust zu raten ob Du nun `message` oder `line` in dem ``if`` meintest und wie das eingerückt werden muss. Sieht in meinem Editor sowieso ziemlich schrecklich aus wegen der viel zu langen Zeilen, hauptsächlich durch die Kommentare rechts von Code. Die gehören eigentlich eher zwischen die Code-Zeilen, vor den Teil den sie kommentieren. Sonst wird das nämlich echt schwer eine maximale Zeilenlänge von 80 Zeichen einzuhalten.

Diese kaputte Zeile ist zudem mit Tabs eingerückt: Konvention ist vier Leerzeichen pro Ebene. Siehe auch den Style Guide for Python Code.

Bei Ausnahmebehandlung keine nackten ``except:`` ohne konkrete Ausnahmen verwenden. Das behandelt *alle* Ausnahmen, auch solche mit denen man gar nicht gerechnet hat. Beispielsweise auch `NameError` oder `AttributeError` wenn man sich im ``try``-Block verschrieben hat. Solche Fehler dann zu finden ist nicht einfach/schön.

Die Behandlung selbst ist auch nicht gut weil die Ausnahme bei Verbindungsproblemen auch einen Text und die MySQL-Fehlernummer enthält. Der Benutzer möchte ja normalerweise nicht (nur) wissen *das* die Verbindung fehlgeschlagen ist, sondern auch *warum*, damit er das Problem lösen kann.

Wenn man an dieser Stelle die Ausnahmebehandlung einfach weg lässt, passiert im Grunde genau das gleiche wie mit der Behandlung, nur dass der Benutzer deutlich mehr Information über das Problem bekommt.

Vor das `exit()` hätte noch ein ``sys.`` gehört, denn die Funktion `exit()` gibt es laut Dokumentation so überhaupt nicht. Und Konvention bei Programmen ist das 0 als Rückgabecode bedeutet, dass alles in Ordnung ist. Was bei einem Verbindungsproblem eher nicht der Fall ist.

`file` ist der Name des eingebauten Dateityps, den sollte man nicht an etwas anderes binden.

Ist das Absicht das es einmal eine 'Fehler.txt' und dann noch eine 'error.txt' gibt? Falls nein, und davon gehe ich mal aus, ist das ein schönes Beispiel warum man Redundanz vermeiden sollte. Wenn man den Namen mehrfach braucht, sollte man sich eine Konstante dafür definieren.

Dateien die man öffnet, sollte man auch wieder schliessen. Was nicht geht wenn man das `open()` direkt in die Argumentliste schreibt.

Die ``while True:``-Schleife sollte eigentlich eine ``for``-Schleife sein. Falls ``for line in multimon_ng.stdout:`` nicht geht, wegen dem Puffer den das intern benutzt, sollte man sich wenigstens aus dem `readline()` mit `iter()` einen Iterator erstellen.

Der `poll()`-Aufruf macht keinen Sinn und der Kommentar in der Zeile stimmt nicht — weg damit.

Die ”magischen” Methoden nur aufrufen wenn es nicht anders geht. `__contains__()` ist für den ``in``-Operator, also sollte man den auch verwenden. Der Kommentar dazu ist redundant. Man sollte nicht kommentieren was der Code macht, denn das steht da ja schon als Code, sondern warum er das macht, sofern das nicht offensichtlich ist.

Kommentare sollten zudem korrekt sein, denn wenn der Leser sich nicht darauf verlassen kann das der Kommentar zum Code passt, werden letztlich alle Kommentare wertlos, weil man keinem mehr vertrauen kann.

Die Abfolge ``.strip().rstrip('').strip()`` ist nach dem ersten `strip()` sinnfrei.

Möchtest Du den Zeitstempel tatsächlich dreimal erstellen, jedes mal mit der Möglichkeit das dann mindestens einer eine andere Zeit hat als die anderen beiden? Die Zeit läuft zwischen den Aufrufen ja weiter.

”Magische” Zahlen sollte man durch Konstanten ersetzen. Die Signale gibt es netterweise im `signal`-Modul schon als Konstanten. Allerdings haben `Popen`-Objekte bereits entsprechende Methoden, so dass man sich gar nicht mit Signalnummern herumschlagen braucht.

Ist bei ``multimon_ng`` tatsächlich ein SIGKILL nötig? Reicht da kein SIGTERM?

Zudem möchte man das ja eigentlich *in jedem Fall* ausführen wenn der ``try``-Block verlassen wird, also gehört das nicht ins ``except`` sondern in ein ``finally``.

Völlig ungetesteter Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
from __future__ import absolute_import, division, print_function
import subprocess
import time
from contextlib import closing
from functools import partial

import mysql.connector


get_timestamp = partial(time.strftime, '%Y-%m-%d %H:%M:%S')


def main():
    connection = mysql.connector.connect(
        host='****', user='****', passwd='****', db='POCSAG1200'
    )

    with open('Fehler.txt', 'a') as error_file:
        error_file.write('#' * 20 + '\n' + get_timestamp() + '\n')

        multimon_ng = subprocess.Popen(
            'sudo rtl_fm -f 147.320M -s 22050'
            ' | multimon-ng -a POCSAG1200 -f alpha -t raw -',
           stdout=subprocess.PIPE,
           stderr=error_file,
           shell=True
        )
        try:
            # 
            # TODO Prüfen ob ``for line in multimon_ng.stdout:``
            #   tatsächlich nicht möglich ist.
            # 
            for line in iter(multimon_ng.stdout.readline, ''):
                if 'Alpha:' in line:
                    if line.startswith('POCSAG'):
                        # 
                        # BUG Diese Beschreibung stimmt nicht mit dem
                        #   Code überein.
                        # 
                        # Wenn kleiner als 7 Stellen wird der Rest vorne
                        # weg mit 0 aufgefüllt (zfill). Feld muss in
                        # VARCHAR vorliegen, führendene 0 wird sonst nicht
                        # geschrieben!
                        # 
                        address = line[21:28].replace(' ', '')
                        sub_ric = (
                            line[40:41]
                                .replace(' ', '')
                                .replace('3', '4')
                                .replace('2', '3')
                                .replace('1', '2')
                                .replace('0', '1')
                        )
                        message = line.split('Alpha:   ', 2)[1].strip()
                        timestamp = get_timestamp()
                        print(timestamp, address, sub_ric, message)
                        with open('POCSAG.txt', 'a') as pocsag_file:
                            pocsag_file.write(
                                '{} {} {} {}\n'.format(
                                    timestamp, address, sub_ric, message
                                )
                            )
                        with closing(connection.cursor()) as cursor:
                            cursor.execute(
                                'INSERT INTO DATENBANKTABELLE'
                                ' (time, ric, funktion, text, einsatz)'
                                ' VALUES (%s, %s, %s, %s, %s)',
                                (timestamp, address, sub_ric, message, '0')
                            )
                        connection.commit()
        except KeyboardInterrupt:
            pass  # Benutzer hat mit Strg+C abgebrochen.
        finally:
            # 
            # TODO Ist `kill()` eventuell Overkill und `terminate()`
            #   würde völlig ausreichen?
            # 
            multimon_ng.kill()


if __name__ == '__main__':
    main()
Das ist allerdings jetzt schon zu viel für eine Funktion. So lang und so tief verschachtelt sollte Code eher nicht werden. Bevor Du da also noch mehr hinzufügst, teil das mal besser sinnvoll auf Funktionen auf.
“Capitalism is the astounding belief that the most wickedest of men will do the most wickedest of things for the greatest good of everyone.” – John Maynard Keynes
jarhead_r
User
Beiträge: 2
Registriert: Montag 9. Juli 2018, 08:46

Montag 9. Juli 2018, 17:17

Hallo zusammen

Danke erst einmal für eure Antwort, mir ist bewusst das wahrscheinlich vieles nicht schön programmiert ist da ich es aus verschiedenen Code schnippseln zusammen gebaut habe.

Grundsätzlich funktioniert es aber :lol:
Bild

um in das ganze ein wenig licht zu bringen, das Programm bewirkt nichts anderes als das es Multimon-NG startet, das hört eine Funkfrequenz ab und gibt mir die einzelnen Nachrichten auf dem Bildschirm aus.

Mein Code soll eigentlich die einzelnen Nachrichten abfangen und in die DB schreiben. Dies tut es auch! eigentlich sehr sauber.
Bild

Die eigentliche frage ist wie kriege ich es sauber hin das ich zum beispiel Nachrichten die mit <SOH> anfangen ganz rauslöschen kann bzw. nicht in die DB schreiben.

oder und wenn in einem text die <NUL> steht, einfach nur diese Zeichenkette löschen.

@__blackjack__ & @Sirius3

Danke für euren Input!, ich setze mich dran und werde mich noch ein wenig einlesen müssen versuche aber euer Ratschläge gleich mit einfließen zu lassen!

Gruss Jarhead
Sirius3
User
Beiträge: 8277
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 9. Juli 2018, 18:15

In Wirklichkeit hast Du also Zahlen für address und sub_ric, dann solltest Du auch die Zeilen entsprechend parsen:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
from __future__ import absolute_import, division, print_function
import subprocess
from contextlib import closing
from datetime import datetime as DateTime
import mysql.connector

def parse_multimon_output(lines):
    """ Generator yields tuple (address, function, alpha) """
    for line in lines:
        parts = line.split(None, 6)
        # ignoriere alle ungültigen Zeilen
        if parts[0] == "POCSAG1200:" and len(parts) == 7:
            yield int(parts[2]), int(parts[4]), parts[6].strip()

def main():
    connection = mysql.connector.connect(
        host='****', user='****', passwd='****', db='POCSAG1200'
    )

    with open('Fehler.txt', 'a') as error_file:
        timestamp = DateTime.now()
        error_file.write('{}\n{}\n'.format('#' * 20, timestamp))
        multimon_ng = subprocess.Popen(
            'sudo rtl_fm -f 147.320M -s 22050'
            ' | multimon-ng -a POCSAG1200 -f alpha -t raw -',
           stdout=subprocess.PIPE,
           stderr=error_file,
           shell=True
        )
        try:
            for address, function, message in parse_multinom_output(multimon_ng.stdout):
                timestamp = DateTime.now()
                sub_ric = function + 1
                with open('POCSAG.txt', 'a') as pocsag_file:
                    pocsag_file.write(
                        '{:%Y-%m-%d %H:%M:%S} {} {} {}\n'.format(
                            timestamp, address, sub_ric, message
                        )
                    )
                with closing(connection.cursor()) as cursor:
                    cursor.execute(
                        'INSERT INTO DATENBANKTABELLE'
                        ' (time, ric, funktion, text, einsatz)'
                        ' VALUES (%s, %s, %s, %s, %s)',
                        (timestamp, address, sub_ric, message, 0)
                    )
                connection.commit()
        except KeyboardInterrupt:
            # Benutzer hat mit Strg+C abgebrochen.
            pass
        finally:
            multimon_ng.kill()


if __name__ == '__main__':
    main()
.replace und startswith kennst Du ja schon, dann sollte es für Dich auch keine Probleme mit <SOH> und <NUL> geben.
Antworten