Parsing HTML Tabelle mit BeatifulSoup

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
mit
User
Beiträge: 285
Registriert: Dienstag 16. September 2008, 10:00

Hi,
ich habe folgendes HTML document

Code: Alles auswählen

<html><head>
<meta charset="utf-8">

</head>
<body>
<a name="Test1"> 
<center>
<b>Test 1</b> <table border="0">
	<tbody><tr>
		<th> Type </th>
		<th> Region </th>
	</tr>
	<tr>
		<td> <table border="0">
	<thead>
		<tr> 
			<th><b>Type</b></th>
			<th> &nbsp; </th>
			<th> Count </th>
			<th> Percent </th>
		</tr>
	</thead>
		<tbody><tr> 
			<td> <b>T1</b> </td> 
			<th> &nbsp; </th>
			<td class="numeric" bgcolor="#ff0000"> 34,314 </td> 
			<td class="numeric" bgcolor="#ff0000"> 31.648% </td>
		</tr>
		<tr> 
			<td> <b>T2</b> </td> 
			<th> &nbsp; </th>
			<td class="numeric" bgcolor="#bf3f00"> 25,820 </td> 
			<td class="numeric" bgcolor="#bf3f00"> 23.814% </td>
		</tr>
		<tr> 
			<td> <b>T3</b> </td> 
			<th> &nbsp; </th>
			<td class="numeric" bgcolor="#24da00"> 4,871 </td> 
			<td class="numeric" bgcolor="#24da00"> 4.493% </td>
		</tr>
	
</tbody></table><br>
 </td>
		<td> <table border="0">
	<thead>
		<tr> 
			<th><b> Type</b></th>
			<th> &nbsp; </th>
			<th> Count </th>
			<th> Percent </th>
		</tr>
	</thead>
		<tbody><tr> 
			<td> <b>T4</b> </td> 
			<th> &nbsp; </th>
			<td class="numeric" bgcolor="#ff0000"> 34,314 </td> 
			<td class="numeric" bgcolor="#ff0000"> 31.648% </td>
		</tr>
		<tr> 
			<td> <b>T5</b> </td> 
			<th> &nbsp; </th>
			<td class="numeric" bgcolor="#53ab00"> 11,187 </td> 
			<td class="numeric" bgcolor="#53ab00"> 10.318% </td>
		</tr>
		<tr> 
			<td> <b>T6</b> </td> 
			<th> &nbsp; </th>
			<td class="numeric" bgcolor="#bf3f00"> 25,820 </td> 
			<td class="numeric" bgcolor="#bf3f00"> 23.814% </td>
		</tr>
	
</tbody></table><br>
 </td>
	</tr>
</tbody></table>
</center>

	</a>
</body></html>
und aus dieser Tabell wuerde ich gerne die Werte "Count" und "Precentage" fuer T1 bis T6 extrahiren. Leider bin ich nicht in der lage auf die Werte zuzugreifen mit diesem code:

Code: Alles auswählen

from bs4 import BeautifulSoup

with open("/home/mit/tmp/test3.html") as f:
    soup = BeautifulSoup(f, "lxml")
    
    print soup.find_all('a', {'name':'Test1'})
    
    for tr in soup.find_all('a', attrs={'name':'Test1'}).find_all('tr')[2:]:
        tds = tr.find_all('td')
        print tds
Ich bekomme diesen Error:

Code: Alles auswählen

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    for tr in soup.find_all('a', attrs={'name':'Test1'}).find_all('tr')[2:]:
AttributeError: 'ResultSet' object has no attribute 'find_all'
Wie ist es moeglich auf die Werte aus der Tabelle zuzugreifen?

Vielen Dank im Vorraus.
Zuletzt geändert von mit am Freitag 29. November 2013, 09:34, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

ein Resultset hat keine Methode 'find_all', die ja da auch keinen Sinn hat, weil ein ResultSet schon das Ergebnis eines find_all ist. Wie es richtig geht, hast Du ja auch schon in Deinem Code drin (for-Schleife).
mit
User
Beiträge: 285
Registriert: Dienstag 16. September 2008, 10:00

Danke, es funktioniert!

Code: Alles auswählen

from bs4 import BeautifulSoup

with open("/home/mit/tmp/test3.html") as f:
    soup = BeautifulSoup(f, "lxml")
    
    for a in soup.find_all('a', attrs={'name':'Test1'}):
        for tr in a.find_all('tr')[2:]:
            tds = tr.find_all('td')
            print tds
BlackJack

@mit: Da anscheinend nur *ein* Treffer für 'a' mit name=Test1 erwartet wird, könnte man auch einfach das `find_all()` sein lassen und nur `find()` verwenden und sich damit die äussere Schleife über eine Liste sparen von der man weiss das sie sowieso nur ein Element hat. Und die Werte unterscheiden sich von allen anderen Tabellenzellen durch die 'numeric'-Klasse — das könnte man auch nutzen:

Code: Alles auswählen

from bs4 import BeautifulSoup


