it-roy-ru.com

Убрать 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>' печатает «привет» и т.д. Как можно поступить так?

237
directedition

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

378
Eloff

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

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

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

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

132
mmmdreg

Почему все вы делаете это нелегко? Вы можете использовать функцию 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)
33
Aminah Nuraini

Мне нужен был способ разделить теги и, чтобы декодировать 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 (что означает меньше кода и лучшую обработку ошибок).

27
Søren Løvborg

Укороченная версия!

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> вещей, не оставляя is плавающим вокруг. Но это другой способ принять произвольный вклад и сделать его совершенно безвредным. Большинство методов на этой странице оставят нетронутыми такие вещи, как незакрытые комментарии (<!--) и угловые скобки, которые не являются частью тегов (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 и выше) , Мако, Пилоны и многое другое. Он легко работает с шаблонами Django из Django 1.7.

Django strip_tags и другие html-утилиты из последней версии хороши, но я считаю их менее удобными, чем MarkupSafe. Они довольно автономны, вы можете скопировать то, что вам нужно, из этот файл .

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

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

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

27
rescdsk

Есть простой способ сделать это:

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

Пожалуйста! :)

18
Medeiros

Если вам нужно сохранить 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()
16
Robert

Если вы хотите удалить все 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: превышена максимальная глубина рекурсии», чего не произошло с вышеуказанным блоком кода.

12
Vasilis

Решение на основе 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)
9
ccpizza

Пакет «Красивый суп» делает это немедленно для вас. 

from bs4 import BeautifulSoup

soup = BeautifulSoup(html)
text = soup.get_text()
print(text)
6
runawaykid

Вы можете использовать другой HTML-анализатор ( например, lxml или Beautiful Soup ) - тот, который предлагает функции для извлечения только текста. Или вы можете запустить регулярное выражение в строке строки, которая удаляет теги. Смотрите http://www.amk.ca/python/howto/regex/ для получения дополнительной информации.

2
Jason Coon

Для одного проекта мне нужно было раздеть 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()
1
mousetail

Python 3 адаптация ответа Серена-Левборга

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()
1
CpILL

Все решения с HTML-парсером можно взломать, если они запускаются только один раз:

html_to_text('<<b>script>alert("hacked")<</b>/script>

результаты в:

<script>alert("hacked")</script>

что вы намереваетесь предотвратить. если вы используете HTML-парсер, считайте теги до замены нуля:

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
1
Falk Nisius

Это быстрое решение, которое может быть еще более оптимизировано, но оно будет работать нормально. Этот код заменит все непустые теги на «» и удалит все 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("&lt","<")
        lineTemp = lineTemp.replace("&gt",">")                  
        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"
1
kiran Mohan

Я успешно использовал ответ Элоффа для 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.

Еще раз спасибо Томасу К за исправление и за оригинальный код Элоффа, представленный выше!

1
MilesNielsen

Вот решение, аналогичное принятому в настоящее время ответу ( https://stackoverflow.com/a/925630/95989 ), за исключением того, что оно напрямую использует внутренний класс HTMLParser (т.е. не имеет подклассов), тем самым делая его значительно более кратким :

 def strip_html (text): 
 части = [] 
 parser = HTMLParser () 
 parser.handle_data = parts.append 
 parser.feed (текст) 
 return '' .join (parts) 
1
Richard

Вы можете написать свою собственную функцию:

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
0
Yuda Prawira

Вот мое решение для 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

Не уверен, что это идеально, но решил мой вариант использования и кажется простым. 

0
John Loutzenhiser

Простой код! Это удалит все виды тегов и содержимого внутри него.

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('&nbsp;', ' ')
        return s

Но он не даст полного результата, если текст содержит символы <> .

0
Vanjith

Я анализирую 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 правильно. 

0
PascalVKooten

Используя 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

Это хорошо работает для меня, но может быть улучшено, конечно ...

0
hayj