Стриптиз HTML из строк в Python
from mechanize import Browser
br = Browser()
br.open('http://somewebpage')
html = br.response().readlines()
for line in html:
print line
При печати строки в файле HTML я пытаюсь найти способ показать содержимое только каждого элемента HTML, а не самого форматирования. Если он найдет '<a href="whatever.com">some text</a>'
, он будет печатать только "некоторый текст", '<b>hello</b>'
печатает "привет" и т.д. Как это можно сделать?
Ответы
Ответ 1
Я всегда использовал эту функцию для разметки тегов HTML, поскольку для этого требуется только Python stdlib:
В Python 2
from HTMLParser import HTMLParser
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)
def strip_tags(html):
s = MLStripper()
s.feed(html)
return s.get_data()
Для Python 3
from html.parser import HTMLParser
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.strict = False
self.convert_charrefs= True
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)
def strip_tags(html):
s = MLStripper()
s.feed(html)
return s.get_data()
Примечание: это работает только для 3.1. Для версии 3.2 или выше вам нужно вызвать родительский класс init. См. Использование HTMLParser в Python 3.2
Ответ 2
Я не очень много думал о случаях, которые он пропустит, но вы можете сделать простое регулярное выражение:
re.sub('<[^<]+?>', '', text)
Для тех, кто не понимает регулярное выражение, он ищет строку <...>
, где внутренний контент состоит из одного или нескольких символов (+
), который не является <
. ?
означает, что он будет соответствовать самой маленькой строке, которую он может найти. Например, с учетом <p>Hello</p>
он будет соответствовать <'p>
и </p>
отдельно с помощью ?
. Без него он будет соответствовать всей строке <..Hello..>
.
Если в html (например, 2 < 3
) появляется не тег <
, он должен быть записан как escape-последовательность &...
, поэтому ^<
может быть ненужным.
Ответ 3
Почему все вы делаете это трудным путем? Вы можете использовать get_text()
BeautifulSoup get_text()
.
from bs4 import BeautifulSoup
html_str = '''
<td><a href="http://www.fakewebsite.com">Please can you strip me?</a>
<br/><a href="http://www.fakewebsite.com">I am waiting....</a>
</td>
'''
soup = BeautifulSoup(html_str)
print(soup.get_text())
#or via attribute of Soup Object: print(soup.text)
Ответ 4
Краткая версия!
import re, cgi
tag_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
# Remove well-formed tags, fixing mistakes by legitimate users
no_tags = tag_re.sub('', user_input)
# Clean up anything else by escaping
ready_for_web = cgi.escape(no_tags)
Источник регулярных выражений: MarkupSafe. Их версия также обрабатывает объекты HTML, в то время как этот быстрый не делает.
Почему я не могу просто удалить теги и оставить его?
Одно дело держать людей от <i>italicizing</i>
вещей, не оставляя i
плавающим. Но это другое, чтобы принять произвольный вклад и сделать его совершенно безвредным. Большинство методов на этой странице оставят такие вещи, как закрытые комментарии (<!--
) и угловые скобки, которые не являются частью тегов (blah <<<><blah
) неповрежденными. Версия HTMLParser может даже оставить полные теги, если они находятся в закрытом комментарии.
Что делать, если ваш шаблон {{ firstname }} {{ lastname }}
? firstname = '<a'
и lastname = 'href="http://evil.com/">'
будут пропускаться каждым дескриптором тега на этой странице (кроме @Medeiros!), потому что они не являются полными тегами самостоятельно. Вырезать обычные теги HTML недостаточно.
Django strip_tags
, улучшенная (см. следующий заголовок) версия верхнего ответа на этот вопрос дает следующее предупреждение:
Абсолютно БЕЗ гарантии о том, что полученная строка является безопасным для HTML. Поэтому НИКОГДА не маркируйте результат вызова strip_tags
, не ускоряя его, например, с помощью escape()
.
Следуйте их советам!
Чтобы разбить теги с помощью HTMLParser, вы должны запускать его несколько раз.
Легко обойти верхний ответ на этот вопрос.
Посмотрите на эту строку (источник и обсуждение):
<img<!-- --> src=x onerror=alert(1);//><!-- -->
В первый раз, когда HTMLParser видит это, он не может сказать, что <img...>
является тегом. Он выглядит сломанным, поэтому HTMLParser не избавляется от него. Он вынимает только <!-- comments -->
, оставляя вас с
<img src=x onerror=alert(1);//>
Эта проблема была раскрыта проекту Django в марте 2014 года. Их старый strip_tags
был по существу тем же самым, что и верхний ответ на этот вопрос. Их новая версия в основном запускает его в цикле до тех пор, пока не запустит его снова, не изменит строку:
# _strip_once runs HTMLParser once, pulling out just the text of all the nodes.
def strip_tags(value):
"""Returns the given HTML with all tags stripped."""
# Note: in typical case this loop executes _strip_once once. Loop condition
# is redundant, but helps to reduce number of executions of _strip_once.
while '<' in value and '>' in value:
new_value = _strip_once(value)
if len(new_value) >= len(value):
# _strip_once was not able to detect more tags
break
value = new_value
return value
Конечно, ничто из этого не является проблемой, если вы всегда избегаете результата strip_tags()
.
Обновление 19 марта 2015 г.. В версиях Django произошла ошибка до 1.4.20, 1.6.11, 1.7.7 и 1.8c1. Эти версии могут вводить бесконечный цикл в функцию strip_tags(). Исправленная версия воспроизводится выше. Подробнее здесь.
Хорошие вещи для копирования или использования
Мой примерный код не обрабатывает HTML-объекты - пакеты с Django и MarkupSafe.
Мой примерный код извлекается из отличной библиотеки MarkupSafe для предотвращения межсайтовых скриптингов. Это удобно и быстро (с ускорением C к его родной версии Python). Он включен в Google App Engine и используется Jinja2 (2.7 и выше), Mako, Pylons и Больше. Он легко работает с шаблонами Django от Django 1.7.
Django strip_tags и другие утилиты html из последней версии хороши, но я считаю их менее удобными, чем MarkupSafe. Они довольно самодостаточны, вы можете скопировать то, что вам нужно от этого файла.
Если вам нужно снять почти все теги, библиотека Bleach хороша. Вы можете заставить его применять такие правила, как "мои пользователи могут выделять курсивом, но они не могут создавать iframes".
Поймите свойства вашего стриппера тегов! Запустите тесты Fuzz! Вот код, который я использовал для исследования для этого ответа.
sheepish note - сам вопрос заключается в печати на консоли, но это лучший результат Google для "python strip html from string", поэтому почему этот ответ составляет 99% в Интернете.
Ответ 5
Мне нужен был способ вырезать теги и декодировать HTML-объекты в обычный текст. Следующее решение основано на ответе Eloff (который я не смог использовать, потому что он удаляет сущности).
from HTMLParser import HTMLParser
import htmlentitydefs
class HTMLTextExtractor(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.result = [ ]
def handle_data(self, d):
self.result.append(d)
def handle_charref(self, number):
codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number)
self.result.append(unichr(codepoint))
def handle_entityref(self, name):
codepoint = htmlentitydefs.name2codepoint[name]
self.result.append(unichr(codepoint))
def get_text(self):
return u''.join(self.result)
def html_to_text(html):
s = HTMLTextExtractor()
s.feed(html)
return s.get_text()
Быстрый тест:
html = u'<a href="#">Demo <em>(¬ \u0394ημώ)</em></a>'
print repr(html_to_text(html))
Результат:
u'Demo (\xac \u0394\u03b7\u03bc\u03ce)'
Обработка ошибок:
- Неверная структура HTML может вызвать HTMLParseError.
- Недопустимые именованные сущности HTML (такие как
&#apos;
допустимый в XML и XHTML, но не в обычном HTML) вызовут исключение ValueError
. - Числовые объекты HTML, указывающие кодовые точки вне диапазона Unicode, приемлемого для Python (например, в некоторых системах символы вне базовой многоязычной плоскости), вызовут исключение
ValueError
.
Примечание по безопасности: Не путайте разметку HTML (преобразование HTML в простой текст) с очисткой HTML (преобразование обычного текста в HTML). Этот ответ удалит HTML и расшифрует объекты в обычный текст, что не делает результат безопасным для использования в контексте HTML.
Пример: <script>alert("Hello");</script>
будет преобразован в <script>alert("Hello");</script>
, который на 100% корректен, но явно недостаточен, если полученный текстовый текст вставляется как есть на страницу HTML.
Правило не сложное: всякий раз, когда вы вставляете текстовую строку в вывод HTML, вы всегда должны избегать ее с помощью HTML (используя cgi.escape(s, True)
), даже если вы "знаете", что она не содержит HTML (например, потому что вы удалили содержимое HTML).
(Однако OP спросил о выводе результата на консоль, и в этом случае экранирование HTML не требуется.)
Версия Python 3. 4+: (с doctest!)
import html.parser
class HTMLTextExtractor(html.parser.HTMLParser):
def __init__(self):
super(HTMLTextExtractor, self).__init__()
self.result = [ ]
def handle_data(self, d):
self.result.append(d)
def get_text(self):
return ''.join(self.result)
def html_to_text(html):
"""Converts HTML to plain text (stripping tags and converting entities).
>>> html_to_text('<a href="#">Demo<!--...--> <em>(¬ \u0394ημώ)</em></a>')
'Demo (\xac \u0394\u03b7\u03bc\u03ce)'
"Plain text" doesn't mean result can safely be used as-is in HTML.
>>> html_to_text('<script>alert("Hello");</script>')
'<script>alert("Hello");</script>'
Always use html.escape to sanitize text before using in an HTML context!
HTMLParser will do its best to make sense of invalid HTML.
>>> html_to_text('x < y < z <!--b')
'x < y < z '
Unrecognized named entities are included as-is. ''' is recognized,
despite being XML only.
>>> html_to_text('&nosuchentity; ' ')
"&nosuchentity; ' "
"""
s = HTMLTextExtractor()
s.feed(html)
return s.get_text()
Обратите внимание, что HTMLParser улучшился в Python 3 (что означает меньше кода и лучшую обработку ошибок).
Ответ 6
Простой способ:
def remove_html_markup(s):
tag = False
quote = False
out = ""
for c in s:
if c == '<' and not quote:
tag = True
elif c == '>' and not quote:
tag = False
elif (c == '"' or c == "'") and tag:
quote = not quote
elif not tag:
out = out + c
return out
Идея объясняется здесь: http://youtu.be/2tu9LTDujbw
Вы можете увидеть, как он работает здесь: http://youtu.be/HPkNPcYed9M?t=35s
PS - Если вас интересует класс (о умной отладке с помощью python), я даю вам ссылку: http://www.udacity.com/overview/Course/cs259/CourseRev/1. Это бесплатно!
Добро пожаловать!:)
Ответ 7
Если вам нужно сохранить объекты HTML (т.е. &
), я добавил метод handle_entityref для ответа Eloff.
from HTMLParser import HTMLParser
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def handle_entityref(self, name):
self.fed.append('&%s;' % name)
def get_data(self):
return ''.join(self.fed)
def html_to_text(html):
s = MLStripper()
s.feed(html)
return s.get_data()
Ответ 8
Если вы хотите удалить все HTML-теги, я обнаружил, что проще всего использовать BeautifulSoup:
from bs4 import BeautifulSoup # Or from BeautifulSoup import BeautifulSoup
def stripHtmlTags(htmlTxt):
if htmlTxt is None:
return None
else:
return ''.join(BeautifulSoup(htmlTxt).findAll(text=True))
Я попробовал код принятого ответа, но я получил "RuntimeError: превышена максимальная глубина рекурсии", чего не произошло с вышеуказанным блоком кода.
Ответ 9
Решение lxml.html -based (lxml является нативной библиотекой и поэтому намного быстрее, чем любое чистое решение на python).
from lxml import html
from lxml.html.clean import clean_html
tree = html.fromstring("""<span class="item-summary">
Detailed answers to any questions you might have
</span>""")
print(clean_html(tree).strip())
# >>> Detailed answers to any questions you might have
Также смотрите http://lxml.de/lxmlhtml.html#cleaning-up-html, что именно делает lxml.cleaner.
Если вам требуется больше контроля над тем, что именно очищается перед преобразованием в текст, тогда вы можете явно использовать lxml Cleaner, передавая нужные параметры в конструкторе, например:
cleaner = Cleaner(page_structure=True,
meta=True,
embedded=True,
links=True,
style=True,
processing_instructions=True,
inline_style=True,
scripts=True,
javascript=True,
comments=True,
frames=True,
forms=True,
annoying_tags=True,
remove_unknown_tags=True,
safe_attrs_only=True,
safe_attrs=frozenset(['src','color', 'href', 'title', 'class', 'name', 'id']),
remove_tags=('span', 'font', 'div')
)
sanitized_html = cleaner.clean_html(unsafe_html)
Ответ 10
Пакет Beautiful Soup делает это немедленно для вас.
from bs4 import BeautifulSoup
soup = BeautifulSoup(html)
text = soup.get_text()
print(text)
Ответ 11
Вы можете использовать либо другой парсер HTML (как lxml, либо Beautiful Soup) - тот, который предлагает функции для извлечения только текста. Или вы можете запустить регулярное выражение в строке строки, которая удаляет теги. Подробнее см. http://www.amk.ca/python/howto/regex/.
Ответ 12
Я успешно использовал Eloff для Python 3.1 [большое спасибо!].
Я обновился до Python 3.2.3 и столкнулся с ошибками.
Решение, предоставленное здесь благодаря ответчику Томасу К., должно вставить super().__init__()
в следующий код:
def __init__(self):
self.reset()
self.fed = []
... чтобы он выглядел следующим образом:
def __init__(self):
super().__init__()
self.reset()
self.fed = []
... и он будет работать для Python 3.2.3.
Опять же, благодаря Thomas K для исправления и исходного кода Eloff, приведенного выше!
Ответ 13
Решения с HTML-Parser все разрываются, если они запускаются только один раз:
html_to_text('<<b>script>alert("hacked")<</b>/script>
приводит к:
<script>alert("hacked")</script>
то, что вы намерены предотвратить. если вы используете HTML-Parser, считайте теги до тех пор, пока не будут заменены ноль:
from HTMLParser import HTMLParser
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.fed = []
self.containstags = False
def handle_starttag(self, tag, attrs):
self.containstags = True
def handle_data(self, d):
self.fed.append(d)
def has_tags(self):
return self.containstags
def get_data(self):
return ''.join(self.fed)
def strip_tags(html):
must_filtered = True
while ( must_filtered ):
s = MLStripper()
s.feed(html)
html = s.get_data()
must_filtered = s.has_tags()
return html
Ответ 14
Это быстрое исправление и может быть еще более оптимизировано, но оно будет работать нормально. Этот код заменит все непустые теги на "" и разделит все теги html на определенный входной текст. Вы можете запустить его с использованием входного ввода. /file.py
#!/usr/bin/python
import sys
def replace(strng,replaceText):
rpl = 0
while rpl > -1:
rpl = strng.find(replaceText)
if rpl != -1:
strng = strng[0:rpl] + strng[rpl + len(replaceText):]
return strng
lessThanPos = -1
count = 0
listOf = []
try:
#write File
writeto = open(sys.argv[2],'w')
#read file and store it in list
f = open(sys.argv[1],'r')
for readLine in f.readlines():
listOf.append(readLine)
f.close()
#remove all tags
for line in listOf:
count = 0;
lessThanPos = -1
lineTemp = line
for char in lineTemp:
if char == "<":
lessThanPos = count
if char == ">":
if lessThanPos > -1:
if line[lessThanPos:count + 1] != '<>':
lineTemp = replace(lineTemp,line[lessThanPos:count + 1])
lessThanPos = -1
count = count + 1
lineTemp = lineTemp.replace("<","<")
lineTemp = lineTemp.replace(">",">")
writeto.write(lineTemp)
writeto.close()
print "Write To --- >" , sys.argv[2]
except:
print "Help: invalid arguments or exception"
print "Usage : ",sys.argv[0]," inputfile outputfile"
Ответ 15
Адаптация python 3 søren-løvborg
from html.parser import HTMLParser
from html.entities import html5
class HTMLTextExtractor(HTMLParser):
""" Adaption of http://stackoverflow.com/a/7778368/196732 """
def __init__(self):
super().__init__()
self.result = []
def handle_data(self, d):
self.result.append(d)
def handle_charref(self, number):
codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number)
self.result.append(unichr(codepoint))
def handle_entityref(self, name):
if name in html5:
self.result.append(unichr(html5[name]))
def get_text(self):
return u''.join(self.result)
def html_to_text(html):
s = HTMLTextExtractor()
s.feed(html)
return s.get_text()
Ответ 16
Для одного проекта мне нужно было раздеть HTML, а также CSS и JS. Таким образом, я сделал вариант ответа Eloffs:
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
self.strict = False
self.convert_charrefs= True
self.fed = []
self.css = False
def handle_starttag(self, tag, attrs):
if tag == "style" or tag=="script":
self.css = True
def handle_endtag(self, tag):
if tag=="style" or tag=="script":
self.css=False
def handle_data(self, d):
if not self.css:
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)
def strip_tags(html):
s = MLStripper()
s.feed(html)
return s.get_data()
Ответ 17
Здесь решение, аналогичное принятому в настоящее время ответу (fooobar.com/questions/31753/...), за исключением того, что оно напрямую использует внутренний класс HTMLParser
(т.е. не HTMLParser
подклассы), тем самым делая его значительно более кратким:
def strip_html(text):
parts = []
parser = HTMLParser()
parser.handle_data = parts.append
parser.feed(text)
return ''.join(parts)
Ответ 18
Вы можете написать свою собственную функцию:
def StripTags(text):
finished = 0
while not finished:
finished = 1
start = text.find("<")
if start >= 0:
stop = text[start:].find(">")
if stop >= 0:
text = text[:start] + text[start+stop+1:]
finished = 0
return text
Ответ 19
Я разбираю Github readmes, и я считаю, что следующее действительно хорошо работает:
import re
import lxml.html
def strip_markdown(x):
links_sub = re.sub(r'\[(.+)\]\([^\)]+\)', r'\1', x)
bold_sub = re.sub(r'\*\*([^*]+)\*\*', r'\1', links_sub)
emph_sub = re.sub(r'\*([^*]+)\*', r'\1', bold_sub)
return emph_sub
def strip_html(x):
return lxml.html.fromstring(x).text_content() if x else ''
И затем
readme = """<img src="https://raw.githubusercontent.com/kootenpv/sky/master/resources/skylogo.png" />
sky is a web scraping framework, implemented with the latest python versions in mind (3.4+).
It uses the asynchronous `asyncio` framework, as well as many popular modules
and extensions.
Most importantly, it aims for **next generation** web crawling where machine intelligence
is used to speed up the development/maintainance/reliability of crawling.
It mainly does this by considering the user to be interested in content
from *domains*, not just a collection of *single pages*
([templating approach](#templating-approach))."""
strip_markdown(strip_html(readme))
Удаляет все уценки и html правильно.
Ответ 20
Используя BeautifulSoup, html2text или код из @Eloff, в большинстве случаев остаются некоторые элементы html, код javascript...
Таким образом, вы можете использовать комбинацию этих библиотек и удалить форматирование уценки (Python 3):
import re
import html2text
from bs4 import BeautifulSoup
def html2Text(html):
def removeMarkdown(text):
for current in ["^[ #*]{2,30}", "^[ ]{0,30}\d\\\.", "^[ ]{0,30}\d\."]:
markdown = re.compile(current, flags=re.MULTILINE)
text = markdown.sub(" ", text)
return text
def removeAngular(text):
angular = re.compile("[{][|].{2,40}[|][}]|[{][*].{2,40}[*][}]|[{][{].{2,40}[}][}]|\[\[.{2,40}\]\]")
text = angular.sub(" ", text)
return text
h = html2text.HTML2Text()
h.images_to_alt = True
h.ignore_links = True
h.ignore_emphasis = False
h.skip_internal_links = True
text = h.handle(html)
soup = BeautifulSoup(text, "html.parser")
text = soup.text
text = removeAngular(text)
text = removeMarkdown(text)
return text
Это хорошо работает для меня, но может быть улучшено, конечно...
Ответ 21
Вот мое решение для Python 3.
import html
import re
def html_to_txt(html_text):
## unescape html
txt = html.unescape(html_text)
tags = re.findall("<[^>]+>",txt)
print("found tags: ")
print(tags)
for tag in tags:
txt=txt.replace(tag,'')
return txt
Не уверен, что это идеально, но решил мой вариант использования и кажется простым.
Ответ 22
Простой код! Это удалит все виды тегов и содержимого внутри него.
def rm(s):
start=False
end=False
s=' '+s
for i in range(len(s)-1):
if i<len(s):
if start!=False:
if s[i]=='>':
end=i
s=s[:start]+s[end+1:]
start=end=False
else:
if s[i]=='<':
start=i
if s.count('<')>0:
self.rm(s)
else:
s=s.replace(' ', ' ')
return s
Но он не даст полного результата, если текст содержит символы <>.
Ответ 23
hext
это пакет, который помимо всего прочего может лишить HTML. Это альтернатива beautifulsoup
. Следующее было протестировано с hext==0.2.3
.
Сохраните это в служебном модуле, например, util/hext.py
:
import hext
_HTML_TEXT_RULE = hext.Rule('<html @text:text />')
def html_to_text(text: str) -> str:
# Ref: https://stackoverflow.com/a/56894409/
return _HTML_TEXT_RULE.extract(hext.Html(f'<html>{text}</html>'))[0]['text']
Примеры использования:
>>> from .util.hext import html_to_text
>>> html_to_text('<b>Hello world!</b>')
'Hello world!'
>>> html_to_text('<a href="google.com">some text</a>')
'some text'
>>> html_to_text('<span class="small-caps">l</span>-arginine minimizes immunosuppression and prothrombin time and enhances the genotoxicity of 5-fluorouracil in rats')
'l-arginine minimizes immunosuppression and prothrombin time and enhances the genotoxicity of 5-fluorouracil in rats'
>>> html_to_text('Attenuation of diabetic nephropathy by dietary fenugreek (<em>Trigonella foenum-graecum</em>) seeds and onion (<em>Allium cepa</em>) <em>via</em> suppression of glucose transporters and renin-angiotensin system')
'Attenuation of diabetic nephropathy by dietary fenugreek (Trigonella foenum-graecum) seeds and onion (Allium cepa) via suppression of glucose transporters and renin-angiotensin system'
Примеры использования с искаженным HTML:
>>> html_to_text('<b>Hello <i>world!')
'Hello world!'
>>> html_to_text('<a href="google.com">some <faketag>text')
'some text'
Ответ 24
Этот метод работает безупречно для меня и не требует дополнительных установок:
import re
import htmlentitydefs
def convertentity(m):
if m.group(1)=='#':
try:
return unichr(int(m.group(2)))
except ValueError:
return '&#%s;' % m.group(2)
try:
return htmlentitydefs.entitydefs[m.group(2)]
except KeyError:
return '&%s;' % m.group(2)
def converthtml(s):
return re.sub(r'&(#?)(.+?);',convertentity,s)
html = converthtml(html)
html.replace(" ", " ") ## Get rid of the remnants of certain formatting(subscript,superscript,etc).