Merkwürdiges Zeichen in Datenbank-Feldern

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Kurt.Wallander
User
Beiträge: 28
Registriert: Donnerstag 24. September 2015, 22:46

Hallo,

ich habe ein Skript geschrieben, mit dem ich EPG-Daten aus meinem DVB-Receiver (Enigma2) auslese und in eine MySQL-Datenbank schreibe. Ist noch nicht ganz fertig, funktioniert aber so weit schon ganz gut. Das Prinzip: Über das Webinterface des Receivers werden die EPG-Webseiten der jeweiligen Sender eingelesen und dann in einzelne Einträge zerlegt. Aus Performance-Gründen werde ich die heruntergeladenen HTML-Seiten aber noch als Files lokal ablegen und dann erst parsen. Habe ich schon ausprobiert - dauert nur halb so lange.

Jetzt aber zu meinem Thema, zum ABER ...

Überall dort, wo im Ausgangsmaterial, der HTML-Seite also, ein <br> für einen Zeilenumbruch steht, taucht in meiner Datenbank ein Š auf (großes S mit umgekehrtem Zirkonflex). Ich habe mir die Liste epg einfach mal auf der Konsole ausgeben lassen, dort steht anstelle des <br> ein UTF-8-Code: \xc2\x8a.

Kann mir mal bitte jemand aufs Pferd helfen?

Hier der Code:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf8 -*-

import MySQLdb
from contextlib import closing
import re
import urllib

import login


EVENT_END = '</e2event>'
TIME_START = '<e2eventstart>'
TIME_END = '</e2eventstart>'
DURATION_START = '<e2eventduration>'
DURATION_END = '</e2eventduration>'
TITLE_START = '<e2eventtitle>'
TITLE_END = '</e2eventtitle>'
DESCRIPTION_START = '<e2eventdescription>'
DESCRIPTION_END = '</e2eventdescription>'
LONG_DESCRIPTION_START = '<e2eventdescriptionextended>'
LONG_DESCRIPTION_END = '</e2eventdescriptionextended>'


def get_events(lines):

    i = 0
    item = ""
    events = []
    while i < len(lines):
        item = item + lines[i]
        if EVENT_END in lines[i]:
            events.append(item)
            item = ""
        i += 1

    return events


def parse_event(event):

    data = []
    data.append(re.search(
        TITLE_START+'(.*?)'+TITLE_END, event).group(1))
    data.append(re.search(
        DESCRIPTION_START+'(.*?)'+DESCRIPTION_END, event).group(1))
    data.append(re.search(
        LONG_DESCRIPTION_START+'(.*?)'+LONG_DESCRIPTION_END, event).group(1))
    data.append(re.search(
        TIME_START+'(.*?)'+TIME_END, event).group(1))
    data.append(re.search(
        DURATION_START+'(.*?)'+DURATION_END, event).group(1))

    return data


def add_events_to_db(connection, cursor, alias, events):

    i = 0

    while i < len(events):

        data = parse_event(events[i])

        title = data[0].decode('utf-8')
        description = data[1].decode('utf-8')
        long_description = data[2].decode('utf-8')
        time = data[3]
        duration = data[4]

        cursor.execute('INSERT INTO epg \
        (alias, title, description, long_description, time, duration) \
        VALUES (%s,%s,%s,%s,%s,%s)',
                       (alias, title, description,
                        long_description, time, duration))

        i += 1

    connection.commit()


def main():

    with closing(MySQLdb.connect(
            login.DB_HOST, login.DB_USER,
            login.DB_PASSWORD, login.DB_DATABASE)) as connection:
        with closing(connection.cursor()) as cursor:

            cursor.execute('TRUNCATE TABLE epg')
            connection.commit()

            cursor.execute('SELECT alias, url FROM lamedb')
            result = cursor.fetchall()

            for db_record in result:

                alias = db_record[0]
                url = db_record[1]

                f = urllib.urlopen(url)
                epg = f.readlines()

                events = get_events(epg)
                add_events_to_db(connection, cursor, alias, events)


if __name__ == '__main__':
    main()

Alternativ habe ich dies hier ausgetestet (zwischenspeichern und dann erst parsen, s.o.). Hatte aber denselben Effekt:

Code: Alles auswählen

# ...
            for db_record in result:

                alias = db_record[0]
                url = db_record[1]
                local_file = '/home/pi/radiobeere/epg/' + alias + '.epg'

                urllib.urlretrieve(url, local_file)

            for db_record in result:
                alias = db_record[0]
                local_file = '/home/pi/radiobeere/epg/' + alias + '.epg' 

                f = open(local_file, 'r')
                epg = f.readlines()
Zuletzt geändert von Anonymous am Montag 30. November 2015, 23:31, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@Kurt.Wallander: Verwende für XML einen XML-Parser und keine Zeichenkettenoperationen und regulären Ausdrücke. Davon abgesehen dass das nicht allgemein funktioniert und wahrscheinlich irgendwann mal auf die Nase fällt wenn sich an der XML-Serialisierung etwas ändert mit dem Dein selbstgebasteltes nicht zurecht kommt, kommt von einem XML-Parser der Text als Unicode. Wenn man dann noch die Datenbank passend eingestellt hat, dann braucht man sich da nicht weiter um irgendwelche Kodierungen kümmern.
Kurt.Wallander
User
Beiträge: 28
Registriert: Donnerstag 24. September 2015, 22:46

