Daten aus Webseite auslesen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Trial&Error
User
Beiträge: 8
Registriert: Freitag 24. Juni 2016, 09:21

Hallo Forum,

bin ganz frisch in der Welt von Python angekommen. Bislang konnte ich meine kleinen Scripte immer mit einer Online Suche zusammenbasteln.

Jetzt würde ich mich gerne mal an ein etwas größeres Projekt trauen, das mir aber täglich Arbeit abnehmen würde.

Zur Situation:
Ich habe bei Kunden ein Embedded Device stehen, das jeweils über eine Webseite Informationen in Form einer Tabelle bereitstellt, die ich Täglich (einmal) abfragen und in eine Tabelle tragen muss.
Wie man sich denken kann ist das eine ziemlich nervige Aufgabe, die ich gerne automatisieren würde.

Wunsch:
Ein kleines Script, dass per Cron jeden Morgen die Daten von den Geräten ausliest und in eine Tabelle schreibt.

Status:
Ich habe inzwischen herausgefunden, wie ich die Webseiten in Python einlesen kann.

Code: Alles auswählen

class AppURLopener(urllib.request.FancyURLopener):
    version = "Mozilla/5.0"

opener = AppURLopener()
response = opener.open('<ip eines Gerätes>')

for line in response:
    print(line)                 # Debug
Wie ich herausgefunden habe ist es notwendig einen „normalen Browser“ zu „emulieren“, da der Entwickler der Boxen anscheinend einen Schutz integriert hat, der das Parsen durch Bots verhindern soll. (Um mögliche rechtliche Bedenken vorzubeugen. Die Geräte gehören uns und werden teilweise von uns Administriert. Haben aber halt keine offizielle Schnittstelle.)

Als Ergebnis erhalte ich jetzt den HTML Code der Seite. Aber wie kann ich in dieser Seite jetzt die Tabelle finden und wie kann ich die Daten daraus auslesen? Ist es ein Problem, dass die Seiten je nach Standort unterschiedlich aussehen und sich die Tabellen an unterschiedlichen Positionen im Code befinden?

Hier mal ein Beispiel für so eine Tabelle:

Code: Alles auswählen

<div id="table">
	<ul id="table">
						
		<li class="x">
			<a href="__System URL__" class="type_url" title="__System Bezeichnung__">
			   <span class="type">
				__auszulesender Typ__
			   </span>
			   <span class="value">
			   	__auszulesender Wert__
			   </span>
			</a>
		</li>						
		<li class="x">
			<a href="__System URL__" class="type_url" title="__System Bezeichnung__">
			   <span class="type">
				__auszulesender Typ__
			   </span>
			   <span class="value">
			   	__auszulesender Wert__
			   </span>
			</a>
		</li>
		<li class="x">
			<a href="__System URL__" class="type_url" title="__System Bezeichnung__">
			   <span class="type">
				__auszulesender Typ__
			   </span>
			   <span class="value">
			   	__auszulesender Wert__
			   </span>
			</a>
		</li>
		...			
	</ul>
</div>
Ich hoffe, dass ich meine Frage in der richtige Rubrik gestellt und beim Suchen im Forum die Lösung nicht übersehen habe :)
Scheint hier ja schon einige (und irgend wie immer Anfänger :? ) zu geben, schon ein ähnliches Problem hatten aber auf deren Fragen es bislang keine für mich adaptierbare Antwort zu geben scheint.
viewtopic.php?f=1&t=37095&p=284040&hili ... en#p284040
viewtopic.php?f=7&t=34536&p=262972&hili ... en#p262972 --> könnte die hier angesprochene `lxml.html` bei meinem Problem hilfreich sein?

So, jetzt wo ich fertig bin frage ich mich gerade ob das nicht etwas zu lang geworden ist. Ich hoffe einfach mal auf Antworten :)

[EDIT 1]
Habe gerade diese Seite gefunden: viewtopic.php?f=1&t=25781&p=194169&hili ... en#p194169
Anscheinend sind `BeautifulSoup` und `lxml.html` die richtigen Bibliotheken.

[EDIT 2]
Dann dürfte das die passende Doku für lxml.html sein: http://lxml.de/lxmlhtml.html
Gibt es die auch auf Deutsch?
Pok1990
User
Beiträge: 6
Registriert: Freitag 24. Juni 2016, 14:34

