使用BeautifulSoup提取特定的TD表格元素文本?

时间:2014-03-30 15:56:35

标签: python html beautifulsoup

我尝试使用BeautifulSoup库从自动生成的HTML表中提取IP地址,并且我遇到了一些麻烦。

HTML的结构如下:

<html>
<body>
    <table class="mainTable">
    <thead>
        <tr>
            <th>IP</th>
            <th>Country</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><a href="hello.html">127.0.0.1<a></td>
            <td><img src="uk.gif" /><a href="uk.com">uk</a></td>
        </tr>
        <tr>
            <td><a href="hello.html">192.168.0.1<a></td>
            <td><img src="uk.gif" /><a href="us.com">us</a></td>
        </tr>
        <tr>
            <td><a href="hello.html">255.255.255.0<a></td>
            <td><img src="uk.gif" /><a href="br.com">br</a></td>
        </tr>
    </tbody>
</table>

下面的小代码从两个td行中提取文本,但我只需要IP数据,而不是IP和国家/地区数据:

from bs4 import BeautifulSoup
soup = BeautifulSoup(open("data.htm"))

table = soup.find('table', {'class': 'mainTable'})
for row in table.findAll("a"):
print(row.text)

此输出:

127.0.0.1
uk
192.168.0.1
us
255.255.255.0
br

我需要的是IP table.tbody.tr.td.a元素文本,而不是国家table.tbody.tr.td.img.a元素。

是否有任何有经验的BeautifulSoup用户会对如何进行此选择和提取有所了解。

感谢。

4 个答案:

答案 0 :(得分:3)

这为您提供了正确的列表:

>>> pred = lambda tag: tag.parent.find('img') is None
>>> list(filter(pred, soup.find('tbody').find_all('a')))
[<a href="hello.html">127.0.0.1<a></a></a>, <a></a>, <a href="hello.html">192.168.0.1<a></a></a>, <a></a>, <a href="hello.html">255.255.255.0<a></a></a>, <a></a>]

只需对此列表的元素应用.text

上面列表中有多个空的<a></a>标记,因为html中的<a>标记未正确关闭。要摆脱它们,你可以使用

pred = lambda tag: tag.parent.find('img') is None and tag.text

最终:

>>> [tag.text for tag in filter(pred, soup.find('tbody').find_all('a'))]
['127.0.0.1', '192.168.0.1', '255.255.255.0']

答案 1 :(得分:2)

您可以使用一个小正则表达式来提取IP地址。带有正则表达式的BeautifulSoup是一个很好的抓取组合。

ip_pat = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
for row in table.findAll("a"):
    if ip_pat.match(row.text):
        print(row.text)    

答案 2 :(得分:1)

仅在<td>中的每一行tbody搜索:

# html should contain page content:
[row.find('td').getText() for row in bs4.BeautifulSoup(html).find('tbody').find_all('tr')]

或者更具可读性:

rows = [row in bs4.BeautifulSoup(html).find('tbody').find_all('tr')]
iplist = [row.find('td').getText() for row in rows]

答案 3 :(得分:1)

首先请注意HTML格式不正确。它没有关闭a标签。这里有两个 <a>标记:

<a href="hello.html">127.0.0.1<a>

如果您打印table,您会看到BeautifulSoup正在将HTML解析为:

<td>
<a href="hello.html">127.0.0.1</a><a></a>
</td>
...

每个a后跟一个空的a


如果您需要这些额外的<a>代码,如果您想要每个第三个 <a>代码,那么

for row in table.findAll("a")[::3]:
    print(row.get_text())

足以:

127.0.0.1
192.168.0.1
255.255.255.0

另一方面,如果<a>标记的出现不那么规律,您只希望<a>标记没有先前的兄弟(例如但不限于<img> ),然后

for row in table.findAll("a"):
    sibling = row.findPreviousSibling()
    if sibling is None:
        print(row.get_text())

会起作用。


如果您有lxml,则可以使用XPath更简洁地表达标准:

import lxml.html as LH
doc = LH.parse("data.htm")
ips = doc.xpath('//table[@class="mainTable"]//td/a[not(preceding-sibling::img)]/text()')
print(ips)

上面使用的XPath具有以下含义:

//table                            select all <table> tags
    [@class="mainTable"]           that have a class="mainTable" attribute
//                                 from these tags select descendants
  td/a                             which are td tags with a child <a> tag
    [not(preceding-sibling::img)]  such that it does not have a preceding sibling <img> tag
    /text()                        return the text of the <a> tag 

learn XPath确实需要一点时间,但是一旦你学会了它,你可能再也不想使用BeautifulSoup了。