Возможно ли выполнить итерацию по HTML-дереву в BeautifulSoup4?

#python #html #beautifulsoup

#python #HTML #beautifulsoup

Вопрос:

Пример HTML (‘x.html ‘ в фрагменте python):

 <table>
    <tr>
        <td>a</td>
        <td>b</td>
        <table><tr><td>c</td></tr></table>
    </tr>
</table>
  

Я хочу получить список с тремя столбцами из одной строки в таблице:

 [
  '<td>a</td>',
  '<td>b</td>',
  '<table><tr><td>c</td></tr></table>'
]
  

Я попытался просто выполнить итерацию по BeautifulSoup объекту, но он возвращает весь HTML и пустую (ну, 'n' ) строку.

 In [9]: soup = BeautifulSoup(open('x.html').read(), 'html.parser')
In [10]: for a in soup: 
    ...:     print(type(a)) 
    ...:                                                                                                                                                                                                    
<class 'bs4.element.Tag'>
<class 'bs4.element.NavigableString'>
  

Я также пытался использовать find_all() метод, но он находит вложенные <td>c</td> , которые я не хочу видеть в результатах:

 In [24]: len(soup.find_all('td'))                                                                                                                                                                           
Out[24]: 4  # <-- I need 3 things, not 4
  

Я думал, что параметр find / find_all recursive относится к вложенным элементам, но я не понимаю, работает ли это вообще:

 Signature: soup.find_all(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
In [26]: len(soup.find_all('td', recursive=False))                                                                                                                                                          
Out[26]: 0
  

Может быть, написать xml.sax синтаксический анализатор будет проще?

Комментарии:

1. К вашему сведению, я не верю, что наличие a <table> в качестве дочернего элемента a <tr> является допустимым html.

2. find внешний <tr> , а затем изучите .contents — вы должны увидеть дочерние теги, которые похожи на то, что вы ищете.

Ответ №1:

Как предложила @Danielle в комментарии, вы можете получить .contents из внешнего tr . Но поскольку вы читаете из этого файла, вы получите ряд "n" и других нежелательных элементов вместе с этим. Вы можете проверить, isinstance(x,Tag) чтобы получить только содержимое тега.

В некоторых ситуациях с неправильным html может не быть такого простого решения, как это. В этих случаях вы также можете передать пользовательскую функцию в find_all . Например. Данные, которые вы ищете, также могут быть получены с помощью этой логики — Найдите все td и table теги в первой таблице файла. Конечно, логика может отличаться от этой, но вы поняли идею.

 from bs4 import BeautifulSoup
from bs4 import Tag
soup = BeautifulSoup(open('x.html').read(), 'html.parser')
#solution 1
print([x for x in soup.find('tr').contents if isinstance(x,Tag)])
#solution 2 with a custom function
first_table=soup.find('table')
def is_td_or_table(item):
    if isinstance(item,Tag):
        if item.name in ['td','table'] and item.find_parent("table") is first_table:
            return True
print(first_table.find_all(is_td_or_table))