Hallo,

Ich hatte ein ähnliches Problem, weil ich einmal bestimmte links aus Webseiten extrahieren wollte.

eingesetzt habe ich dann das hier -->https://docs.python.org/3.5/library/htm ... tml.parser

ich hab dann eine Klasse damit geschrieben, welche mir aus den bestimmten HTML-tags die Werte extrahiert.
Im Grunde würde das bei dir auch funktionieren, solange du eine normale html-seite vom Server bekommst.

du kipppst quasi in die Klasse den html-text rein, diese parst dann den String durch und du entscheidest bei jeden Tag im html-text was genau du machen willst.
Die Logik dahinter musst du dann Programmieren, aber du meintest ja das du Daten aus einer Tabelle ablesen möchtest.
du kannst dann nach Zeilenanfängen, und spaltenanfängen und spalten-enden und Zeilen-enden eine Logik einprogrammieren und diese Daten dann in ein Dict schreiben. am ende gibst du dann das Dict weiter in die höhere Programmlogik.
Trial&Error
User
Beiträge: 8
Registriert: Freitag 24. Juni 2016, 09:21

@Pok1990
Danke für die Info. Das hört sich schon mal ganz vielversprechend an.
Ich muss aber gestehen, dass ich (hüstel) bisher noch nicht objektorientiert Programmiert habe.

Ich werde mir die Seite aber mal durchlesen. Eventuell ist das ja gar nicht mal so schwer.

---

Bezüglich lxml:
Die Installation ist auf der Seite http://lxml.de/installation.html beschrieben. Unter Ubuntu einfach

Code: Alles auswählen

sudo apt-get install python3-lxml
eingeben.

Bei normalen Webseiten scheint das auch recht gut zu funktionieren. Da ich bislang noch nicht wirklich verstanden habe, was mir die original Dokumentation erklären will habe ich mal dieses kleine Beispiel nachgebaut: http://docs.python-guide.org/en/latest/ ... os/scrape/
Das funktioniert auch. Ich bekomme es nur irgend wie nicht auf meinen Bedarf adaptiert.

[EDIT 1]
Mein Beispiel Code scheint nicht ganz korrekt zu sein. Es müsste eher so aussehen:

Code: Alles auswählen

<div id="table">
   <ul id="table">
                  
      <li class="green">
         <a href="__System URL__" class="type_url" title="__System Bezeichnung__">
            <span class="type">
            __auszulesender Typ__
            </span>
            <span class="value">
              green
            </span>
         </a>
      </li>                  
      <li class="green">
         <a href="__System URL__" class="type_url" title="__System Bezeichnung__">
            <span class="type">
            __auszulesender Typ__
            </span>
            <span class="value">
               green
            </span>
         </a>
      </li>
      <li class="yellow">
         <a href="__System URL__" class="type_url" title="__System Bezeichnung__">
            <span class="type">
            __auszulesender Typ__
            </span>
            <span class="value">
              yellow
            </span>
         </a>
      </li>
       <li class="red">
         <a href="__System URL__" class="type_url" title="__System Bezeichnung__">
            <span class="type">
            __auszulesender Typ__
            </span>
            <span class="value">
               red
            </span>
         </a>
      </li>
      ...         
   </ul>
</div>
Die Klassen der Tabelle sind die Werte, die ich herausfinden muss. Jedes Tabellenfeld enthält sowohl den Typen (im Beispiel " __auszulesender Typ__) als auch den Wert. In meinem Beispiel währe der erste Wert "green", der dem darüber stehenden " __auszulesender Typ__" zugewiesen werden muss.

[EDIT 2]
Sollte sich hier jemand mit lxml auskennen. Hier mal mein bisheriger Code:

Code: Alles auswählen

from lxml import html
import requests
page = requests.get('http://www.example.com')

tree = html.fromstring(page.content)


# Debug
# print(tree)    # <-- Das gibt gar nichts aus!?


green = tree.xpath('//[li@class="green"]/text()')


print('OK: ', green)
Wie kann ich überprüfen, ob ich überhaupt Daten aus der Webseite ausgelesen habe? print(tree) gibt mir nichts zurück. Ich hätte erwartet, dass da gie ganze Webseite drin ist.
BlackJack

@Trial&Error: Das `print()` gibt nichts aus weil es auskommentiert ist. ;-) Ansonsten sollte es so etwas ausgeben wie:
[codebox=text file=Unbenannt.txt]<Element html at 0x9ad216c>[/code]

