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

Tabelle aus HTML mit lxml.html und regex einlesen

Beitragvon alexander » Sonntag 28. September 2008, 19:21

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

Beitragvon lunar » Sonntag 28. September 2008, 21:08

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

Beitragvon alexander » Sonntag 28. September 2008, 21:41

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

Beitragvon lunar » Sonntag 28. September 2008, 21:50

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.
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Beitragvon Leonidas » Sonntag 28. September 2008, 23:01

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 Modvoice
lunar

Beitragvon lunar » Sonntag 28. September 2008, 23:07

Netter Spruch ;)
alexander
User
Beiträge: 8
Registriert: Donnerstag 21. August 2008, 07:31

Beitragvon alexander » Montag 29. September 2008, 13:09

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: 5389
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Beitragvon snafu » Montag 29. September 2008, 14:41

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

Beitragvon alexander » Montag 29. September 2008, 15:01

@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

Beitragvon lunar » Montag 29. September 2008, 15:07

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

Beitragvon alexander » Montag 29. September 2008, 16:26

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

Beitragvon lunar » Montag 29. September 2008, 17:37

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 ')]

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder