Удалите тег с помощью BeautifulSoup, но сохраните его содержимое
В настоящее время у меня есть код, который делает что-то вроде этого:
soup = BeautifulSoup(value)
for tag in soup.findAll(True):
if tag.name not in VALID_TAGS:
tag.extract()
soup.renderContents()
Кроме того, я не хочу выбрасывать содержимое внутри недопустимого тега. Как избавиться от тега, но сохранить содержимое внутри, вызывая soup.renderContents()?
Ответы
Ответ 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
, который вы можете установить на теги, которые будут удалены в то время как их контент попадает в родительский тег.