michahe
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 857
|
Hallo, ich habe eine längere Liste von URLs (nicht lokal gespeichert), die HTML-Dateien enthalten diese Tags
<td class="text" style="border-right:0;">
</td>
Dazwischen befindet sich eine Jahreszahl, z.B. 2022, die möchte ich extrahieren und speichern. So habe ich angefangen:
| Start='<td class="text" style="border-right:0;">'
Stop='</td>'
|
Ich habe sed wie hier beschrieben versucht, das liefert aber nichts für meine Variablen. Miene Fragen:
Wie extrahiere und speichere ich die Jahreszahl richtig? Wie durchlaufe ich die URL-Liste entsprechend?
Danke, Michael
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13204
|
Du willst ein Tool, das mit Tags arbeitet z.B. xmlstarlet oder Ruby mit Nokogiri.
|
noisefloor
Anmeldungsdatum: 6. Juni 2006
Beiträge: 29567
|
Hallo, Ergänzung: HTML ist kein zeilenbasiertes Format. Sprich da können an beliebigen Stellen Zeilenumbrüche drin sein oder alles kann in einer Zeile stehen und es ist immer noch valides HTML. Von daher ist das Durchsuchen von HTML mit regulären Ausdrücken nicht der richtige Weg. Kann funktionieren, muss aber nicht. Ein HTML/XML Parser ist das Mittel der Wahl. Gruß, noisefloor
|
shiro
Supporter
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1270
|
noisefloor schrieb: Ergänzung: HTML ist kein zeilenbasiertes Format. Sprich da können an beliebigen Stellen Zeilenumbrüche drin sein oder alles kann in einer Zeile stehen und es ist immer noch valides HTML. Von daher ist das Durchsuchen von HTML mit regulären Ausdrücken nicht der richtige Weg. Kann funktionieren, muss aber nicht. Ein HTML/XML Parser ist das Mittel der Wahl.
Na ja, man kann mit "sed" auch NICHT zeilenbasiert (-z) arbeiten. Ich würde zwar mit "xmllint --html" arbeiten aber hier ist ja nach "sed" gefragt. Das könnte man daher etwa wie folgt realisieren:
$ sed -z 's/\n//g;s/<td\ class=[^>]*>/\n/gi;s/^.*<table>//i;s/<[^>]*>//g;s/$/\n/;s/^\n//' <<<'<html><body>
Ein Text in folgender Tabelle<br><table><tr><td
class="text" style="border-right:0;">2022</td>
</tr><tr><td class="text" style="border-right:0;"
>2023-2034</td></tr><tr><td class="text" style=
"border-right:0;">2050 und Text</td></tr></table>
</body></html>'
2022
2023-2034
2050 und Text
$
Falls zu den "sed" regex noch Fragen sind, kann ich das auch gern erläutern.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13204
|
shiro schrieb:
aber hier ist ja nach "sed" gefragt.
Tatsächlich nicht. sed ist lediglich, was michahe benutzt hat.
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17620
Wohnort: Berlin
|
| sed -n "/^$Start$/,/^$End$/{2p}" file.html
|
Damit $Start und $End ausgewertet werden müssen sie in doppelten Anführungszeichen stehen, sonst wird versucht die Dollarzeichen als solche zu matchen. Im SO-Beispiel wurde nach einem literalen "START=A" gesucht, nicht nach einer Variablen. Das "=A passt bei Dir überhaupt nicht und i.d. Vorlage wurde nach START/END, nicht Start/End gesucht. Außerdem bestand das Interesse die umschließenden Zeilen mit auszugeben, was Du offenbar nicht willst. Dies funktioniert:
| echo -e '<td class="text" style="border-right:0;">
2019\n</td>' > file.html
Start='<td class="text" style="border-right:0;">'
Stop='</td>'
sed -n "/^$Start$/,/^$End$/{2p}" file.html
|
Das -n unterdrückt die Ausgabe für alle Ausdrücke, die kein 'p' wie print haben.
Das /^$Start$/ expandiert zu Zeilen, die nur aus Deiner Startvariablen bestehen, das Caret steht für Zeilenanfang, das zweite Dollarzeichen für Zeilenende.
Ein /foo/,/bar/ matcht den Bereich von foo bis bar.
Für den Bereich wird dann aber nur Zeile 2 ausgegeben, wo hoffentlich der Content ist.
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17620
Wohnort: Berlin
|
noisefloor schrieb: Hallo, Ergänzung: HTML ist kein zeilenbasiertes Format. Sprich da können an beliebigen Stellen Zeilenumbrüche drin sein oder alles kann in einer Zeile stehen und es ist immer noch valides HTML.
Der Fragesteller hat sich ja offenbar angeschaut, wie das HTML formatiert ist. Von selbst ändert sich das ja auch nicht. Ob es valides HTML ist, ist überhaupt fraglich.
Von daher ist das Durchsuchen von HTML mit regulären Ausdrücken nicht der richtige Weg. Kann funktionieren, muss aber nicht. Ein HTML/XML Parser ist das Mittel der Wahl.
Das ist natürlich genauso wenig robust dagegen, dass der Contentprovider was ändert. Das die Contentprovider nur am Whitespace was ändern ist theoretisch denkbar, aber dürfte in der Praxis selten Relevanz entfalten.
|
CarstenHa
Anmeldungsdatum: 1. Mai 2020
Beiträge: 142
|
Wenn dein <td>-Element ein class-Attribut oder noch besser das Attribut id besitzt (das darf pro HTML-Seite nur einmal mit dem gleichen Namen vorkommen) und die Seite immer gleich aufgebaut ist (siehe oben beschrieben) hilft dir vielleicht sowas in der Art? seite.html:
| <html>
<table>
<td class="datum" style="border-right:0;">
2022
</td>
<td id="datum">
2023
</td>
</table>
</html>
|
| sed -n '/<td.*class="datum"/,/<\/td>/p' seite.html | grep -o '[0-9]\{4\}'
|
Zur Erklärung: mit dem sed-Befehl '/.../,/.../' filterst du erst den entsprechenden Bereich raus. Anschließend extrahierst du mit grep die Jahreszahl. Gruß Carsten
|
michahe
(Themenstarter)
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 857
|
Mein erster Dank an @shiro dafür dass er meine Frage wieder in den richtigen Kontext geschoben hat! Dank an @user_unknown, sein Beispiel funktioniert perfekt:
| Start='<td class="text" style="border-right:0;">'
Stop='</td>'
sed -n "/^$Start$/,/^$End$/{2p}" file.html
|
Nun habe ich die Original-Datei getestet, die Ausgabe ist LEER. Dann habe ich das Original auf gekürzt (siehe beigefügte Datei 999.html), auch hier ist die Ausgabe LEER. Erst mit komplett gelöschten HEAD und BODY (etc.) - Zeilen (9999.html) kommt die korrekte Ausgabe. Wie muss ich den Code ergänzen?
Und gleich noch die Bitte um einen Denkanstoß, wie ich die URL-Liste abarbeite und die Ergebnisse (Jahreszahlen) speichere.
- 999.html (167 Bytes)
- Download 999.html
- 999min.html (53 Bytes)
- Download 999min.html
|
shiro
Supporter
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1270
|
Da sind mehrere Probleme bei dir:
Du definierst die Variable "Stop" und verwendest die Variable "End". Auf einen Namen solltest du dich festlegen. Du solltest bei "Stop" den "/" escapen. Also "Stop='<\/td>'". Die Annahme, dass der zwischen "Start" und "Stop" gefundene Block hinsichtlich Zeilennummer neu nummeriert wird ist falsch. Beispiel:
| $ Start='<td class="text" style="border-right:0;">'
$ Stop='<\/td>'
$ sed -n "/^$Start$/,/^$Stop$/ {=;p}" 999.html
5
<td class="text" style="border-right:0;">
6
2002
7
</td>
$
|
Wie man sieht, ist die 6. Zeile (und nicht die 2.) die gewünschte Jahreszahl. Wenn du mehrere <td> Blöcke hast, sind das natürlich auch andere Zeilennummern. Die von mir oben vorgeschlagene "sed" Zeile funktioniert allerdings:
| $ sed -z 's/\n//g;s/<td\ class=[^>]*>/\n/gi;s/^.*<table>//i;s/<[^>]*>//g;s/$/\n/;s/^\n//' 999.html
2002
$
|
Bearbeitet von rklm: Syntaxhighlighting
|
michahe
(Themenstarter)
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 857
|
shiro schrieb: Stimmt, aber das ist der Code von @user_unknown. Ersetze ich End durch Stop kommt ein Fehler ...
Die von mir oben vorgeschlagene "sed" Zeile funktioniert allerdings ...
Ja, liefert aber zuviele Zeilen aus dem Original-HTML. Der Start-String <td class="text" style="border-right:0;"> im HTML-Dokument ist eindeutig. Ideal wäre also:
Alles davor, einschließlich Start-String ignorieren; dann prüfen, ob in einer neuen Zeile eine 4-stellige Zahl und darauf in einer neuen Zeile der Stop-String <\/td> folgt. Falls ja, die Zahl in einer Variable speichern ...
|
shiro
Supporter
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1270
|
Ok, wenn du NUR die '<td class="text" style="border-right:0;">' Einträge haben willst, solltest du den "s" Befehl verschärfen und dann die restlichen "<td..>" Elemente raus löschen. z.B. wie
| $ sed -zE 's/\n//g;s/<td\ class="text"\ style="border-right:0;"[^>]*>/\n/gi;s#<td[^/]+/td>##g;s/^.*<table>//i;s/<[^>]*>//g;s/$/\n/;s/^\n//' 999.html
2002
$
|
Nach dem Entfernen der Zeilenenden (\n) wird somit der gesuchte "td" zu einem "\n" ersetzt, das vor dem Zelleninhalt stehen soll. Danach werden alle "<td../td>" Einträge gelöscht, da du sie nicht haben wolltest. Danach wir alles vom Anfang bis zum "<table>" gelöscht, alle HTML Tags entfernt, am Ende der Datei ein "\n" angehängt und das erste "\n" bei der Ausgabe entfernt. Wenn du wirklich nur deine Zahl als Zelleninhalt gelistet haben willst, musst du noch über ein "[0-9]+" regex filtern. Also beispielsweise so:
| $ sed -zE 's/\n//g;s/<td\ class="text"\ style="border-right:0;"[^>]*>/\n/gi;s#<td[^/]+/td>##g;s/^.*<table>//i;s/<[^>]*>//g;s/[^0-9\n]//g;s/$/\n/;s/^\n//' 999.html
2002
|
Dabei werden alle Zeichen, die nicht Ziffern oder "\n" sind gelöscht. Bearbeitet von rklm: Syntaxhighlighting
|
michahe
(Themenstarter)
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 857
|
Danke shiro, > $ sed -zE 's/\n//g;s/<td\ class="text"\ style="border-right:0;"[^>]*>/\n/gi;s#<td[^/]+/td>##g;s/^.*<table>//i;s/<[^>]*>//g;s/$/\n/;s/^\n//' 999.html
Funktioniert leider nicht mit einer HTML-Datei näher am Original, siehe 999plus.html. Außerdem wäre es im Sinne der Verständlichkeit schön, die Start- / Stop-Strings als Variable einzuführen.
- 999plus.html (575 Bytes)
- Download 999plus.html
|
shiro
Supporter
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1270
|
Na ja, wenn du die "Start/Stop" Konstruktion bevorzugst, geht auch folgender Ansatz:
| $ file=999plus.html
$ Start='<td class="text" style="b[^>]*>'
$ Stop='<\/td>'
$ sed -z 's/\n//g;s/</\n&/g;s/> *\([0-9]\)/>\n\1/g' $file | sed -n "/^$Start$/,/^$Stop$/p" | sed "/^</d"
1996
$
|
Zuerst werden wieder alle Zeilenende (\n) eliminiert, dann wird vor jedes "<" Zeichen (Tag) ein "\n" gesetzt. Da der Zellenwert dann hinter dem "<td ...>" Tag steht, wird hinter das ">" bei folgender Ziffer ein "\n" eingefügt. Die derart normalisierte Ausgabe kann dann mit dem "Start/Stop" Ansatz bearbeitet werden. Je Block hat man dann 3 Zeilen (<td>\nZahl\n</td>). Man könnte jetzt über ein "awk" jede 3, Zeile beginnend mit der 2. ausgeben. Ich habe mit "sed" allerdings einfach jede Zeile, die mit "<" beginnt gelöscht. Bei "Start" habe ich nun auch einen regex eingetragen, da offenbar der "style=" String nicht immer gleich war aber mit einem "border" Attribut begann. Wirklich hilfreich wäre es allerdings, wenn du mal eine echte html Datei zur Verfügung stellst, damit man sehen kann, was dort noch auftritt und was du uns nicht mitgeteilt hast aber von Relevanz ist. Bearbeitet von rklm: Syntaxhighlighting
|
michahe
(Themenstarter)
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 857
|
Danke, shiro, Na ja, wenn du die "Start/Stop" Konstruktion bevorzugst, geht auch folgender Ansatz:
Ich finde es viel verständlicher so. Aber jetzt werden mit dem Original zwei Zeilen mit der Jahreszahl 1996) geliefert.
Für den Start-String hatten wir folgende Varianten: * Gemäß meiner Analyse:
Start='<td class="text" style="border-right:0;">'
* Dein String:
Start='<td class="text" style="b[^>]*>'
Vielleicht macht die Abkürzung (b[^>]*) das Problem denn gefunden wird die zweite Zeile aus folgendem Ausschnitt ...
<td class="text" style="border-bottom:0;border-right:0;">
1996
</td>
Mein Versuch
Start='<td class="text" style="border-right[^>]*>'
liefert das richtige Ergebnis, nur eine Jahreszahl. Gibt es so aus Deiner Sicht ein Problem?
|