Мне нужно обработать большой архив чрезвычайно беспорядочного HTML, полный посторонних таблиц, интервалов и встроенных стилей, в уценку.
Я пытаюсь использовать Красивый суп для выполнения этой задачи, и моя цель - в основном вывод функции get_text(), за исключением сохранения тегов привязки с неповрежденным href.
В качестве примера я хотел бы преобразовать:
<td>
<font><span>Hello</span><span>World</span></font><br>
<span>Foo Bar <span>Baz</span></span><br>
<span>Example Link: <a href = "https://google.com" target = "_blank" style = "mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #395c99;font-weight: normal;text-decoration: underline;">Google</a></span>
</td>
В:
Hello World
Foo Bar Baz
Example Link: <a href = "https://google.com">Google</a>
До сих пор мой мыслительный процесс заключался в том, чтобы просто захватить все теги и развернуть их все, если они не являются якорями, но это приводит к тому, что текст повторяется несколько раз, поскольку soup.find_all(True) возвращает рекурсивно вложенные теги как отдельные элементы:
#!/usr/bin/env python
from bs4 import BeautifulSoup
example_html = '<td><font><span>Hello</span><span>World</span></font><br><span>Foo Bar <span>Baz</span></span><br><span>Example Link: <a href = "https://google.com" target = "_blank" style = "mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #395c99;font-weight: normal;text-decoration: underline;">Google</a></span></td>'
soup = BeautifulSoup(example_html, 'lxml')
tags = soup.find_all(True)
for tag in tags:
if (tag.name == 'a'):
print("<a href='{}'>{}</a>".format(tag['href'], tag.get_text()))
else:
print(tag.get_text())
Что возвращает несколько фрагментов / дубликатов при перемещении парсера вниз по дереву:
HelloWorldFoo Bar BazExample Link: Google
HelloWorldFoo Bar BazExample Link: Google
HelloWorldFoo Bar BazExample Link: Google
HelloWorld
Hello
World
Foo Bar Baz
Baz
Example Link: Google
<a href='https://google.com'>Google</a>






Чтобы рассматривать только прямые дочерние элементы, установите рекурсивный = False, тогда вам нужно обработать каждый 'td' и отдельно извлечь текст и ссылку привязки.
#!/usr/bin/env python
from bs4 import BeautifulSoup
example_html = '<td><font><span>Some Example Text</span></font><br><span>Another Example Text</span><br><span>Example Link: <a href = "https://google.com" target = "_blank" style = "mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #395c99;font-weight: normal;text-decoration: underline;">Google</a></span></td>'
soup = BeautifulSoup(example_html, 'lxml')
tags = soup.find_all(recursive=False)
for tag in tags:
print(tag.text)
print(tag.find('a'))
Если вы хотите, чтобы текст был напечатан на отдельных строках, вам придется обрабатывать промежутки индивидуально.
for tag in tags:
spans = tag.find_all('span')
for span in spans:
print(span.text)
print(tag.find('a'))
Одним из возможных способов решения этой проблемы было бы введение некоторой специальной обработки для элементов a, когда дело доходит до печати текста элемента.
Вы можете сделать это, переопределив метод _all_strings() и вернув строковое представление элемента-потомка a, и пропустив строку с возможностью навигации внутри элемента a. Что-то в этом роде:
from bs4 import BeautifulSoup, NavigableString, CData, Tag
class MyBeautifulSoup(BeautifulSoup):
def _all_strings(self, strip=False, types=(NavigableString, CData)):
for descendant in self.descendants:
# return "a" string representation if we encounter it
if isinstance(descendant, Tag) and descendant.name == 'a':
yield str(descendant)
# skip an inner text node inside "a"
if isinstance(descendant, NavigableString) and descendant.parent.name == 'a':
continue
# default behavior
if (
(types is None and not isinstance(descendant, NavigableString))
or
(types is not None and type(descendant) not in types)):
continue
if strip:
descendant = descendant.strip()
if len(descendant) == 0:
continue
yield descendant
Демо:
In [1]: data = """
...: <td>
...: <font><span>Hello</span><span>World</span></font><br>
...: <span>Foo Bar <span>Baz</span></span><br>
...: <span>Example Link: <a href = "https://google.com" target = "_blank" style = "mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #395c99;font-weight: normal;tex
...: t-decoration: underline;">Google</a></span>
...: </td>
...: """
In [2]: soup = MyBeautifulSoup(data, "lxml")
In [3]: print(soup.get_text())
HelloWorld
Foo Bar Baz
Example Link: <a href = "https://google.com" style = "mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #395c99;font-weight: normal;text-decoration: underline;" target = "_blank">Google</a>
Поразительно, спасибо, это гибкое и элегантное решение, о котором я бы никогда не подумал. Я сделал небольшую корректировку обработки тега a, чтобы выводить так, как я хотел, и это идеально.
Как он мог вернуть только атрибут href? Нравится: Example Link: <https://google.com>
Отвечая на мой вопрос, просто измените: if isinstance(descendant, Tag) and descendant.name == 'a': yield str(descendant) Кому: if isinstance(descendant, Tag) and descendant.name == 'a': yield str('<{}> '.format(descendant.get('href', '')))
вы хотите удалить стили и другие атрибуты ссылок ?? потому что ваш ввод и вывод относится к этому