Python BeautifulSoup извлекает текст между элементами
Я пытаюсь извлечь "ЭТО МОЙ ТЕКСТ" из следующего HTML:
<html>
<body>
<table>
<td class="MYCLASS">
<!-- a comment -->
<a hef="xy">Text</a>
<p>something</p>
THIS IS MY TEXT
<p>something else</p>
</br>
</td>
</table>
</body>
</html>
Я пробовал это следующим образом:
soup = BeautifulSoup(html)
for hit in soup.findAll(attrs={'class' : 'MYCLASS'}):
print hit.text
Но я получаю весь текст между всеми вложенными тегами плюс комментарий.
Может ли кто-нибудь помочь мне просто получить "ЭТО МОЙ ТЕКСТ" из этого?
Ответы
Ответ 1
Подробнее о том, как перемещаться по с помощью дерева синтаксического анализа в BeautifulSoup
. Дерево обработки имеет tags
и NavigableStrings
(как ЭТО ТЕКСТ). Пример
from BeautifulSoup import BeautifulSoup
doc = ['<html><head><title>Page title</title></head>',
'<body><p id="firstpara" align="center">This is paragraph <b>one</b>.',
'<p id="secondpara" align="blah">This is paragraph <b>two</b>.',
'</html>']
soup = BeautifulSoup(''.join(doc))
print soup.prettify()
# <html>
# <head>
# <title>
# Page title
# </title>
# </head>
# <body>
# <p id="firstpara" align="center">
# This is paragraph
# <b>
# one
# </b>
# .
# </p>
# <p id="secondpara" align="blah">
# This is paragraph
# <b>
# two
# </b>
# .
# </p>
# </body>
# </html>
Для перемещения вниз по дереву синтаксического анализа у вас есть contents
и string
.
-
Содержимое - это упорядоченный список объектов Tag и NavigableString содержащихся в элементе страницы
-
если тег имеет только один дочерний элемент node, а этот дочерний элемент node является строкой, дочерний элемент node доступен как tag.string, а также tag.contents [0]
Для вышеизложенного, то есть вы можете получить
soup.b.string
# u'one'
soup.b.contents[0]
# u'one'
Для нескольких дочерних узлов вы можете иметь, например,
pTag = soup.p
pTag.contents
# [u'This is paragraph ', <b>one</b>, u'.']
, поэтому здесь вы можете играть с contents
и получать содержимое по нужному вам индексу.
Вы также можете перебирать тег, это ярлык. Например,
for i in soup.body:
print i
# <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>
# <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>
Ответ 2
Вы можете использовать .contents
:
>>> for hit in soup.findAll(attrs={'class' : 'MYCLASS'}):
... print hit.contents[6].strip()
...
THIS IS MY TEXT
Ответ 3
Используйте .children
вместо:
from bs4 import NavigableString, Comment
print ''.join(unicode(child) for child in hit.children
if isinstance(child, NavigableString) and not isinstance(child, Comment))
Да, это немного танца.
Вывод:
>>> for hit in soup.findAll(attrs={'class' : 'MYCLASS'}):
... print ''.join(unicode(child) for child in hit.children
... if isinstance(child, NavigableString) and not isinstance(child, Comment))
...
THIS IS MY TEXT
Ответ 4
с вашим собственным объектом супа:
soup.p.next_sibling.strip()
- вы захватываете <p> непосредственно с
soup.p
* (это зависит от того, что он является первым <p> в дереве разбора)
- затем используйте
next_sibling
для объекта тега, который возвращает soup.p
, поскольку желаемый текст вложен на тот же уровень дерева разбора, что и <p>
-
.strip()
- это просто метод Python str для удаления начальных и конечных пробелов.
* иначе find элемент, используя ваш выбор filter (ы)
в интерпретаторе это выглядит примерно так:
In [4]: soup.p
Out[4]: <p>something</p>
In [5]: type(soup.p)
Out[5]: bs4.element.Tag
In [6]: soup.p.next_sibling
Out[6]: u'\n THIS IS MY TEXT\n '
In [7]: type(soup.p.next_sibling)
Out[7]: bs4.element.NavigableString
In [8]: soup.p.next_sibling.strip()
Out[8]: u'THIS IS MY TEXT'
In [9]: type(soup.p.next_sibling.strip())
Out[9]: unicode
Ответ 5
Короткий ответ: soup.findAll('p')[0].next
Реальный ответ: вам нужна инвариантная контрольная точка, из которой вы можете добраться до своей цели.
Вы упомянули в своем комментарии к Хайдро, что текст, который вы хотите, не всегда находится в одном месте. Найдите смысл, в котором он находится в одном и том же месте относительно некоторого элемента. Затем выясните, как сделать BeautifulSoup перемещаться по дереву разбора, следуя этому инвариантному пути.
Например, в HTML, который вы указываете в исходном сообщении, целевая строка появляется сразу после элемента первого абзаца, и этот абзац не пуст. Поскольку findAll('p')
найдет элементы абзаца, soup.find('p')[0]
будет элементом первого абзаца.
В этом случае вы можете использовать soup.find('p')
, но soup.findAll('p')[n]
является более общим, поскольку, возможно, вашему фактическому сценарию нужен 5-й параграф или что-то в этом роде.
Атрибут поля next
будет следующим анализируемым элементом в дереве, включая дочерние элементы. Таким образом, soup.findAll('p')[0].next
содержит текст абзаца, а soup.findAll('p')[0].next.next
вернет вашу цель в предоставленном HTML.
Ответ 6
Документация BeautifulSoup дает пример об удалении объектов из документа с использованием метода extract. В следующем примере цель состоит в том, чтобы удалить все комментарии из документа:
Удаление элементов
Как только у вас есть ссылка на элемент, вы можете вырвать его из дерево с методом извлечения. Этот код удаляет все комментарии из документа:
from BeautifulSoup import BeautifulSoup, Comment
soup = BeautifulSoup("""1<!--The loneliest number-->
<a>2<!--Can be as bad as one--><b>3""")
comments = soup.findAll(text=lambda text:isinstance(text, Comment))
[comment.extract() for comment in comments]
print soup
# 1
# <a>2<b>3</b></a>
Ответ 7
soup = BeautifulSoup(html)
for hit in soup.findAll(attrs={'class' : 'MYCLASS'}):
hit = hit.text.strip()
print hit
Это будет печатать: ЭТО МОЙ ТЕКСТ
Попробуйте это.