Und wenn Du Fehlermeldungen bekommst, dann gib die bitte auch an. Am besten komplett mit Traceback 1:1 kopieren. Da gibt's nämlich einen ``XPathEvalError: Invalid expression`` weil der XPath kein korrekter XPath ist. Was im Grunde weder mit Python noch mit `lxml` zu hat, sondern mit XPath.

Am besten entwickelt man solche Abfragen in einer interaktiven Python-Shell. Da kann man ”live” mit den Objekten experimentieren und muss nicht immer das ganze Programm starten, was jedes mal wieder die Webseite abfragt. So kann man sich Stück für Stück einer Lösung nähern. Zum Beispiel in folgenden Schritten bei Deinem Beispiel:

Code: Alles auswählen

In [65]: tree.xpath('//li')
Out[65]: 
[<Element li at 0x9b025a4>,
 <Element li at 0x9b0270c>,
 <Element li at 0x9b02d4c>,
 <Element li at 0x9b02cd4>]

In [66]: tree.xpath('//li[@class="green"]')
Out[66]: [<Element li at 0x9b025a4>, <Element li at 0x9b0270c>]

In [67]: tree.xpath('//li[@class="green"]/text()')
Out[67]: ['\n         ', '\n      ', '\n         ', '\n      ']
Beziehungsweise wahrscheinlich eher so am Ende:

Code: Alles auswählen

In [73]: tree.xpath('//li[@class="green"]/span')
Out[73]: []

In [74]: tree.xpath('//li[@class="green"]//span')
Out[74]: 
[<Element span at 0x9b73e64>,
 <Element span at 0x9b73f7c>,
 <Element span at 0x9b73fcc>,
 <Element span at 0x9b73e8c>]

In [75]: tree.xpath('//li[@class="green"]//span[@class="type"]')
Out[75]: [<Element span at 0x9b73e64>, <Element span at 0x9b73fcc>]

In [76]: tree.xpath('//li[@class="green"]//span[@class="type"]/text()')
Out[76]: 
['\n            __auszulesender Typ__\n            ',
 '\n            __auszulesender Typ__\n            ']
Bei HTML das semantisch halbwegs sinnvoll ausgezeichnet wurde, kommt man mit CSS-Selektoren etwas leichter lesbar zumindest an die Elemente. Den Text muss man sich dann mit Python-Code holen:

Code: Alles auswählen

In [77]: tree.cssselect('li.green span.type')
Out[77]: [<Element span at 0x9b73e64>, <Element span at 0x9b73fcc>]

In [78]: [e.text for e in tree.cssselect('li.green span.type')]
Out[78]: 
['\n            __auszulesender Typ__\n            ',
 '\n            __auszulesender Typ__\n            ']
Pok1990
User
Beiträge: 6
Registriert: Freitag 24. Juni 2016, 14:34

Wenn du noch nicht OO gearbeitet hast dann eigne dir das an ;)

Ich arbeite zum teil in Python nur noch so, denn es erlaubt einen den eigenen Code schneller wiederzuverwerten.
selbst wenn du die klasse nur einmal benutzt und im prinzip die OO auch weglassen könntest hat es gewisse Vorteile im Design.

ich poste dir mal meine klasse... sie wird dir fast gar nicht helfen, aber ich kann ja ein wenig erklären:

Code: Alles auswählen

import html.parser
import logging
from html.entities import name2codepoint
import re

