Tabelle aus HTML mit lxml.html und regex einlesen

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
alexander
User
Beiträge: 8
Registriert: Donnerstag 21. August 2008, 07:31

Hallo,
ich versuche gerade mit lxml.html Tabellenzellen einer HTML-Seite auszulesen.
Den Element Tree hole ich mit:

Code: Alles auswählen

import urllib
from lxml.html import fromstring

def get_site(url):
	content = urllib.urlopen(url).read()
	doctree = lxml.html.fromstring(content)
	doctree.make_links_absolute(url)
jetzt will ich für alle Tabellen mit den Klassennamen "detailTable" aus den Zeilen mit den Namen "odd" oder "even" die Zellen auslesen, deren Klassenname mit "timeDetail", "odvDetail" oder "motDetail" beginnt.

Code: Alles auswählen

...
	<table class="detailTable" cellspacing="0">
	    <tbody>
	        <tr class="odd">
	            <td class="timeDetail borderTopodd">19:17</td>
	            <td class="odvDetail borderTopodd">Hier steht ein Text</td>
	            ...
	            <td class="motDetail borderTopodd" rowspan="2">Hier steht auch ein Text</td>
	            ...
	        </tr>
    ...

Als regulären Ausdruck wollte ich z.B. "(odd|even)" und "timeDetai\\w*" verwenden, jedoch kommen die Funktionen .find_class() und .findall() mit regulären Ausfrücken als Argument nicht zurecht.

Code: Alles auswählen

for table in doctree.find_class('odd'):
	retime = re.compile('.//td[@class="odvDetai\\w*"]')
	for item in table.findall('.//td[@class="timeDetail"]'): #tr[@class="odd"]/
		print type(item)
		time = item.text
		print time

	for item in table.findall('.//td[@class="odvDetail"]'):
		location = item.text
		print location

	for item in table.findall('.//td[@class="modDetail"]'):
		description = item.text
		print description
Wie kann ich reguläre Ausdrücke für die Suche nach Elementen verwenden?
lunar

Was Wunder ... hättest du die Dokumentation gelesen, wüsstest du, dass "findall" eine Etree-spezifische Abfragesprache nutzt, die von XPath abgeleitet wurde, dass lxml.html Elemente eine .xpath() und eine .cssselect() Methode haben, um mittels XPath-Ausdrücken bzw. CSS-Selektoren zu suchen, und dass man reguläre Ausdrücke nicht für die Suche nach Elementen in lxml.html-Etrees nutzen kann.
alexander
User
Beiträge: 8
Registriert: Donnerstag 21. August 2008, 07:31

