Использование модуля Beautiful Soup Python для замены тегов простым текстом - PullRequest
1 голос
/ 14 января 2010

Я использую Beautiful Soup для извлечения «контента» из веб-страниц. Я знаю, что некоторые люди задавали этот вопрос раньше, и все они указывали на Beautiful Soup, и именно так я начал с этого.

Мне удалось успешно получить большую часть контента, но я столкнулся с некоторыми проблемами с тегами, которые являются частью контента. (Я начинаю с базовой стратегии: если в узле больше x-символов, то это контент). Давайте возьмем приведенный ниже HTML-код в качестве примера:

<div id="abc">
    some long text goes <a href="/"> here </a> and hopefully it 
    will get picked up by the parser as content
</div>

results = soup.findAll(text=lambda(x): len(x) > 20)

Когда я использую приведенный выше код для получения длинного текста, он разрывается (идентифицированный текст начинается с «и, надеюсь, ...») на тегах. Поэтому я попытался заменить тег простым текстом следующим образом:

anchors = soup.findAll('a')

for a in anchors:
  a.replaceWith('plain text')

Выше не работает, потому что Beautiful Soup вставляет строку как NavigableString, и это вызывает ту же проблему, когда я использую findAll с len (x)> 20. Я могу использовать регулярные выражения, чтобы сначала проанализировать HTML как простой текст, очистите все ненужные теги и затем вызовите Beautiful Soup. Но я хотел бы не обрабатывать один и тот же контент дважды - я пытаюсь разобрать эти страницы, чтобы показать фрагмент контента по заданной ссылке (очень похоже на общий доступ к Facebook) - и, если все сделано с Beautiful Soup, Я предполагаю, что это будет быстрее.

Так что мой вопрос: есть ли способ «очистить теги» и заменить их «простым текстом» с помощью Beautiful Soup. Если нет, что будет лучшим способом сделать это?

Спасибо за ваши предложения!

Обновление: Код Алекса работал очень хорошо для примера примера. Я также пробовал различные крайние случаи, и они все работали хорошо (с модификацией ниже). Поэтому я попробовал сделать это на реальном веб-сайте и столкнулся с проблемами, которые меня озадачивают.

import urllib
from BeautifulSoup import BeautifulSoup

page = urllib.urlopen('http://www.engadget.com/2010/01/12/kingston-ssdnow-v-dips-to-30gb-size-lower-price/')

anchors = soup.findAll('a')
i = 0
for a in anchors:
    print str(i) + ":" + str(a)
    for a in anchors:
        if (a.string is None): a.string = ''
        if (a.previousSibling is None and a.nextSibling is None):
            a.previousSibling = a.string
        elif (a.previousSibling is None and a.nextSibling is not None):
            a.nextSibling.replaceWith(a.string + a.nextSibling)
        elif (a.previousSibling is not None and a.nextSibling is None):
            a.previousSibling.replaceWith(a.previousSibling + a.string)
        else:
            a.previousSibling.replaceWith(a.previousSibling + a.string + a.nextSibling)
            a.nextSibling.extract()
    i = i+1

Когда я запускаю приведенный выше код, я получаю следующую ошибку:

0:<a href="http://www.switched.com/category/ces-2010">Stay up to date with 
Switched's CES 2010 coverage</a>
Traceback (most recent call last):
  File "parselink.py", line 44, in <module>
  a.previousSibling.replaceWith(a.previousSibling + a.string + a.nextSibling)
 TypeError: unsupported operand type(s) for +: 'Tag' and 'NavigableString'

Когда я смотрю на HTML-код, «Будьте в курсе ..» не имеет никакого предыдущего брата (я не знал, как работал предыдущий брат, пока не увидел код Алекса и, основываясь на моем тестировании, выглядит так, будто он ищет 'text' перед тэгом). Итак, если нет предыдущего брата, я удивляюсь, что он не проходит через логику if a.previousSibling - None и a; nextSibling - None.

Не могли бы вы дать мне знать, что я делаю неправильно?

-ecognium

Ответы [ 2 ]

3 голосов
/ 14 января 2010

Подход, который работает для вашего конкретного примера:

from BeautifulSoup import BeautifulSoup

ht = '''
<div id="abc">
    some long text goes <a href="/"> here </a> and hopefully it 
    will get picked up by the parser as content
</div>
'''
soup = BeautifulSoup(ht)

anchors = soup.findAll('a')
for a in anchors:
  a.previousSibling.replaceWith(a.previousSibling + a.string)

results = soup.findAll(text=lambda(x): len(x) > 20)

print results

который излучает

$ python bs.py
[u'\n    some long text goes  here ', u' and hopefully it \n    will get picked up by the parser as content\n']

Конечно, вам, вероятно, придется проявить немного больше внимания, т. Е. Если нет a.string или если a.previousSibling равно None - вам понадобятся подходящие if операторы для принятия уход за такими угловыми делами. Но я надеюсь, что эта общая идея поможет вам. (На самом деле вы можете захотеть также объединить следующий брат или сестру, если это строка - не знаете, как это сочетается с вашей эвристикой len(x) > 20, но, например, скажите, что у вас 9-символьные строки с <a>, содержащие 5-символьные строки в середине, может быть, вы захотите выбрать лот как «23-символьную строку»? Я не могу сказать, потому что не понимаю мотивация для вашей эвристики).

Я полагаю, что помимо тегов <a> вы также захотите удалить другие, такие как <b> или <strong>, возможно <p> и / или <br> и т. Д. ...? Я думаю, это тоже зависит от того, что на самом деле стоит за вашей эвристикой!

1 голос
/ 02 июля 2010

Когда я попытался сгладить теги в документе, таким образом, все содержимое тегов было бы перенесено на его родительский узел на месте (я хотел уменьшить содержимое p тег со всеми подпунктами, списками, div и span и т. Д. Внутри, но избавиться от шрифта style и font теги и некоторые ужасные остатки генератора word-to-html), я обнаружил, что это довольно сложно сделать с самой BeautifulSoup, так как extract () также удаляет содержимое и replaceWith () , к сожалению, не делает не принимать Нет в качестве аргумента. После некоторых экспериментов с дикой рекурсией я, наконец, решил использовать регулярные выражения до или после обработки документа с помощью BeautifulSoup следующим методом:

import re
def flatten_tags(s, tags):
   pattern = re.compile(r"<(( )*|/?)(%s)(([^<>]*=\\\".*\\\")*|[^<>]*)/?>"%(isinstance(tags, basestring) and tags or "|".join(tags)))
   return pattern.sub("", s)

Аргумент tags является либо одним тегом, либо списком тегов, которые должны быть сведены.

...