def main():
    with open('test.html') as html_file:
        soup = BeautifulSoup(html_file)
        for td in soup.find('a', {'name': 'Test1'})('td', 'numeric'):
            print td.string


if __name__ == '__main__':
    main()
mit
User
Beiträge: 285
Registriert: Dienstag 16. September 2008, 10:00

Danke, für die Lösung. Ich wollte die Tabellen Inhalt in ein dict speichern "'T1': ('25,820', '23.814%')" und es hat mit dem folgenden Code funktioniert.

Code: Alles auswählen

from bs4 import BeautifulSoup

test = {}

with open("test3.html") as f:
    soup = BeautifulSoup(f, "lxml")

    for a in soup.find_all('a', attrs={'name': 'Test1'}):
        for tr in a.find_all('tr')[2:]:
            tds = tr.find_all('td')
            if len(tds) > 0:
                test[str(tds[0].text).strip()] = (str(tds[1].text).strip(),
                    str(tds[2].text).strip())

print test
print test['T1']
Gibt es eine bessere Lösung um die in eine Datenstruktur zu packen?
Zuletzt geändert von Anonymous am Sonntag 1. Dezember 2013, 12:00, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

Alternativ:

Code: Alles auswählen

from itertools import izip
from bs4 import BeautifulSoup


def main():
    with open('test.html') as html_file:
        soup = BeautifulSoup(html_file)
        result = dict()
        for table in soup.table('table'):
            td_iter = iter(table('td'))
            items = izip(td_iter, td_iter, td_iter)
            for type_td, count_td, percent_td in items:
                result[type_td.text.strip()] = (
                    count_td.string.strip(), percent_td.string.strip()
                )
    print result



if __name__ == '__main__':
    main()
Solltest Du tatsächlich Bytestrings aus den Unicode-Objekten machen wollen, dann würde ich nicht einfach `str()` verwenden, sondern die Zeichenketten tatsächlich explizit kodieren.
mit
User
Beiträge: 285
Registriert: Dienstag 16. September 2008, 10:00

Warum funtioniert type_td.string.strip() nicht, aber count_td.string.strip() funktioniert?
BlackJack

@mit: `string` ist die Zeichenkette die *direkt* das Kind vom Element ist. Bei `type_td` ist der Text aber in einem weiteren Kindelement (<b>) verschachtelt. Das `text`-Attribut sammelt alle Texte rekursiv aus den Kindelementen zusammen und verbindet die zu einer Zeichenkette. Wenn man `string` verwenden wollen würde, hätte man explizit über das <b>-Element gehen müssen:

Code: Alles auswählen

In [23]: type_td
Out[23]: <td> <b>T1</b> </td>

In [24]: type_td.string

In [25]: type_td.text
Out[25]: u' T1 '

In [26]: type_td.b.string
Out[26]: u'T1'
mit
User
Beiträge: 285
Registriert: Dienstag 16. September 2008, 10:00

Danke, jetzt verstehe ich es.

Warum funktioniert der folgende Code nicht?

Code: Alles auswählen

from bs4 import BeautifulSoup

with open("test4.html") as f:
    soup = BeautifulSoup(f, "lxml")

    for rows in soup.find('a', attrs={'name': 'Test1'}).find("tbody").find_all("tr"):
        print rows
        for row in rows:
            print row.find_all("td")
BlackJack

@mit: Schreib mal statt ``print rows`` ``print list(rows)``, dann siehst Du über was Du da alles iterierst.
mit
User
Beiträge: 285
Registriert: Dienstag 16. September 2008, 10:00

Ich bekomme diese Fehlermeldung

Code: Alles auswählen

[u'\n', <th> Type </th>, u'\n', <th> Region </th>, u'\n']
Traceback (most recent call last):
  File "test.py", line 9, in <module>
    print row.find_all("td")
  File "/home/mit/apps/pymodules/lib/python2.7/site-packages/beautifulsoup4-4.3.2-py2.7.egg/bs4/element.py", line 675, in __getattr__
    self.__class__.__name__, attr))
AttributeError: 'NavigableString' object has no attribute 'find_all'

mit diesem Code

Code: Alles auswählen

from bs4 import BeautifulSoup

with open("test3.html") as f:
    soup = BeautifulSoup(f, "lxml")

    for rows in soup.find('a', attrs={'name': 'Test1'}).find("tbody").find_all("tr"):
        print list(rows)
        for row in rows:
            print row.find_all("td")
HTML Datei ist die selbe wie im ersten post. Ich vermute es liegt an th Elementen die keine td beinhalten, aber bin nicht sicher wie man es lösen könnte.
BlackJack

@mit: Nein, es liegt genau an dem was die Fehlermeldung sagt und was man in der ausgegebenen Liste ziemlich deutlich sehen sollte: `NavigableString`-Objekte haben keine `find_all()`-Methode. Das erste, dritte, und das letzte Element in der Liste sind *Zeichenketten* (Unterklasse von `unicode`) und keine Elemente die Kinder haben können nach denen man suchen kann.
Antworten