Python: Apache Log einlesen und auswerten

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
Klabautermann
User
Beiträge: 4
Registriert: Montag 10. April 2017, 08:46

Hey,
ich brauche einmal eure Hilfe, da ich ein Python Anfänger bin.
Ich muss für ein Projekt eine apache.log Datei in Python einlesen und die Dateizugriffe pro IP ausgeben.
(Immer die IP und darunter die Datei angegeben)
Leider komme ich hier trotz Internetrecherche nicht weiter.

Schonmal danke im voraus. :D

Der gesamte Log ist wie folgt aufgebaut.:


[codebox=text file=Unbenannt.txt]12.148.295.198 - - [27/May/2013:08:35:38 +0200] "GET /robots.txt HTTP/1.1" 404 1107
172.16.16.12 - - [26/May/2013:14:06:05 +0200] "GET /navleiste/b_xyz_navleiste.gif HTTP/1.1" 200 136
...[/code]

Und die Ausgabe sollte am besten wie folgt aussehen.:

[codebox=text file=Unbenannt.txt]12.148.2965.198:
/projekte/dqm_Hausbau/Mittwoch.html
/robots.txt
129.143.4.65:
/projekte/comenius/bhvtour.htm
...[/code]
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

welchen Ansatz hast du denn bzw. was hast du schon probiert? Bitte eine Idee oder Code posten - egal, wie falsch oder fehlerhaft der ist. Aber dann hat man wenigsten einen Ansatzpunkt.

Und wie viel Python kannst du? Hast du das Python-Tutorial schon durchgearbeitet? Grundsätzlich lässt sich das ganze mit Bordmitteln von Python lösen.

Gruß, noisefloor
BlackJack

@Klabautermann: Wo liegt denn das konkrete Problem? Welches Tutorial hast Du durchgearbeitet, oder mit welchem Material lernst Du Python?

Grundsätzliches vorgehen beim Programmieren: Das Problem in kleinere Teilprobleme zerlegen, und diese Teilprobleme wieder zerlegen, solange bis man bei Teilproblemen angelangt ist, die man mit einer Funktion mit wenigen Zeilen Code lösen kann. Diese Funktion testen, und wenn sie tut was sie soll, dann macht man mit der nächsten Teillösung weiter. Irgendwann setzt man dann Teillösungen aus bereits vorhandenen Teillösungen/Funktionen zusammen, solange bis das Gesamtproblem gelöst ist.

Trennlinien zum Aufteilen sind im Groben oft Eingabe, Verarbeitung, und Ausgabe. Im kleineren kann man oft einen Arbeitsschritt der auf viele gleichartige Daten angewendet wird in einer Funktion isolieren, die dann in einer anderen für jeden ”Datensatz” aufgerufen wird. Also zum Beispiel lässt sich die Teilaufgabe „alle Zeilen aus dem Log parsen“ runterbrechen auf „eine Zeile parsen“ und diese Funktion kann man dann auf alle Zeilen anwenden. Ähnlich auf der anderen Seite: „Alle IPs mit den Dateien ausgeben“ kann man runterbrechen auf „eine IP mit den Dateien ausgeben“ und die dann für jede IP ausführen.

Neben dem Code muss man sich Gedanken über die Datenstrukturen machen. Was kommt rein in das Programm. Wie kann man dort die Daten extrahieren und in welche Struktur die sich für die Verarbeitung eignet und so weiter. Dazu sollte man mindestens mit den Grunddatentypen von Python vertraut sein. Dazu würde ich noch das `collections`-Modul nehmen. Und was die Verarbeitung angeht, ist das `itertools`-Modul fast immer nützlich, das sollte man auch kennen.

Konkret für diese Aufgabe würde ich das `re`-Modul anschauen.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Konkret für diese Aufgabe würde ich das `re`-Modul anschauen.
Schnell & einfach hätte ich das mit den eingebauten Methoden des String-Datentyps gemacht - was zumindest dann funktionieren sollte, wenn alle Zeilen des Logs die o.g. Struktur haben.

Gruß, noisefloor
Klabautermann
User
Beiträge: 4
Registriert: Montag 10. April 2017, 08:46

@noisefloor
@BlackJack

Das ist der Code bzw. meine Idee.:

Code: Alles auswählen

import re

