Dee
Anmeldungsdatum: 9. Februar 2006
Beiträge: 20087
Wohnort: Schwabenländle
|
Hallo, ich habe eine kleine Programmieraufgabe und würde gerne wissen, wie Ihr es lösen würdet. Die verwendete Sprache ist dabei egal, es geht ums Konzept. In einer HTML-Datei stehen Datensätze mit den Inhalten "Hersteller", "Name", "Preis", "Alter" und "Datum der letzten Aktualisierung". Die Ordnung ist dabei, dass zuerst immer der Hersteller kommt und danach die einzelnen Produkte für diesen Hersteller. Preis und Alter sind optional. Jeder Eintrag sollte mit dem Datum abschließen. Beispiel: <a href="herstellerlink">Hersteller 1</a>
<strong>Produkt 1</strong>
34,00 Euro ab 10 Jahren
<nobr>[24.01.2014]</nobr>
<strong>Produkt 2</strong>
<nobr>[24.02.2014]</nobr>
<a href="herstellerlink">Hersteller 2</a>
<strong>Produkt 1</strong>
ab 10 Jahren
<nobr>[24.03.2014]</nobr> Jetzt ist es aber auch so, dass die Daten nicht einzeln in einer Zeile stehen müssen, sondern beliebig innerhalb einer Zeile. Es ist also auch so etwas möglich: <a href="herstellerlink">Hersteller 1</a><strong>Produkt 1</strong>
34,00 Euro ab 10 Jahren
<nobr>[24.01.2014]</nobr><strong>Produkt 2</strong><nobr>[24.02.2014]</nobr><a href="herstellerlink">Hersteller 2</a><strong>Produkt 1</strong>ab 10 Jahren<nobr>[24.03.2014]</nobr> Daher meine Frage: Wie würdet Ihr diese Eingabe parsen? Gruß Dee
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11181
Wohnort: München
|
Ich habe deine Beispiele mal als preisliste.txt zusammengeworfen und ein Produkt 3 und 4 aus dem Formatierungs-Beispiel gemacht:
<a href="herstellerlink">Hersteller 1</a>
<strong>Produkt 1</strong>
34,00 Euro ab 10 Jahren
<nobr>[24.01.2014]</nobr>
<strong>Produkt 2</strong>
<nobr>[24.02.2014]</nobr>
<a href="herstellerlink">Hersteller 2</a>
<strong>Produkt 1</strong>
ab 10 Jahren
<nobr>[24.03.2014]</nobr>
<a href="herstellerlink">Hersteller 1</a><strong>Produkt 3</strong>
120,00 Euro ab 99 Jahren
<nobr>[24.01.2014]</nobr><strong>Produkt 4</strong><nobr>[14.02.2012]</nobr><a href="herstellerlink">Hersteller 2</
a><strong>Produkt 2</strong>ab 10 Jahren<nobr>[24.03.2014]</nobr> Mein Parser nutzt Python3 und BeautifulSoup4:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 | #!/usr/bin/env python3
import re
from bs4 import BeautifulSoup
with open('preisliste.txt', 'r') as f:
p = f.read()
price_re = re.compile('\d+,\d+(?=\s*Euro)')
age_re = re.compile('(?<=ab\s)\d+(?=\s*Jahren)')
soup = BeautifulSoup(p)
vendors = soup.find_all('a')
products = soup.find_all('strong')
dates = soup.find_all('nobr')
for vendor, product, date in zip(vendors, products, dates):
vendor_name = vendor.text
product_name = product.text
date = date.text[1:-1]
extra_line = product.next_sibling
m = price_re.search(extra_line)
if m:
price = m.group(0)
else:
price = "NA"
n = age_re.search(extra_line)
if n:
age = n.group(0)
else: age = "NA"
print(20 * "*", "PRODUKT", 20 * "*")
print("Hersteller: {0}".format(vendor_name))
print("Produkt: {0}".format(product_name))
print("Preis: {0}".format(price))
print("Alter: {0}".format(age))
print("Datum der letzten Aktualisierung: {0}".format(date))
|
Ergibt:
$ python3 parse_pricelist.py
******************** PRODUKT ********************
Hersteller: Hersteller 1
Produkt: Produkt 1
Preis: 34,00
Alter: 10
Datum der letzten Aktualisierung: 24.01.2014
******************** PRODUKT ********************
Hersteller: Hersteller 2
Produkt: Produkt 2
Preis: NA
Alter: NA
Datum der letzten Aktualisierung: 24.02.2014
******************** PRODUKT ********************
Hersteller: Hersteller 1
Produkt: Produkt 1
Preis: NA
Alter: 10
Datum der letzten Aktualisierung: 24.03.2014
******************** PRODUKT ********************
Hersteller: Hersteller 2
Produkt: Produkt 3
Preis: 120,00
Alter: 99
Datum der letzten Aktualisierung: 24.01.2014 Der Ansatz ist, dass man Hersteller am Tag <a>, das Produkt am Tag <strong> und das Datum am Tag <nobr> erkennen kann. Der Extra-Text folgt immer auf das Produkt - daher das extra_line = product.next_sibling. Je nachdem wie die echte Seite aussieht, könnte es auch leichter sein direkt mit einem Regex nach dieser Tag-Abfolge zu suchen oder den Abschnitt vorab aus der Seite zu extrahieren, so dass da keine störenden HTML-Elemente mehr drin sind, die die genannten Tags nutzen.
|
track
Anmeldungsdatum: 26. Juni 2008
Beiträge: 7174
Wohnort: Wolfen (S-A)
|
Dee schrieb: In einer HTML-Datei stehen Datensätze mit den Inhalten "Hersteller", "Name", "Preis", "Alter" und "Datum der letzten Aktualisierung". ... meine Frage: Wie würdet Ihr diese Eingabe parsen?
Grundsätzlich basiert das Parsen von HTML hier ja auf Dreckeffekten, die zufällig die betreffenden Datenfelder eindeutig beschreiben. Das ist soweit erstmal grundsätzlich klar. Beim Holzhammer-Parsen habe ich die Gewohnheit, zuerst alle Tags jeweils in eine neue Zeile umzubrechen: sed 's/</\n</g' datensatz.html Dann hat man jeden Tag mit seinem Inhalt in einer neuen Zeile. (→ das klappt aber schon nicht mehr, wenn innerhalb eines Datensatzes z.B. ein <br> vorkäme. - das gäbe sofort eine neue Zeile !) Die ganzen leeren und End-Tag-Zeilen dazwischen stören nicht wirklich, wenn man die Zuordnung durch Zeilenauswahl (grep o.ä.) vornimmt. Konkret könnte so eine Minimal-Auswertung so aussehen: track@lucid:~$ sed 's/</\n</g' datensatz.html | sed -n '/^[^<]/ s/^/Preis:\t/p; s/^<a .*>/Hersteller:\t/p; s/<strong>/Name:\t/p; s/<nobr>/Datum:\t/p'
Hersteller: Hersteller 1
Name: Produkt 1
Preis: 34,00 Euro ab 10 Jahren
Datum: [24.01.2014]
Name: Produkt 2
Datum: [24.02.2014]
Hersteller: Hersteller 2
Name: Produkt 1
Preis: ab 10 Jahren
Datum: [24.03.2014] Wie gesagt: diese Methode ist "gepflegter Pfusch", aber sie funktioniert in einfachen Fällen meistens. LG, track
|
Dee
(Themenstarter)
Anmeldungsdatum: 9. Februar 2006
Beiträge: 20087
Wohnort: Schwabenländle
|
Danke für die Antworten. @seahawk1986: Ich wollte eigentlich keine fertige Lösung, sondern nur das Konzept. Ich müsste jetzt BeautifulSoup verstehen, um das in anderen Sprache nachzubauen. @track: Deine Idee finde ich super und sehr simpel und sollte man in jeder Sprache anwenden können. Das Problem mit dem Beispiel 2 oben ist ja, dass innerhalb einer Zeile der Hersteller wechselt und zwei Produkte zu sehen sind. Eine simple Regexp findet dann eben nur ein Vorkommen und ordnet ggf. auch noch den Hersteller dem falschen Produkt zu. Das vorherige Aufteilen einer Zeile in die relevanten Datenfelder sollte das verhindern. Weitere Lösungsideen sind natürlich willkommen. Die konkrete Seite, um die es geht, ist im Übrigen http://partner.spielen.de/spielbox/messeneuheiten/verlag Gruß Dee
|
track
Anmeldungsdatum: 26. Juni 2008
Beiträge: 7174
Wohnort: Wolfen (S-A)
|
Dee schrieb: @track: ... Das Problem mit dem Beispiel 2 oben ist ja, dass innerhalb einer Zeile der Hersteller wechselt und zwei Produkte zu sehen sind. Eine simple Regexp findet dann eben nur ein Vorkommen und ordnet ggf. auch noch den Hersteller dem falschen Produkt zu.
Nee, das ist kein Schicksal ! - Solche Fragen würde ich mit einem Bestandsspeicher lösen, der mit den entsprechenden Angaben gefüttert und ggf. auch wieder gelöscht wird. Als Beispiel mal so'n Ding, in awk : track@lucid:~$ sed 's/</\n</g' datensatz.html | awk '/^[^<]/ {d[3]=$0} /^<a/ {delete d; sub(".*>",""); d[1]=$0} /^<strong/ {sub(".*>",""); d[2]=$0} /^<nobr/ {sub(".*>",""); d[4]=$0; for(x in d) printf("%s\t",d[x]); print ""}'
[24.01.2014] Hersteller 1 Produkt 1 34,00 Euro ab 10 Jahren
[24.02.2014] Hersteller 1 Produkt 2 34,00 Euro ab 10 Jahren
[24.03.2014] Hersteller 2 Produkt 1 ab 10 Jahren Beachte das delete zum Löschen und die printf- Schleife zum Drucken, jeweils beim passenden Trigger. LG, track
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11181
Wohnort: München
|
Dee schrieb: Danke für die Antworten. @seahawk1986: Ich wollte eigentlich keine fertige Lösung, sondern nur das Konzept. Ich müsste jetzt BeautifulSoup verstehen, um das in anderen Sprache nachzubauen.
Ok, die Seite ist ja ganz anders als dein Beispiel aufgebaut, da passt mein kleines Python-Skript natürlich nicht dazu. HTML-Parser gibt es aber für viele Sprachen ☺ Das Parsen der Einträge ist mit auf der echten Seite aber wesentlich einfacher, jeder Hersteller hat seinen eigenen <h2> Header-Tag, die Spiele des Herstellers in einem <ul> Tag, jedes Spiel sitzt in seinem eigenen <li> Tag, der Timestamp hat eine eigene Klasse usw.
Das kann man eigentlich sehr schon parsen.
|
Dee
(Themenstarter)
Anmeldungsdatum: 9. Februar 2006
Beiträge: 20087
Wohnort: Schwabenländle
|
@track: genau so habe ich es jetzt auch gelöst. Nur in einer anderen Sprache (Tcl) und mit etwas mehr Funktionalität, da da noch etwas mehr dahinterhängt als das bloße Auslesen. @seahawk: Hm, an den <ul> und <li> zu parsen bringt ja auch nur etwas, wenn nicht alles in einer Zeile steht, oder? Daher ist die Idee von Track schon praktisch nach gewissen Elementen einfach ein Linebreak zu setzen und dann dieses Ergebnis zu parsen. Aber dennoch danke für den Hinweis, weil ich die </li> wirklich nicht gesehen hatte und nun einen guten Abschluss für jeden Eintrag habe. ☺ Gruß Dee
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11181
Wohnort: München
|
Dee schrieb: @seahawk: Hm, an den <ul> und <li> zu parsen bringt ja auch nur etwas, wenn nicht alles in einer Zeile steht, oder?
Nein, einem HTML-Parser sind Zeilenumbrüche im Dokument völlig egal, es zählen nur die Tags - und die werden ja auch wieder geschlossen - daher kann man sich dann mit einem HTML-Parser einfach am Dokumentenbaum (ähnlich wie in einer XML-Datei) entlang hangeln und muss immer nur einen zusammenhängenden Abschnitt auswerten:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | <html>
<head>...</head>
<body>
<div id="content">
[...]
<h2>
<a class="publisherlink" href="http://wizkidsgames.com/" rel="nofollow" target="_blank">
WizKids
</a>
</h2>
<ul>
<li>
<strong>
Dungeons & Dragons Dice Masters
</strong>
von
Eric M. Lang
, Mike Elliott
, 2 Spieler
<a class="info" href="http://www.boardgamegeek.com/boardgame/160902/" rel="nofollow" target="_blank">
BGG
</a>
<span class="timestamp">
<nobr>
[22.07.2014 07:08 Uhr]
</nobr>
</span>
</li>
[...]
</ul>
</div>
</body>
</html>
|
In dem Fall sucht man sich dann die <h2> Tags mit der Klasse "publisherlink" und findet so den Hersteller, dann such man das folgende <ul>...</ul> in der gleichen Ebene und geht dann die Elemente darin durch, die jeweils in einem <li>...</li> stecken. Den Titel findet man innerhalb des <strong>...</strong> Tags, dann als nächstes Element Text, dann ggf. noch Links mit weiterführenden Informationen und dann noch der Timestamp, der die Klasse "timestamp" hat. Wenn das fehlerfreies HTML ist, muss man auch nicht BeautifulSoup nutzen (das ist recht langsam) sondern könnte dann in Python z.B. auf lxml oder HTML-Parser für andere Sprachen zurückgreifen.
|
Dee
(Themenstarter)
Anmeldungsdatum: 9. Februar 2006
Beiträge: 20087
Wohnort: Schwabenländle
|
Okay, dann bräuchte ich einen HTML-Parser, der die HTML-Elemente in einer Zeile einzeln durchgeht. Den habe ich ja nicht, weswegen ich ja schnell selbst was schreiben will (bzw. schon getan habe). Gruß Dee
|
microft
Anmeldungsdatum: 6. August 2009
Beiträge: 454
Wohnort: Norddeutschland
|
Dee schrieb: Okay, dann bräuchte ich einen HTML-Parser, der die HTML-Elemente in einer Zeile einzeln durchgeht. Den habe ich ja nicht, weswegen ich ja schnell selbst was schreiben will (bzw. schon getan habe). Gruß Dee
Wenn der HTML Code sauber ist kannst du auch einen XML Parser nehmen. cu
|
Lysander
Anmeldungsdatum: 30. Juli 2008
Beiträge: 2669
Wohnort: Hamburg
|
Dee schrieb: Okay, dann bräuchte ich einen HTML-Parser, der die HTML-Elemente in einer Zeile einzeln durchgeht.
HTML ist ja eben kein zeilenorientiertes Format! Keine wie auch immer geartete Lösung wird da jemals robust sein. Wenn Du ein Fire-and-Forget Script brauchst, kannst Du natürlich so vorgehen - aber dann könntest Du ja auch einen echten Parser nutzen 😉
|
Developer92
Anmeldungsdatum: 31. Dezember 2008
Beiträge: 4101
|
microft schrieb: Wenn der HTML Code sauber ist kannst du auch einen XML Parser nehmen.
Valides HTML muss nicht gleichzeitig valides XML sein, das sind 2 verschiedene Dinge.
|