class HtmlParserHelper(html.parser.HTMLParser):

    def __init__(self, loglevel=logging.INFO):
        html.parser.HTMLParser.__init__(self)
        self.__logger = logging.getLogger(__name__)
        self.__logger.setLevel(loglevel)
        self.__logger.propagate = False
        shandler = logging.StreamHandler()
        shandler.setLevel(loglevel)
        formatter = logging.Formatter('%(levelname)s \t- %(name)s \t: %(message)s')
        shandler.setFormatter(formatter)
        if len(self.__logger.handlers) <= 0:
            self.__logger.addHandler(shandler)
            # deniy multiple prints if the class initiated multiple

        self.__currenttag_td = False
        self.__epfound = False
        self.__beginngathering = False
        self.__episodes = {}

    def handle_starttag(self, tag, attrs):
        if tag == "td":
            self.__currenttag_td = True
            for attr in attrs:
                if attr[0] == "class":
                    self.__logger.debug("attr[0] == class  passed")
                    if attr[1] == "nowrap":
                        self.__logger.debug("begingathering is true")
                        self.__beginngathering = True

        if tag == "a" and self.__epfound is True:
            title = None
            self.__logger.debug("a and epfound passing ")
            for attr in attrs:
                if  attr[0] == "title" and self.__beginngathering is True:
                    self.__episodes[self.__currentepisode][attr[1]] = None
                    title = attr[1]
                if attr[0] == "href" and self.__beginngathering is True:
                    if title == "Streamcloud":
                        self.__logger.info("Streamcloudlink found: " + "http://bs.to/" + attr[1])
                    self.__episodes[self.__currentepisode][title] = "http://bs.to/" + attr[1]
                    title = None
        self.__logger.debug("Start tag:"+ tag)
        for attr in attrs:
            self.__logger.debug("     attr:"+ str(attr))

    def handle_endtag(self, tag):
        if tag == "td":
            self.__currenttag_td = False
        if tag == "tr":
            self.__epfound = False
            self.__currentepisode = None
            self.__beginngathering = False
        self.__logger.debug("End tag  :"+ tag)

    def handle_data(self, data):
        if self.__currenttag_td and self.__epfound is False:
            isnumber = re.match(r"\d+",data)
            if isnumber:
                self.__epfound = True
                self.__currentepisode = isnumber.group()
                # here from 1 digit to 3 dits... its important for formatter
                self.__logger.debug("found a episode: " + self.__currentepisode)
                self.__episodes[self.__currentepisode] = {}
        self.__logger.debug("Data     :"+ data)


    def handle_comment(self, data):
        self.__logger.debug("Comment  :"+ data)

    def handle_entityref(self, name):
        c = chr(name2codepoint[name])
        self.__logger.debug("Named ent:"+ c)

    def handle_charref(self, name):
        if name.startswith('x'):
            c = chr(int(name[1:], 16))
        else:
            c = chr(int(name))
        self.__logger.debug("Num ent  :"+ c)
    def handle_decl(self, data):
        self.__logger.debug("Decl     :"+ data)

    def get_episodes(self):
        self.__logger.debug(self.__episodes)
        return self.__episodes
Die klasse macht nichts anderes als einen html-text entgegen zu nehmen, ihn zu parsen und dann bestimmte daten in ein Dict zu schreiben.
das dict wird dann mit der Methode get_episodes einfach zurückgegeben.
im prinzip kann das bei dir ähnlich funktionieren ;)

das ganze Logging benutze ich für Fehlerausgaben, denn mit Print wirst du immer probleme bekommen früher oder später (spätestens wenn du diese ausgaben irgendwann entfernen willst/musst)

die Methoden mit dem "handle" am anfang werden immer aufgerufen, wenn beim parsen ein html-tag gefunden wurde... je nachdem ob es ein Start-tag oder ein end-tag ist.
Die funktion

Code: Alles auswählen

    def handle_endtag(self, tag):
        if tag == "td":
            self.__currenttag_td = False
        if tag == "tr":
            self.__epfound = False
            self.__currentepisode = None
            self.__beginngathering = False
        self.__logger.debug("End tag  :"+ tag)
z.b. wird immer bei einem end-tag aufgerufen(tags die mit nem "/" beginnen <html> </html>). jedoch macht sie in die meisten fällen nichts. erst wenn das ende einer Tabellen-spalte oder Zeile gefunden wird werden ein paar Variablen rejustiert.
du siehst auch das wenn das Loglevel auf Debug geschaltet wurde ich sogar bei JEDEM tag eine Printausgabe bekomme. das erleichterte es mir nachzuvollziehen wo, an welcher stelle probleme auftraten.

Probiere es aus, wenn du einmal begriffen hast, wie diese klasse funktioniert kannst du vieles mit html seiten anstellen.

Die klasse wird im übrigen so aufgerufen:

Code: Alles auswählen

	parser = htmlpars.HtmlParserHelper()
        parser.feed(urltext)
        staffel = parser.get_episodes()
        