Moin BlackJack,

danke dir für den Tipp. War auch mein erster Gedanke, aber allein der Fingerübung wegen wollte ich erstmal den Fußweg gehen. Äh ... doofes Bild. Egal. Ich habe ein bisschen recherchiert und meine, cElementTree könnte das Mittel der Wahl sein. Deine Empfehlung? Und nur der Interesse halber: Hast du eine Idee, an welchem Punkt ich in dem ursprünglichen Script ansetzen müsste, um auch dort das gewünschte Ziel zu erreichen? Mit dem .decode('utf-8') habe ich ja immerhin die verkrüppelten Umlaute eliminieren können.

Einen schönen Tag!
Kurt
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kurt.Wallander: Wo erscheint das Š? Wie liest Du die Datenbank wieder aus? Welches Encoding hat die Datenbanktabelle? Was schreibst Du rein?
Kurt.Wallander
User
Beiträge: 28
Registriert: Donnerstag 24. September 2015, 22:46

@Sirius3, zu deinen Fragen:
Sirius3 hat geschrieben:@Kurt.Wallander: Wo erscheint das Š? Wie liest Du die Datenbank wieder aus? Welches Encoding hat die Datenbanktabelle? Was schreibst Du rein?
Die Ausgabe (auf einer Website, PHP) ist noch nicht geschrieben, im Moment lese ich noch nichts aus. Ich sehe dieses Zeichen in phpmyadmin. Die Tabelle ist in utf-8 kodiert. Reingeschrieben wird das, was ich vorher aus der XML-Datei geparst habe, im Wesentlichen Text und Zahlen. Das Problem taucht nur dort auf, wo in der XML-Datei Zeilenumbrücke sind, also <br>.
BlackJack

@Kurt.Wallander: Was steht denn konkret in der XML-Datei an der Stelle?
Kurt.Wallander
User
Beiträge: 28
Registriert: Donnerstag 24. September 2015, 22:46

BlackJack hat geschrieben:@Kurt.Wallander: Was steht denn konkret in der XML-Datei an der Stelle?
Hmmm ... ganz merkwürdig. Habe mir die Datei jetzt einfach mal im Terminal per wget geholt, und da kriege ich auch Murks. Nix <br>. Da habe ich mich gestern wohl verguckt. Komisch, komisch, komisch. So sieht eines der Child-Elemente aus:

Code: Alles auswählen

<e2event>
                <e2eventid>63</e2eventid>
                <e2eventstart>1449014700</e2eventstart>
                <e2eventduration>14100</e2eventduration>
                <e2eventcurrenttime>1449010768</e2eventcurrenttime>
                <e2eventtitle>Tonart</e2eventtitle>
                <e2eventdescription></e2eventdescription>
                <e2eventdescriptionextended>02:00-02:05 Nachrichten^Ê^Ê03:00-03:05 Nachrichten^Ê^Ê04:00-04:05 Nachrichten^Ê^ÊAmericana^ÊModeration: Uwe Golz</e2eventdescriptionextended>
                <e2eventservicereference>1:0:1:6d6c:0437:0066:ffff0000:0:0:0:</e2eventservicereference>
                <e2eventservicename>DKULTUR</e2eventservicename>
        </e2event>
Ich fürchte fast, da hat der "Absender" ein Problem, nicht der Abnehmer, also ich. Na ja, ich damit auch. Dann wäre jetzt wohl die nächste Option, diese merkwürdigen Zeichen einfach per rexexp rauszufummeln. Hmmm, nicht wirklich befriedigend.
Kurt.Wallander
User
Beiträge: 28
Registriert: Donnerstag 24. September 2015, 22:46

Aber immerhin ein Teilerfolg: Ich habe das Skript erfolgreich auf Basis von cElementTree umgeschrieben bekommen und kann jetzt die Daten auch ohne lokale Temp-Files flott einlesen:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf8 -*-

import MySQLdb
from contextlib import closing
import urllib
try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

import login


TIME = 'e2eventstart'
DURATION = 'e2eventduration'
TITLE = 'e2eventtitle'
TEASER = 'e2eventdescription'
SUMMARY = 'e2eventdescriptionextended'


def retrieve_epg(connection, cursor, db_result):

    epg = []

    for db_record in db_result:

        alias = db_record[0]
        url = db_record[1]
        f = urllib.urlopen(url)
        epg.append(f.read())

    return epg


def parse_epg(epg):

    time = []
    duration = []
    title = []
    teaser = []
    summary = []

    root = ET.fromstring(epg)

    for element in root.iter(TIME):
        time.append(element.text)
    for element in root.iter(DURATION):
        duration.append(element.text)
    for element in root.iter(TITLE):
        title.append(element.text)
    for element in root.iter(TEASER):
        teaser.append(element.text)
    for element in root.iter(SUMMARY):
        summary.append(element.text)

    return time, duration, title, teaser, summary


