UlfZibis
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3035
Wohnort: Köln
|
Hallo, ich habe hier eine HTML-Datei mit zahlreichen Abschnitten nach folgendem Muster: <article .... >
...
...
</article>
Daraus möchte ich dann weiteres herausfiltern und scheitere schon mit folgendem Ansatz (hier entnommen) (erst mal nur 3 Treffer listen, um Terminalausgabe kurz zu halten): grep -Pzom 3 "(?s)<article.*?article>" DATEI Als Ergebnis bekomme ich dann statt 3 Artikeln alles vom ersten <article .... > bis zum letzten </article> ausgegeben. Die non-greedy-Option wird also ignoriert. Alternativ müsste auch grep -Pm 3 "(?ms)<article.*?article>" DATEI funktionieren, doch damit bekomme ich gar keine Ausgabe. Was kann ich da machen? P.S.: Wenn ich (?s)<article.*?article> in Geany als RegEx-Suchmuster verwende, funktioniert das wunderbar. Moderiert von rklm: Thema von "RegEx-Problem (zeilenübergreifend)" auf Wunsch des OP und zur Klärung umbenannt
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Hier liegt offenbar nicht HTML vor, sondern XML. Denn in HTML gibt es keine article Tags. Wenn du dies verarbeiten willst, solltest du einen XML Parser verwenden. Von der Bash aus eignet sich xmlstarlet ganz gut dafür. Das hat so einen Parser eingebaut. Schau dir einfach mal die Beispiele aus dem Link an.
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3035
Wohnort: Köln
|
snafu1 schrieb: Hier liegt offenbar nicht HTML vor, sondern XML. Denn in HTML gibt es keine article Tags.
Wie auch immer, es handelt sich um den "Quelltext" von: https://www.gelbeseiten.de/Suche/Arzt/K%C3%B6ln
Mein Ziel ist die Extraktion der Datensätze als CSV-Datei.
Wenn du dies verarbeiten willst, solltest du einen XML Parser verwenden. Von der Bash aus eignet sich xmlstarlet ganz gut dafür. Das hat so einen Parser eingebaut. Schau dir einfach mal die Beispiele aus dem Link an.
Das ist vielleicht auch eine gute Idee. Dennoch würde ich mich interessieren, warum die non-greedy-Option ignoriert wird und die Multiline-Option (?m) ebenfalls.
|
Neral
Anmeldungsdatum: 3. Oktober 2007
Beiträge: 229
|
|
NORACSA
Anmeldungsdatum: 31. Januar 2010
Beiträge: 180
|
snafu1 schrieb: Hier liegt offenbar nicht HTML vor, sondern XML. Denn in HTML gibt es keine article Tags.
Gefährliches Altwissen! HTML5 hat article-Tags! Neral schrieb: Man kann HTML nicht mit regulären Ausdrücken parsen.
Man sollte es zumindest nicht. Es kann funktionieren, muss aber nicht.
|
Neral
Anmeldungsdatum: 3. Oktober 2007
Beiträge: 229
|
NORACSA schrieb: Man sollte es zumindest nicht. Es kann funktionieren, muss aber nicht.
Nein, es kann nicht funktionieren. Mathematisch unmöglich. Reguläre Ausdrücke können nur reguläre Sprachen parsen, HTML ist aber nicht regulär. Deswegen kann es keinen regulären Ausdruck geben, der (z. B.) alle article -Tags aus einem allgemeinen HTML-Dokument extrahieren kann.
|
NORACSA
Anmeldungsdatum: 31. Januar 2010
Beiträge: 180
|
Neral schrieb: NORACSA schrieb: Man sollte es zumindest nicht. Es kann funktionieren, muss aber nicht.
Nein, es kann nicht funktionieren. Mathematisch unmöglich. Reguläre Ausdrücke können nur reguläre Sprachen parsen, HTML ist aber nicht regulär. Deswegen kann es keinen regulären Ausdruck geben, der (z. B.) alle article -Tags aus einem allgemeinen HTML-Dokument extrahieren kann.
Mit unregelmäßigem HTML wird es nicht funktionieren. Mit regelmäßigem schon. D.h. wenn man zB eine sehr gleichmäßig aufgebaute Seite hat, dann kann man diese schon mit RegEx parsen. Sollte man allerdings nicht.
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Insofern bin ich auch nicht weiter darauf eingegangen. Ich unterstütze das Parsen von XML und HTML via Regex nicht, weil man früher oder später damit auf die Nase fällt. Ein gutes Tool dafür hatte ich bereits vorgestellt. Wenn dann noch eine weitere Verarbeitung gewünscht ist, würde ich schon fast auf Python mit bs4 für die Verarbeitung verweisen. In jedem Fall dürfte hier ein entsprechender Aufruf via XPath oder BeautifulSoup API die reine Abfrage als Einzeiler möglich machen. Und dabei deutlich robuster im Vergleich zum regulären Ausdruck sein. Aber wer nicht will, der hat schon... 🤷🏼♂️
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
snafu1 schrieb: Insofern bin ich auch nicht weiter darauf eingegangen. Ich unterstütze das Parsen von XML und HTML via Regex nicht, weil man früher oder später damit auf die Nase fällt.
Fun fact: der Ruby-XML-Parser REXML nutzt reguläre Ausdrücke. Das ist allerdings natürlich nicht das gleiche, wie selbst XML mit Regex zu parsen.
Ein gutes Tool dafür hatte ich bereits vorgestellt. Wenn dann noch eine weitere Verarbeitung gewünscht ist, würde ich schon fast auf Python mit bs4 für die Verarbeitung verweisen.
Oder Ruby mit Nokogiri.
In jedem Fall dürfte hier ein entsprechender Aufruf via XPath oder BeautifulSoup API die reine Abfrage als Einzeiler möglich machen. Und dabei deutlich robuster im Vergleich zum regulären Ausdruck sein.
👍
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3035
Wohnort: Köln
|
Neral schrieb: NORACSA schrieb: Man sollte es zumindest nicht. Es kann funktionieren, muss aber nicht.
Nein, es kann nicht funktionieren. Mathematisch unmöglich. Reguläre Ausdrücke können nur reguläre Sprachen parsen, HTML ist aber nicht regulär. Deswegen kann es keinen regulären Ausdruck geben, der (z. B.) alle article -Tags aus einem allgemeinen HTML-Dokument extrahieren kann.
Hallo Neral, ich habe so eine Ahnung, wie Du das meinst. Hast Du den auch eine Lösung für mein Problem? Mit xmlstarlet habe ich so einiges probiert, doch ist die Seite so komplex, dass ich kein Land sehe. Mit RegEx bin ich jedenfalls schon ziemlich weit, solange ich es innerhalb Geany benutze, stolpere aber immer noch darüber, dass die non-greedy-Syntax von grep scheinbar ignoriert wird. Ist das wirklich so, wenn ja, wo kann ich das nachlesen, oder wo ist mein Fehler?
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
Ich mache so etwas gerne mit Ruby 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 | #!/usr/bin/ruby
require 'csv'
require 'mechanize'
require 'nokogiri'
OPTS = {col_sep: ','}
agent = Mechanize.new
ARGV.each do |url|
puts "Reading #{url}..."
doc = agent.get url
# doc = File.read("doc") # testing only
dom = Nokogiri::HTML doc
dom.xpath('//div[@class="mod-TrefferListe"]/article').each do |a|
puts CSV.generate_line([
a.at_xpath('.//h2[@data-wipe-name="Titel"]'),
a.at_xpath('.//p[@class="mod-AdresseKompakt__phoneNumber"]'),
], OPTS)
end
puts
end
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Mit python3-requests und python3-bs4 könnte das z.B. so aussehen, wenn man will, dass er bis zur letzten Seite der Trefferliste läuft:
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 | #!/usr/bin/env python3
import csv
import requests
import sys
from typing import Optional
from bs4 import BeautifulSoup
def load_url(url: str) -> Optional[BeautifulSoup]:
print(f"GET {url}", file=sys.stderr)
r = requests.get(url)
try:
r.raise_for_status()
except requests.exceptions.HTTPError:
print(f"request for {url} failed", file=sys.stderr)
else:
return BeautifulSoup(r.content, "html.parser")
def process_page(url: str) -> None:
if (soup := load_url(url)):
for article in soup.select("div.mod-TrefferListe > article.mod-Treffer"):
csv_writer.writerow(
(article.find("h2", attrs={"data-wipe-name": "Titel"}).text,
article.find("p", class_="mod-AdresseKompakt__phoneNumber").text))
if (next_page := soup.find("a", class_="gs_paginierung__sprungmarke--vor", attrs={'title': 'Weiter'}).get('href')):
process_page(next_page)
if __name__ == '__main__':
csv_writer = csv.writer(sys.stdout)
for url in sys.argv[1:]:
process_page(url)
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3035
Wohnort: Köln
|
seahawk1986 schrieb: Mit python3-requests und python3-bs4 könnte das z.B. so aussehen, wenn man will, dass er bis zur letzten Seite der Trefferliste läuft:
| #!/usr/bin/env python3
import csv
import requests
import sys
[.....]
|
Hey das sieht richtig gut aus. Wenn ich das richtig sehe, basiert das darauf, dass das Programm die Seite direkt per URL aus dem Netz zieht. Das Problem ist, dass die Seite im ersten Anlauf "nur" 50 Treffer liefert. dann muss man den "Mehr Anzeigen"-Knopf sooft betätigen, bis man alle 1528 Ergebnisse hat. Also habe ich mir den Inhalt dann erst mal per "Seiten-Quelltext" anzeigen lassen und dann in eine Datei abgespeichert. Dabei zeigt sich dann, dass dabei auch nur 50 Ergebnisse in der Datei sind. Also habe ich dann einen Trick verwandt. Ich habe mit Strg+A die ganze Seite markiert und dann mittels "Auswahl-Quelltext anzeigen" die Rohdaten abgespeichert. Dabei kommen aber die Umrahmenden Tags wie z.B. <head> nicht vollständig rüber. Damit kam dann XMLstarlet auch überhaupt nicht klar. Ähnlich wird es auch hier sein. Hast Du evtl. eine Idee, wie ich da elegant drum herum komme? Also entweder das Python-Programm überreden, dass es den "Mehr Anzeigen"-Knopf sooft triggert wie nötig, oder dass es mit der dann etwas verhunzten Datei klarkommt, die ich manuell erzwungen habe.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
UlfZibis schrieb: Also entweder das Python-Programm überreden, dass es den "Mehr Anzeigen"-Knopf sooft triggert wie nötig, oder dass es mit der dann etwas verhunzten Datei klarkommt, die ich manuell erzwungen habe.
Das macht das Python-Skript schon - das steckt in der if-Bedingung aus Zeile 24 f. - wenn es ein <a> Tag mit der Klasse gs_paginierung__sprungmarke--vor und dem Attribut title="Weiter" gibt, holt es sich den Link für die Seite und stößt die Verarbeitung an. Du musst ihm nur die URL für die erste Seite bzw. den Suchbegriff als Argument übergeben.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Hier noch ein Update ohne rekursive Funktionsaufrufe und mit besserer Fehlerbehandlung, wenn man auf der letzten Seite ankommt:
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 | #!/usr/bin/env python3
import csv
import requests
import sys
from typing import Optional
from bs4 import BeautifulSoup
def load_url(url: str) -> Optional[BeautifulSoup]:
print(f"GET {url}", file=sys.stderr)
r = requests.get(url)
try:
r.raise_for_status()
except requests.exceptions.HTTPError:
print(f"request for {url} failed", file=sys.stderr)
else:
return BeautifulSoup(r.content, "html.parser")
def process_page(url: str) -> Optional[str]:
if (soup := load_url(url)):
for article in soup.select("div.mod-TrefferListe > article.mod-Treffer"):
try:
csv_writer.writerow(
(article.find("h2", attrs={"data-wipe-name": "Titel"}).text,
article.find("p", class_="mod-AdresseKompakt__phoneNumber").text))
except AttributeError:
pass
next_page_element = soup.find("a", class_="gs_paginierung__sprungmarke--vor", attrs={'title': 'Weiter'})
if next_page_element:
return next_page_element.get('href')
if __name__ == '__main__':
csv_writer = csv.writer(sys.stdout)
for url in sys.argv[1:]:
while url:
url = process_page(url)
|
|