erst wird die klasse erstellt, dann füttere ich die klasse mit .feed(urltext) mit dem html-text. beim Feeden wird auch gleichzeitig geparst und dort werden dann auch die handle-methoden in der klasse selsbt auseführt. wenn er dann zuende geparst hat sind die Daten extrahiert und ich leite sie mit get-episodes() einfach weiter.

Ich hatte im übrigen erst probiert mit Regular expressions zu arbeiten und html seiten mit RE zu parsen.. tu es nicht, das wird schnell schmerzhaft.
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@Pok1990: Logger werden üblicherweise auf Modulebene definiert, weil man ja maximal auf Modulebene auch den Log-Level einstellen will und nicht für jedes Exemplar der Klasse einzeln. Auch das Formatieren der Ausgabe sollte nicht innerhalb der Klasse sondern im Hauptprogramm passieren, weil man ja eine einheitliche Ausgabe für das gesamte Programm haben will. Die Attribute mit zwei Unterstrichen sollten maximal einen haben. episodes gehört sogar zur öffentlichen Schnittstelle und darf gar keinen haben. Die Methode get_episodes ist damit unnötig. Statt die Attribute per for-Schleife durchzugehen, böte es sich an, sie in ein Wörterbuch zu wandeln und dann direkt auf die gewünschten Werte zuzugreifen:

Code: Alles auswählen

    def handle_starttag(self, tag, attrs):
        logger.debug("Start tag: {}".format(tag))
        for name, value in attrs:
            logger.debug("  {}: {}".format(name, value))
        attrs = dict(attrs)
        if tag == "td":
            self._currenttag_td = True
            if attrs.get('class') == 'nowrap':
                logger.debug("begingathering is true")
                self._beginngathering = True
        if tag == "a" and self._epfound:
            logger.debug("a and epfound passing ")
            if self._beginngathering:
                title = attr.get('title')
                href = attr.get('href')
                if title == "Streamcloud":
                    logger.info("Streamcloudlink found: http://bs.to/{}".format(href))
                self.episodes[self._currentepisode][title] = href
Statt auf »is False« wird auf »not« geprüft. Zum Testen, ob ein String eine Zahl ist, braucht man keinen regulären Ausdruck. Es reicht »data.isdigit()«.
Wenn Dich nur das Zeichen interessiert nimm gleich html.entities.entitydefs statt name2codepoint.
BlackJack

Noch 'ne Anmerkung zum loggen: Nicht die Werte in den Loggingtext formatieren sondern Platzhalter für den ``%``-Operator verwenden und die Werte separat übergeben und das formatieren dem Logger überlassen. Der macht das dann nämlich nur wenn es auch tatsächlich gebraucht wird. Gerade DEBUG-Level-Ausgaben sind ja nicht normal, sondern üblicherweise nur zur Fehlersuche eingeschaltet.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Wenn man mit dem Log am Ende auch was anfangen will, kann es übrigens sinnvoll sein strukturierte Nachrichten zu verwenden. Diese lassen sich einfacher durchsuchen und mit hilfreichem Kontext versehen.
Pok1990
User
Beiträge: 6
Registriert: Freitag 24. Juni 2016, 14:34

Logger werden üblicherweise auf Modulebene definiert, weil man ja maximal auf Modulebene auch den Log-Level einstellen will und nicht für jedes Exemplar der Klasse einzeln. Auch das Formatieren der Ausgabe sollte nicht innerhalb der Klasse sondern im Hauptprogramm passieren, weil man ja eine einheitliche Ausgabe für das gesamte Programm haben will. Die Attribute mit zwei Unterstrichen sollten maximal einen haben. episodes gehört sogar zur öffentlichen Schnittstelle und darf gar keinen haben. Die Methode get_episodes ist damit unnötig. Statt die Attribute per for-Schleife durchzugehen, böte es sich an, sie in ein Wörterbuch zu wandeln und dann direkt auf die gewünschten Werte zuzugreifen:
erstmal danke, für die Verbesserungsvorschläge! :D
Muss gestehen, das ich das richtige verwenden von Loggern nicht so ganz durchschaut habe, sonst hätte ich das bestimmt anders gelöst.
Das, was du mit Modulebene meinst kann ich in meinem Wortschatz noch nicht ganz einordnen...