def add_to_db(connection, cursor,
              alias, time, duration, title, teaser, summary):

    i = 0

    while i < len(time):

        if not teaser[i]:
            teaser[i] = ''
        if not summary[i]:
            summary[i] = ''

        cursor.execute('INSERT INTO epg \
        (alias, title, teaser, summary, time, duration) \
        VALUES (%s,%s,%s,%s,%s,%s)',
                       (alias, title[i], teaser[i],
                        summary[i], time[i], duration[i]))

        i += 1

    connection.commit()


def main():

    with closing(MySQLdb.connect(
            login.DB_HOST, login.DB_USER,
            login.DB_PASSWORD, login.DB_DATABASE)) as connection:
        with closing(connection.cursor()) as cursor:

            cursor.execute('TRUNCATE TABLE epg')
            connection.commit()
            cursor.execute('SELECT alias, url FROM lamedb')
            db_result = cursor.fetchall()

            epg = retrieve_epg(connection, cursor, db_result)

            i = 0

            for db_record in db_result:

                alias = db_record[0]
                time, duration, title, teaser, summary = parse_epg(epg[i])
                add_to_db(connection, cursor,
                          alias, time, duration, title, teaser, summary)

                i += 1


if __name__ == '__main__':
    main()
BlackJack

@Kurt.Wallander: Das nützt jetzt nicht so viel ohne zu wissen wie die Daten kodiert sind und wie Deine Anzeige sie interpretiert hat. Man müsste wissen welche Bytewerte da konkret in der Datei stehen.
Kurt.Wallander
User
Beiträge: 28
Registriert: Donnerstag 24. September 2015, 22:46

BlackJack hat geschrieben:@Kurt.Wallander: Das nützt jetzt nicht so viel ohne zu wissen wie die Daten kodiert sind und wie Deine Anzeige sie interpretiert hat. Man müsste wissen welche Bytewerte da konkret in der Datei stehen.
Hast du einen Tipp, wie ich das rauskriege? Stehe gerade heftigst auf meiner eigenen Leitung (ich glaube, ich muss mal ins Bett).
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kurt.Wallander: im ersten Beitrag hast Du noch geschrieben, dass das Zeichen 0x8A ist: Line Tabulation Set. Das macht ja auch bei den Daten Sinn. Wenn Du phpMyAdmin zum Anzeigen verwendest, scheint Dein Browser aber dieses Kontrollzeichen nicht zu mögen und stellt statt dessen das Zeichen der Windows CodePage-1254 dar: Š. Das ist also kein Encoding-Problem von XML, Python oder MySQL sondern ein Darstellungsproblem des Browsers.

Zum Code: Statt time, duration, ... in 5 parallelen Listen zu speichern solltest Du in EINER Liste jeweils 5-Tuple speichern. Das macht die ganze Verarbeitung auch deutlich einfacher. Ähnliches gilt auch für Dein epg - db_record - Gedöns.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Kurt.Wallander:
Du kannst die Bytewerte der Datei mit einem Hex-Editor inspizieren. Alternativ gehts auch mit `.encode('hex')` bei einem Bytestring in Python. Poste doch mal die Original- und Hexstrings der fraglichen Stellen in der folgenden Form - evtl. lässt sich ermitteln, was da schief läuft. So etwa:

Code: Alles auswählen

In [1]: 'äöüß'.encode('hex')
Out[1]: 'c3a4c3b6c3bcc39f'
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@jerch: da läuft nichts schief. Der OP muß nur entscheiden, wie er mit einem Line-Tab umzugehen wünscht.
Kurt.Wallander
User
Beiträge: 28
Registriert: Donnerstag 24. September 2015, 22:46

@Sirius, wofür steht OP? Ordentlicher Programmierer? Output? Opa ohne A? :-)
BlackJack

@Kurt.Wallander: OP steht hier für Original Poster.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Kurt.Wallander hat geschrieben:wofür steht OP?
Original Poster. Diese Abkürzung stammt noch aus der Guten Alten Zeit(TM) des Usenets. Gemeint ist damit der Threadersteller.
In specifications, Murphy's Law supersedes Ohm's.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sirius3:
Das erklärt nicht, was dieses '^Ê' sein soll.
Kurt.Wallander
User
Beiträge: 28
Registriert: Donnerstag 24. September 2015, 22:46

Ah, wieder was gelernt. Danke!
Kurt.Wallander
User
Beiträge: 28
Registriert: Donnerstag 24. September 2015, 22:46

@jerch, ich schicke den Code heute Abend. Bin gerade @work.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@jerch: das Escapezeichen ^ zeigt normalerweise an, dass ein nichtdruckbares Zeichen durch ein druckbares ersetzt wurde. Bekannt ist vielleicht ^M als Zeichen für Carriage return. Die Rechnung ist simple: M hat den Codepoint 77, zieht man 64 ab, bekommt man 13 (CR). Ê hat den Codepoint 202, zieht man 64 ab bekommt man 138 oder 0xBA, also Line Tabulatorset.
Antworten