Seite 1 von 1

Tabelle aus HTML mit lxml.html und regex einlesen

Verfasst: Sonntag 28. September 2008, 19:21
von alexander
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?

Verfasst: Sonntag 28. September 2008, 21:08
von 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.

Verfasst: Sonntag 28. September 2008, 21:41
von alexander
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?

Verfasst: Sonntag 28. September 2008, 21:50
von 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.

Verfasst: Sonntag 28. September 2008, 23:01
von Leonidas
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.

Verfasst: Sonntag 28. September 2008, 23:07
von lunar
Netter Spruch ;)

Verfasst: Montag 29. September 2008, 13:09
von alexander
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?

Verfasst: Montag 29. September 2008, 14:41
von snafu

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

Verfasst: Montag 29. September 2008, 15:01
von alexander
@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.

Verfasst: Montag 29. September 2008, 15:07
von 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 ')]

Verfasst: Montag 29. September 2008, 16:26
von alexander
@lunar: Das war der Ausdruck, den ich gesucht habe, vielen Dank. :)

Verfasst: Montag 29. September 2008, 17:37
von 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 ')]