Убрать HTML из строк в Python - PullRequest
241 голосов
/ 15 апреля 2009
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>' напечатает «привет» и т. Д. Как можно поступить так?

Ответы [ 23 ]

386 голосов
/ 29 мая 2009

Я всегда использовал эту функцию для удаления тегов 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

138 голосов
/ 02 февраля 2011

Я не особо задумывался о случаях, которые он пропустит, но вы можете сделать простое регулярное выражение:

re.sub('<[^<]+?>', '', text)

Для тех, кто не понимает регулярное выражение, ищет строку <...>, где внутреннее содержимое состоит из одного или нескольких (+) символов, которые не являются <. ? означает, что он будет соответствовать самой маленькой строке, которую он может найти. Например, если задано <p>Hello</p>, оно будет совпадать <'p> и </p> отдельно с ?. Без этого он будет соответствовать всей строке <..Hello..>.

Если в html появляется не тег < (например, 2 < 3), его следует записать как escape-последовательность &..., так что ^< может оказаться ненужным.

41 голосов
/ 30 декабря 2015

Почему вы все делаете это нелегко? Вы можете использовать функцию 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)
29 голосов
/ 01 ноября 2013

Короткая версия!

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's 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's strip_tags и другие html-утилиты из последней версии хороши, но я считаю их менее удобными, чем MarkupSafe. Они довольно автономны, вы можете скопировать все, что вам нужно, из этого файла .

Если вам нужно удалить почти всех тегов, библиотека Bleach хороша. Вы можете заставить его применять правила, такие как «мои пользователи могут выделять курсивом, но они не могут создавать фреймы».

Поймите свойства вашего стриптизерши! Запустите пушистые тесты на нем! Вот код Я использовал для исследования этого ответа.

зову примечание - Сам вопрос касается печати на консоль, но это лучший результат Google для "python strip html from string", поэтому этот ответ на 99% относится к сети.

28 голосов
/ 15 октября 2011

Мне нужен был способ вырезать теги и , чтобы декодировать HTML-объекты в обычный текст. Следующее решение основано на ответе Элоффа (который я не смог использовать, потому что он удаляет сущности).

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>(&not; \u0394&#x03b7;&#956;&#x03CE;)</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 (например, в некоторых системах символы за пределами Basic Multilingual Plane ) вызовут исключение ValueError.

Примечание по безопасности: Не путайте разметку HTML (преобразование HTML в простой текст) с очисткой HTML (преобразование обычного текста в HTML). Этот ответ удалит HTML и расшифрует объекты в обычный текст, что не делает результат безопасным для использования в контексте HTML.

Пример: &lt;script&gt;alert("Hello");&lt;/script&gt; будет преобразован в <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>(&not; \u0394&#x03b7;&#956;&#x03CE;)</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('&lt;script&gt;alert("Hello");&lt;/script&gt;')
    '<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 &lt z <!--b')
    'x < y < z '

    Unrecognized named entities are included as-is. '&apos;' is recognized,
    despite being XML only.
    >>> html_to_text('&nosuchentity; &apos; ')
    "&nosuchentity; ' "
    """
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

Обратите внимание, что HTMLParser улучшился в Python 3 (что означает меньше кода и лучшую обработку ошибок).

18 голосов
/ 22 января 2013

Есть простой способ:

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. Это бесплатно!

Добро пожаловать! :)

16 голосов
/ 04 декабря 2012

Если вам нужно сохранить сущности HTML (т.е. &amp;), я добавил метод handle_entityref к ответу Элоффа .

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()
12 голосов
/ 30 января 2013

Если вы хотите удалить все теги 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 голосов
/ 26 февраля 2017

Решение на основе lxml.html (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)
7 голосов
/ 28 мая 2017

Пакет Beautiful Soup сделает это немедленно для вас.

from bs4 import BeautifulSoup

soup = BeautifulSoup(html)
text = soup.get_text()
print(text)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...