Подтвердить что ты не робот

Удалите тег с помощью BeautifulSoup, но сохраните его содержимое

В настоящее время у меня есть код, который делает что-то вроде этого:

soup = BeautifulSoup(value)

for tag in soup.findAll(True):
    if tag.name not in VALID_TAGS:
        tag.extract()
soup.renderContents()

Кроме того, я не хочу выбрасывать содержимое внутри недопустимого тега. Как избавиться от тега, но сохранить содержимое внутри, вызывая soup.renderContents()?

4b9b3361

Ответ 1

Используемая мной стратегия заключается в замене тега его содержимым, если они имеют тип NavigableString, а если нет, то перезапишите их и замените их содержимым на NavigableString и т.д. Попробуйте следующее:

from BeautifulSoup import BeautifulSoup, NavigableString

def strip_tags(html, invalid_tags):
    soup = BeautifulSoup(html)

    for tag in soup.findAll(True):
        if tag.name in invalid_tags:
            s = ""

            for c in tag.contents:
                if not isinstance(c, NavigableString):
                    c = strip_tags(unicode(c), invalid_tags)
                s += unicode(c)

            tag.replaceWith(s)

    return soup

html = "<p>Good, <b>bad</b>, and <i>ug<b>l</b><u>y</u></i></p>"
invalid_tags = ['b', 'i', 'u']
print strip_tags(html, invalid_tags)

Результат:

<p>Good, bad, and ugly</p>

Я дал тот же ответ на другой вопрос. Кажется, это очень много.

Ответ 2

Текущие версии библиотеки BeautifulSoup имеют недокументированный метод для объектов Tag, называемых replaceWithChildren(). Итак, вы можете сделать что-то вроде этого:

html = "<p>Good, <b>bad</b>, and <i>ug<b>l</b><u>y</u></i></p>"
invalid_tags = ['b', 'i', 'u']
soup = BeautifulSoup(html)
for tag in invalid_tags: 
    for match in soup.findAll(tag):
        match.replaceWithChildren()
print soup

Похоже, что он ведет себя так, как вы этого хотите, и это довольно простой код (хотя он делает несколько проходов через DOM, но это легко можно оптимизировать.)

Ответ 3

Хотя это уже упоминалось другими людьми в комментариях, я думал, что опубликую полный ответ, показывающий, как это сделать с Mozilla Bleach. Лично я считаю, что это намного лучше, чем использование BeautifulSoup для этого.

import bleach
html = "<b>Bad</b> <strong>Ugly</strong> <script>Evil()</script>"
clean = bleach.clean(html, tags=[], strip=True)
print clean # Should print: "Bad Ugly Evil()"

Ответ 4

У меня более простое решение, но я не знаю, есть ли у него недостаток.

ОБНОВЛЕНИЕ: есть недостаток, см. комментарий Джесси Дхиллон. Кроме того, другим решением будет использование Mozilla Bleach вместо BeautifulSoup.

from BeautifulSoup import BeautifulSoup

VALID_TAGS = ['div', 'p']

value = '<div><p>Hello <b>there</b> my friend!</p></div>'

soup = BeautifulSoup(value)

for tag in soup.findAll(True):
    if tag.name not in VALID_TAGS:
        tag.replaceWith(tag.renderContents())

print soup.renderContents()

Это также напечатает <div><p>Hello there my friend!</p></div> по желанию.

Ответ 5

вы можете использовать soup.text

.text удаляет все теги и конкатенирует весь текст.

Ответ 6

Вам, вероятно, придется переместить тег-теги, чтобы быть дочерними родителями тегов, прежде чем удалить тег - это то, что вы имеете в виду?

Если это так, то при вставке содержимого в нужное место сложно, что-то вроде этого должно работать:

from BeautifulSoup import BeautifulSoup

VALID_TAGS = 'div', 'p'

value = '<div><p>Hello <b>there</b> my friend!</p></div>'

soup = BeautifulSoup(value)

for tag in soup.findAll(True):
    if tag.name not in VALID_TAGS:
        for i, x in enumerate(tag.parent.contents):
          if x == tag: break
        else:
          print "Can't find", tag, "in", tag.parent
          continue
        for r in reversed(tag.contents):
          tag.parent.insert(i, r)
        tag.extract()
print soup.renderContents()

с примером, это печатает <div><p>Hello there my friend!</p></div> по желанию.

Ответ 7

Ни один из предложенных ответов, похоже, не работал с BeautifulSoup для меня. Здесь версия, которая работает с BeautifulSoup 3.2.1, а также вставляет пространство при объединении контента из разных тегов вместо конкатенации слов.

def strip_tags(html, whitelist=[]):
    """
    Strip all HTML tags except for a list of whitelisted tags.
    """
    soup = BeautifulSoup(html)

    for tag in soup.findAll(True):
        if tag.name not in whitelist:
            tag.append(' ')
            tag.replaceWithChildren()

    result = unicode(soup)

    # Clean up any repeated spaces and spaces like this: '<a>test </a> '
    result = re.sub(' +', ' ', result)
    result = re.sub(r' (<[^>]*> )', r'\1', result)
    return result.strip()

Пример:

strip_tags('<h2><a><span>test</span></a> testing</h2><p>again</p>', ['a'])
# result: u'<a>test</a> testing again'

Ответ 8

Вот лучшее решение без каких-либо проблем и шаблона кода, чтобы отфильтровать теги, поддерживающие контент. Предполагается, что вы хотите удалить теги детей в родительском теге и просто хотите сохранить содержимое/текст, тогда вы можете просто сделать:

for p_tags in div_tags.find_all("p"):
    print(p_tags.get_text())

Чтобы он и вы могли быть свободны со всеми тегами br или я b в родительских тегах и получать чистый текст.

Ответ 9

Используйте распаковать.

Unwrap удалит одно из нескольких экземпляров тега и сохранит содержимое.

Пример:

>> soup = BeautifulSoup('Hi. This is a <nobr> nobr </nobr>')
>> soup
<html><body><p>Hi. This is a <nobr> nobr </nobr></p></body></html>
>> soup.nobr.unwrap
<nobr></nobr>
>> soup
>> <html><body><p>Hi. This is a nobr </p></body></html>

Ответ 10

Это старый вопрос, но просто сказать о лучших способах его решения. Прежде всего, BeautifulSoup 3 * больше не разрабатывается, поэтому лучше использовать BeautifulSoup 4 *, так называемый bs4.

Кроме того, у lxml есть только что необходимая функция: Класс чистого имеет атрибут remove_tags, который вы можете установить на теги, которые будут удалены в то время как их контент попадает в родительский тег.