def statuscnt(Datei):
    with open(Datei) as fin:
        for line in fin:
            print (line.split()[0] + ("\n") + line.split()[-4])  

Datei = 'C://Users//XXX//Desktop//apache.log'
statuscnt(Datei)
Jetzt sieht die Ausgabe aber wie folgt aus.:
[codebox=text file=Unbenannt.txt]12.148.2965.198
/projekte/dqm_Hausbau/Mittwoch.html
12.148.2965.198:
/robots.txt
129.143.4.65:
/projekte/comenius/bhvtour.htm
129.143.4.65:
/ananas/test123/bla.html
...[/code]

Mein Ziel ist es aber jede IP nur einmal anzuzeigen und darunter jeweiligen die Dateipfäde.
Ich finde aber keine wirkliche lösung für mein Problem. :(
Und ich lerne Python mit kleineren Projekten wie z.B. dieses hier....
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dazu musst du eine geeignete Datenstruktur waehlen. Die Abbildung eines Schluessels auf eine andere Struktur wird einem mit einem Woerterbuch (dict in Python) geboten. Zusammen mit dem praktischen defaultdict, mit dem man automatisch eine Liste fuer die Eintraege anlegt, sieht das so aus:

Code: Alles auswählen

from collections import defaultdict

result = defaultdict(list)
for ip, rest in parse_logfiles():
      result[ip].append(rest)
Zuletzt geändert von Anonymous am Dienstag 11. April 2017, 15:11, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Klabautermann
User
Beiträge: 4
Registriert: Montag 10. April 2017, 08:46

@ __deets__

Irgendwie verstehe ich nicht so richtig, wie ich das jetzt anwende....
BlackJack

@Klabautermann: __deets__ hat Dir gezeigt wie man ein `collections.defaultdict` für diese Aufgabe verwenden kann um eine geeignete Datenstruktur aufzubauen. Unter der Voraussetzung das man eine Funktion hat die Paare von Werten zurück gibt die aus der IP und dem Teil ab dem Pfad der URL besteht.

Wobei die Datenstruktur eventuell auch noch nicht genau das ist was Du haben möchtest, denn diese Struktur enthält dann die Teile ab dem Pfad in der Reihenfolge in der sie in der Protokolldatei auftauchen und wenn die gleiche URL mehrfach abgerufen wurde, dann wird sie auch mehrfach in der Liste erfasst. Eventuell möchtest Du eine Menge statt einer Liste verwenden. Dafür hat Python den Datentyp `set()`.

Und was vielleicht auch noch geklärt werden muss ist die Frage nach weiteren Teilen der URL, denn neben dem Pfad kann die ja noch einen „query“- und/oder einen „fragment“-Teil besitzen. Zählen verschiedene Daten dort als unterschiedliche Einträge, oder muss man die vorher entfernen?

Für die Ausgabe müsste/sollte man dann aus der ungeordneten Menge eine sortierte Liste erstellen, sonst wird es etwas unübersichtlich.

Wie schon gesagt: Teil das alles in Teilprobleme auf und löse die jeweils mit Funktionen und teste die. Erste mögliche Teillösung ist das extrahieren der Daten aus *einer* Logzeile. Falls der URL-Teil noch nachbearbeitet werden muss (query/fragment) dann könnte man das auch in einer eigenen Funktion machen. Die Funktionen testen. Also mit verschiedenen Protokollzeilen, beziehungsweise mit den verschiedenen Variationen wie eine URL ab dem Pfad-Teil aussehen kann.

Wenn das dann steht ist der Eingabeteil fertig und Du kannst an die Verarbeitung gehen: eine Datenstruktur aus diesen Daten aufbauen in der die Informationen so erfasst werden, das man die Ausgabe einfach damit programmieren kann. Also wie schon von __deets__ erwähnt eine Abbildung von IP auf die URLs. In welcher Struktur die URLs gespeichert werden, zum Beispiel Liste oder Menge, hängt dann wieder von den Anforderungen ab, die Du an die Ausgabe hast.
Klabautermann
User
Beiträge: 4
Registriert: Montag 10. April 2017, 08:46

Gut, ich versuche das dann mal so wie du gastagt hast also ich beginne erstmal mit einer Zeile, versuche den Eingabeteil fertig zu bekommen und sehe dann weiter.

Nochmal danke @BlackJack und @ __deets__ für die Tipps. :)
Antworten