【问题标题】:beautiful soup not to parse nested table data漂亮的汤不解析嵌套表数据
【发布时间】:2019-04-30 04:58:47
【问题描述】:

我有一个嵌套表结构。我正在使用下面的代码来解析数据。

for row in table.find_all("tr")[1:][:-1]:
    for td in row.find_all("td")[1:]:
        dataset = td.get_text()

这里的问题是当有嵌套表时,比如在我的情况下,<td></td> 内部有表,所以在最初解析后再次解析这些表,因为我使用的是 find_all(tr)find_all(td)。那么如何避免解析已经解析的嵌套表呢?

输入:

<table>
<tr>
   <td>1</td><td>2</td>
</tr>
<tr>
   <td>3</td><td>4</td>
</tr>
<tr>
  <td>5 
    <table><tr><td>11</td><td>22</td></tr></table>
      6
  </td>
</tr>
</table>

预期输出:

1  2
3  4
5  
11 22
6

但我得到的是:

1 2
3 4
5
11 22
11 22
6

即再次解析内表。

规格:
beautifulsoup4==4.6.3

应保留数据顺序,内容可以是任何内容,包括任何字母数字字符。

【问题讨论】:

  • 有网址可以分享吗?能否指出预期的输出?
  • 如果您的td 是您的行的直接子代,您可以使用recursive=False 作为find_all 方法中的参数。喜欢:row.find_all("td", recursive=False)
  • @Maaz 尝试过,但仍然可以解析
  • 如果你能给出一些具体的输入例子和你想要的结果会更好。
  • @InfectedDrake,我添加了示例输入和预期输出

标签: python-3.x html-table beautifulsoup


【解决方案1】:

使用 bs4 和 re 的组合,你可能会实现你想要的。

我使用的是 bs4 4.6.3

from bs4 import BeautifulSoup as bs
import re

html = '''
<table>
<tr>
   <td>1</td><td>2</td>
</tr>
<tr>
   <td>3</td><td>4</td>
</tr>
<tr>
  <td>5 
    <table><tr><td>11</td><td>22</td></tr></table>
      6
  </td>
</tr>
</table>'''

soup = bs(html, 'lxml')

ans = []

for x in soup.findAll('td'):
    if x.findAll('td'):
        for y in re.split('<table>.*</table>', str(x)):
            ans += re.findall('\d+', y)
    else:
        ans.append(x.text)
print(ans)

对于每个td,我们测试这是否是一个巢td。如果是这样,我们在桌子上拆分并获取所有内容并与每个数字匹配一个正则表达式。

注意这仅适用于两个深度级别,但适用于任何深度

【讨论】:

  • ['1', '2', '3', '4', '5', '6', '11', '22'] 输出没问题,但订单不见了!这是预期的 ['1', '2', '3', '4', '5', '11', '22','6']
  • 内容可以是任何东西,而不仅仅是数字
  • 感谢您指出 :) 您应该在您的问题中添加这些规范
  • 我试图给出一个最小的例子,所以无法涵盖所有​​方面
  • 你是对的!但现在这是规范的一部分,现在应该是问题:)
【解决方案2】:

我已尝试使用 findChilden() 方法以及如何设法产生输出。我不确定这在任何其他情况下是否会对您有所帮助。

from bs4 import BeautifulSoup
data='''<table>
<tr>
   <td>1</td><td>2</td>
</tr>
<tr>
   <td>3</td><td>4</td>
</tr>
<tr>
  <td>5 
    <table><tr><td>11</td><td>22</td></tr></table>
      6
  </td>
</tr>
</table>'''


soup=BeautifulSoup(data,'html.parser')

for child in soup.find('table').findChildren("tr" , recursive=False):
  tdlist = []
  if child.find('table'):
     for td in child.findChildren("td", recursive=False):
         print(td.next_element.strip())
         for td1 in td.findChildren("table", recursive=False):
             for child1 in td1.findChildren("tr", recursive=False):
                 for child2 in child1.findChildren("td", recursive=False):
                     tdlist.append(child2.text)
                 print(' '.join(tdlist))
                 print(child2.next_element.next_element.strip())
  else:

     for td in child.findChildren("td" , recursive=False):
         tdlist.append(td.text)
     print(' '.join(tdlist))

输出:

1 2
3 4
5
11 22
6

为解释而编辑

第 1 步:

在表内使用findChilden()时,首先返回3条记录。

for child in soup.find('table').findChildren("tr", recursive=False):
    print(child)

输出:

<tr>
<td>1</td><td>2</td>
</tr>
<tr>
<td>3</td><td>4</td>
</tr>
<tr>
<td>5 
        <table><tr><td>11</td><td>22</td></tr></table>
          6
      </td>
</tr>

第 2 步:

检查任何孩子是否有标签&lt;table&gt;并进行一些操作。

 if child.find('table'):

第 3 步:

按照步骤1,使用findChilden()获取&lt;td&gt;标签。

一旦您获得&lt;td&gt;,请按照步骤 1 再次获得孩子。


第 4 步:

for td in child.findChildren("td", recursive=False)            
      print(td.next_element.strip())

下一个元素将返回标签的第一个文本,因此在这种情况下它将返回值 5。


第 5 步

for td in child.findChildren("td", recursive=False):
             print(td.next_element.strip())
             for td1 in td.findChildren("table", recursive=False):
                 for child1 in td1.findChildren("tr", recursive=False):
                     for child2 in child1.findChildren("td", recursive=False):
                         tdlist.append(child2.text)
                     print(' '.join(tdlist))
                     print(child2.next_element.next_element.strip())

如果您在这里看到,我只是递归地执行第 1 步。是的,我再次使用 child2.next_element.next_element&lt;/table&gt; 标记之后获取 6 的值。

【讨论】:

  • 能否解释一下代码,以便我理解并按照我的要求使用
  • @Nagaraju : 你对这个解释满意还是需要进一步的帮助。
【解决方案3】:

您可以检查另一个table 是否存在于td 标记中,如果存在则直接跳过该td,否则将其用作常规td

for row in table.find_all("tr")[1:][:-1]:
    for td in row.find_all("td")[1:]:
        if td.find('table'):  # check if td has nested table
            continue
        dataset = td.get_text()

【讨论】:

  • 这可行,但它会跳过嵌套表之外的 中的任何文本
【解决方案4】:

在您的示例中,对于 bs4 4.7.1,我使用 :has :not 来排除带有子表的循环行

from bs4 import BeautifulSoup as bs

html = '''
<table>
    <tr>
        <td>1</td>
        <td>2</td>
    </tr>
    <tr>
        <td>3</td>
        <td>4</td>
    </tr>
    <tr>
        <td>
            <table>
                <tr>
                    <td>11</td>
                    <td>22</td>
                </tr>
            </table>
        </td>
    </tr>
</table>'''

soup = bs(html, 'lxml')
for tr in soup.select('tr:not(:has(table))'):
    print([td.text for td in tr.select('td')])

【讨论】:

  • 我正在使用 beautifulsoup4==4.6.3
  • 我建议升级,因为 4.7.1 更可靠且功能更多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-10
  • 2013-03-21
  • 1970-01-01
  • 2018-05-24
  • 1970-01-01
  • 2017-05-23
相关资源
最近更新 更多