ich find es auch ein wenig doof, das ich für jede Klasse denselben mist im konstruktor habe... weils funktionoiert hat habe ich mich damit aber nich weiter beschäftigt.

Dadurch das ich unterschiedliche klassen zum loggen habe haben sie einen anderen Namen und ich kann recht schnell herrausfinden in welchem Modul der Fehler lag (bestimmt geht das auch bei deiner Beschriebenen Methode)

--
Was das Unterstrichene angeht.. Ja kann man machen!
ich habe bewusst einige dinge "private" gesetzt und das Dict am ende auch via methode zurückgegeben... vlt eine angewohnheit aus Java.
gibt es denn einen guten Grund die Daten so zu behandeln wie du es vorschlägst? (vlt lesbarkeit... ?? )

Das mich die Attr einfach so in ein Wörterbuch wandeln kann wusste ich so nicht, danke!

Die Attribute gehe ich bewusst so durch, da ich nicht alles brauche.
trotzdem danke für die Mühe! da sind ein paar dinge dabei die ich warscheinlich so übernehmen werde!

Beste Grüße
Pok

PS: sorry fürs OFF topic :oops: letztendlich gehts ja um ein anderes thema!
BlackJack

@Pok1990: Modulebene: Sachen die im Modul direkt stehen und nicht in Funktionen oder Klassen/Methoden.

Du hast *nichts* ”private” gesetzt. Das gibt es in Python nicht. Auch an die Attribute mit doppelten Unterstrichen kommt man von aussen heran. Das verändert nur den Namen des Attributs, und wie der verändert wird, steht in der Dokumentation. Auch *warum* der verändert wird: damit es bei Mehrfachvererbung und tiefen Vererbungshierarchien nicht zu Namenskollisionen kommt. Da man beides in Python aber selten bis gar nicht macht, sind die doppelten führenden Unterstriche in der Regel falsch. Das ist kein Zugriffsschutz wie ”private” in Java oder anderen Sprachen die so etwas haben. Wenn man etwas als Implementierungsdetail kennzeichnen möchte, also Attribute die man von aussen nicht verwenden soll, beziehungsweise wenn doch, dann ausdrücklich auf eigene Gefahr, nimmt man per Konvention *einen* führenden Unterstrich. Diese Konvention wird an einigen Stellen auch praktisch berücksichtigt. Zum Beispiel bei einem Sternchen-Import, bei den gängigen Dokumentationswerkzeugen, oder in interaktiven Shells und IDEs bei der Autovervollständigung.

Gibt es einen guten Grund des Zugriff auf praktisch öffentliche Attribute durch trivuale Getter- oder Setter-Methoden umständlicher zu machen? Python ist nicht Java. Dort ist der Grund das man eine Schnittstelle haben möchte bei der man das triviale aus einem Getter/Setter nehmen kann, ohne das man Code anpassen muss, der darauf zugreift. Dafür gibt es in Python aber Properties, also keinen Grund auf Vorrat einen Haufen Boilerplate-Code für Attributzugriffe zu schreiben und die künstlich zu Methoden zu machen.

Die Begründung mit der ``for``-Schleife habe ich nicht verstanden. Gerade wenn Du nicht alles brauchst, wäre es doch sinnvoller nicht über alles mit einer Schleife zu iterieren, sondern Dir gezielt nur die Werte heraus zu holen die Du brauchst‽
Pok1990
User
Beiträge: 6
Registriert: Freitag 24. Juni 2016, 14:34

Danke für deine Ausführungen,
ich meine mal gelesen zu haben, das 2 Unterstriche konventionstechnisch für Private- variablen gesetzt werden sollen... gut vlt ist das auch schon veraltet!

Deine Worte ergeben Sinn und ich verstehe den SInn dahinter. Ich werde das berücksichtigen.

--
das Mit der Modulebene möchte ich dann doch etwas Ausführlicher wissen...

Wenn ich einen Logger im Modul A anlege auf Modulebene und dort das Log level etc festlege, dann wirkt sich das doch nicht auf das Modul B aus.
und wie setze ich denn im Hauptprogramm ein einheitliches Format, wenn die Logger auf modulebene dieses nicht übernehmen können?

Beste Grüße,
Pok
BlackJack