Ich hatte mir die Doku zu lxml durchgelesen, Reguläre Ausdrücke bzw. deren Nichtanwendbarkeit wurden leider nicht namentlich erwähnt. :(

Gibt es denn Alternativen zu lxml.html, die mit regulären Ausdrücken bei der Suche im Etree umgehen können?
lunar

alexander hat geschrieben:Ich hatte mir die Doku zu lxml durchgelesen, Reguläre Ausdrücke bzw. deren Nichtanwendbarkeit wurden leider nicht namentlich erwähnt. :(
Wieso nur kommst du dann auf die Idee, sie zu verwenden? Wenn eine Doku etwas nicht erwähnt, dann wohl deswegen, weil es nicht unterstützt wird.
Gibt es denn Alternativen zu lxml.html, die mit regulären Ausdrücken bei der Suche im Etree umgehen können?
Nein. Es gibt alternative Parser wie BeautifulSoup, deren Baumobjekte Abfragen mit regulären Ausdrücken unterstützen, das ist dann allerdings kein Elementtree mehr.

Warum nutzt du nicht einfach die Mittel, die lxml.html dir zu Verfügung stellt? CSS-Selektoren und XPath sind imho um Welten besser zum Scrapen geeignet als reguläre Ausdrücke.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

lunar hat geschrieben:CSS-Selektoren und XPath sind imho um Welten besser zum Scrapen geeignet als reguläre Ausdrücke.
Vor allem weil man damit, ganz im Gegensatz zu regulären Ausdrücken auch nichttriviale Queries mit Relationen der Elemente zueinander ausdrücken kann.

Wenn es ein Zitat gibt, das da gut passt, dann auf jeden Fall dieses:
Some people, when confronted with a problem, think, “I know, I’ll use regular expressions.”
Now they have two problems.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
alexander
User
Beiträge: 8
Registriert: Donnerstag 21. August 2008, 07:31

Danke für den Wink mit dem Zaunpfahl :wink:

jetzt nutze ich xpath() bekomme aber nicht ganz das Ergebnis, das ich erwartet habe:

Code: Alles auswählen

is_time = re.compile("(\d\d:\d\d)")
for table in doctree.find_class('detailTable'):
			print '-'*60
	for item in table.xpath('.//td[contains(name(), timeDetail)]'):
		try:
			element = item.text.strip()
		except:
			pass
		if is_time.match(element):
			time = element
			print time
		else:
			if element != '':
			print element
			element = ''
liefert nicht nur den Text für die Zelle

Code: Alles auswählen

<td class="timeDetail borderTopodd">19:17</td> 
sondern auch den Text der folgenden Zellen

Code: Alles auswählen

<td class="odvDetail borderTopodd">Hier steht ein Text</td>
                ...
<td class="motDetail borderTopodd" rowspan="2">Hier steht auch ein Text</td> 
Ausgabe:

Code: Alles auswählen

------------------------------------------------------------
19:17
Hier steht ein Text
Hier steht auch ein Text
Woran liegt das? Die Zellen sind doch auf der gleichen Ebene im Etree, oder nicht?
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Code: Alles auswählen

In [1]: s = '''<table class="detailTable" cellspacing="0">
   ...:         <tbody>
   ...:             <tr class="odd">
   ...:                 <td class="timeDetail borderTopodd">19:17</td>
   ...:                 <td class="odvDetail borderTopodd">Hier steht ein Text</td>
   ...:                 ...
   ...:                 <td class="motDetail borderTopodd" rowspan="2">Hier steht auch ein Text</td>
   ...:                 ...
   ...:             </tr>
   ...:         </tbody>
   ...:         </table>'''

In [2]: from lxml.html import fromstring

In [3]: doc = fromstring(s)

In [4]: print doc.xpath('.//td[@class="timeDetail borderTopodd"]')[0].text
19:17
alexander
User
Beiträge: 8
Registriert: Donnerstag 21. August 2008, 07:31

@snafu: Das hatte ich als erstes probiert, das Problem ist allerdings, dass der Klassenname nicht immer "timeDetail borderTopodd" lautet, sondern auch mal nur "timeDetail" oder "timeDetail borderToppeven" usw.
Deswegen suche ich einen xpath()-Ausdruck, der mir alle entsprechenden Texte der Zellen, deren Klassenname mit "timeDetail" beginnt zurückliefert.
lunar

Bei HTML-Klassen ist man imho besser mit einem CSS-Selektor bedient:

Code: Alles auswählen

tree = lxml.html.fromstring(source)
print tree.cssselect('td.timeDetail')[0].text_content()
lxml.html kompiliert CSS-Selektoren in xpath. Der dem obigen Ausdruck äquivalente XPath sieht wie folgt aus:

Code: Alles auswählen

descendant-or-self::td[contains(concat(' ', normalize-space(@class), ' '), ' timeDetail ')]
alexander
User
Beiträge: 8
Registriert: Donnerstag 21. August 2008, 07:31

@lunar: Das war der Ausdruck, den ich gesucht habe, vielen Dank. :)
lunar

Naja, da würde ich aber doch lieber den CSS-Ausdruck nehmen, denn "Readability counts" ;)

Der Dank ist zwar nett, aber nicht nötig. Ich habe mir den Ausdruck nicht selbst ausgedacht: ;)

Code: Alles auswählen

import lxml.cssselect
print lxml.cssselect.CSSSelector('td.timeDetail').path
# descendant-or-self::td[contains(concat(' ', normalize-space(@class), ' '), ' timeDetail ')]
Antworten