.find () в BeautifulSoup в Python - PullRequest
0 голосов
/ 02 июля 2018

Я пытаюсь использовать Beautiful Soup для поиска HTML-документа. Есть ли способ найти в документе таблицу, которая содержит определенные строки ключевых слов? Например, если бы у меня была таблица:

<table>
  <tr>
    <td> 'abc' </td>
    <td> 'other data' </td>
  </tr>
  <tr>
    <td> 'def' </td>
    <td> 'other data' </td>
  </tr>
  <tr>
    <td> '123' </td>
    <td> 'other data' </td>
  </tr>
  <tr>
    <td> '456' </td>
    <td> 'other data' </td>
  </tr>
</table>

в HTML-документе, в котором было много других таблиц, но это была единственная таблица, содержащая строки «abc», «def» и «456», есть ли способ поиска этой таблицы?

У меня была мысль использовать цикл for для поиска во всех таблицах, строках и данных, но это кажется ненужным количеством шагов (документ очень большой). Есть ли способ сформировать вопрос, используя BeautifulSoup, например (очевидно, это не будет работать, но, возможно, это дает представление о том, что я хочу иметь возможность писать):

.find('table', string=('abc'), string=('def'), string=re.compile('456'))

edit: Да, таблице нужны все эти строки. HTML-файл также содержит другие таблицы, которые могут содержать любую из этих строк, но только 1 таблица имеет все 3 вместе. И да, я буду искать только строки в тегах (которые могут быть «текст» вместо «строка», я не уверен в разнице).

1 Ответ

0 голосов
/ 03 июля 2018

Одним из решений было бы передать функцию в soup.find(). Эта функция должна принимать один и только один аргумент (объект bs4.element.Tag), и ее целью является возвращение только True или False: True, если таблица соответствует заданным вами критериям.

Если вы хотите протестировать разные строки, вы можете начать с функции, которая принимает два параметра, и использовать functools.partial(), чтобы уменьшить ее до одного:

from functools import partial

def _table_contains_strs(tag, strings):
    """Return True if `tag` has <td> tags that contain `strings`."""
    if tag.name != 'table':
        return False
    tds = tag.find_all('td')
    if not tds:
        return False
    test = {s: False for s in strings}
    for tag in tds:
        for s in strings:
            if s in tag:
                test[s] = True
        if all(test.values()):
            # You can return early (without full iteration)
            # if all strings already matched.
            return True
    return False

def _make_single_arg_function(func, *args, **kwargs):
    return partial(_table_contains_strs, *args, **kwargs)

table_contains_strs = _make_single_arg_function(
    _table_contains_strs,
    strings=('abc', 'def', '456')
)

Теперь table_contains_strs - это функция, которая принимает один аргумент, Tag.

Вот пример:

from bs4 import BeautifulSoup

# Add some text to other tags to make sure you're
# finding only in <td>s
html = """\
<table>
    <th>field1</th>
    <th>field2</th>
  <tr>
    <td>abc</td>
    <td>other data</td>
  </tr>
  <tr>
    <td>def</td>
    <td>other data</td>
  </tr>
  <tr>
    <td>123</td>
    <td>other data</td>
  </tr>
  <tr>
    <td>456</td>
    <td>other data</td>
  </tr>
</table>"""

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

soup.find(table_contains_strs)
# Should return the table above

Примечание: я не могу сказать, что это масштабировалось бы исключительно хорошо, потому что он использует вложенный цикл for для проверки каждой строки в каждом <td> теге. Но, надеюсь, это сделает работу. Возможно, было бы не очень хорошей идеей просто использовать .text, потому что это будет захватывать и другие вложенные теги, которые вы указали, что вы не хотели. Другой оптимизацией было бы сделать ленивую версию генератора из find_all().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...