@Pok1990: Logger sind ja hierarchisch aufgebaut. Das grundlegende Format kann man also schon im Hauptmodul festlegen. Und bei den anderen Loggern dann nur noch die Sachen die abweichen.
Pok1990
User
Beiträge: 6
Registriert: Freitag 24. Juni 2016, 14:34

Muss ich mal ausprobieren... habe mich relativ stark an
https://docs.python.org/3/howto/logging.html
gehalten... dort wurden dann eigene Streamhandler und co definiert..

ich habe das dann so übernommen und konnte so gut kontrollieren was wer wo wann ausgibt. Funktionieren tut es jedenfalls.
Aber ich komme auch nicht umhin, einen relativ großen Overhead für einfaches Logging festzustellen.
BlackJack

@Pok1990: Hm, das fängt aber viel harmloser an als das was Du da machst. Und da steht am Anfang von „Advanced Logging Tutorial“ zum Beispiel auch „A good convention to use when naming loggers is to use a module-level logger, in each module which uses logging, named as follows: …“. Und als nächstes wird dann `basicConfig()` nochmal erwähnt. Und wenn man es komlexer will oder braucht, dann wäre es immer noch besser diese ganzen Sachen aus dem Code heraus zu halten und eine der Konfigurationsmethoden mit Dateien oder Wörterbüchern im Hauptprogramm zu verwenden.
Pok1990
User
Beiträge: 6
Registriert: Freitag 24. Juni 2016, 14:34

ja vlt... ich wollte halt pro Modul einen Logger haben.
also wird beim erstellen einer Klasse halt immer ein Logger mit dem Namen der Klasse erstellt (der wird dann auch beim loggen ausgegeben, sodass nachvollziehbar wird in welchem Modul der Fehler ist.

Dann habe ich, weil ich noch im Lernen war mit den Handlern rumgespielt... weil ich in Zukunft vlt auch Filehandler oder andere nutzen möchte... da kam dann noch ein Rattenschwanz an Configurationsarbeit hinzu, weil dann mehrfach ausgaben aufgetaucht sind ... im Prozess des Schaffens wurde dann das daraus.

Es gab auch Momente wo ich wirklich unterschiedliche Loglevel für meine Module haben wollte, weil einige Module hal "fertig" waren und andere noch nicht, im Nachinein hätte ich dann die Tests anders machen können *hust*
Und da steht am Anfang von „Advanced Logging Tutorial“ zum Beispiel auch „A good convention to use when naming loggers is to use a module-level logger, in each module which uses logging, named as follows: …“
Genau das tue ich doch... :?

Code: Alles auswählen

 self.__logger = logging.getLogger(__name__) 
(in zukunft dann halt ohne die 2 Leerzeichen.)
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@Pok1990: gegen einen Logger pro Modul sagt ja auch niemand etwas, aber Du erzeugst einen Logger pro Instanz und zwar auch nur immer mit dem Namen des Moduls. Du solltest Dich an die Dokumentation halten, weil das im Normalfall eine gute Lösung ist. Nur wenn Du wirklich weißt, was Du tust, kannst Du abweichen.
BlackJack

@Pok1990: Da Du Klasse und Modul irgendwie durcheinander bringst: Du hast hoffentlich nicht pro Klasse eine Datei/ein Modul‽

Die Mehrfachausgaben kamen, weil Du das eben an der falschen Stelle machst. Du verhinderst das die Ausgaben zum Root-Logger durchgereicht werden (propagate = False), und machst bei dem untergeordneten Logger die ganze Konfiguration. Die würde man beim Root-Logger machen und die ganzen anderen modulspezifischen Logger dann ihre Ausgaben an den weiterleiten lassen. Da braucht man nichts weiter für machen, das ist das Standardverhalten. Wobei man das Loglevel dann ja immer noch für einzelne Logger-Exemplare einstellen kann. Entweder in dem Modul oder über eine Konfigurationsdatei.

„Module-level logger“ heisst Logger auf Modulebene. Nicht in einer Klasse. Falls man für Klassen dann noch speziellere haben möchte, könnte man die ja auch noch einrichten und den Namen aus Modul- und Klassenname zusammensetzen, also eine Ebene unter das Modul setzen, in der Logger-Hierarchie.
Antworten