Eliminar HTML de cadenas en Python


from mechanize import Browser
br = Browser()
br.open('http://somewebpage')
html = br.response().readlines()
for line in html:
  print line

Al imprimir una línea en un archivo HTML, estoy tratando de encontrar una manera de mostrar solo el contenido de cada elemento HTML y no el formato en sí. Si encuentra '<a href="whatever.com">some text</a>', solo imprimirá 'algún texto', '<b>hello</b>' imprimirá 'hola', etc. ¿Cómo se podría hacer esto?

 226
Author: Peter Mortensen, 2009-04-15

20 answers

Siempre utilicé esta función para eliminar las etiquetas HTML, ya que solo requiere el Python stdlib:

En 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()

Para 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()

Nota: esto solo funciona para la versión 3.1. Para 3.2 o superior, necesita llamar a la función init de la clase padre. Ver Usando HTMLParser en Python 3.2

 366
Author: Community,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:02

No he pensado mucho en los casos que se perderá, pero puede hacer una expresión regular simple:

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

Para aquellos que no entienden regex, esto busca una cadena <...>, donde el contenido interno está hecho de uno o más caracteres (+) que no es un <. El ? significa que coincidirá con la cadena más pequeña que pueda encontrar. Por ejemplo, dado <p>Hello</p>, coincidirá <'p> y </p>, por separado, con el ?. Sin él, coincidirá con toda la cadena <..Hello..>.

Si no es etiqueta < aparece en html (eg. 2 < 3), debe escribirse como una secuencia de escape &... de todos modos, por lo que el ^< puede ser innecesario.

 127
Author: mmmdreg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-05 14:55:49

Versión Corta!

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)

Fuente de expresiones regulares: MarkupSafe. Su versión también maneja entidades HTML, mientras que esta rápida no lo hace.

¿Por qué no puedo simplemente quitar las etiquetas y dejarlas?

Una cosa es mantener a la gente alejada de <i>italicizing</i> las cosas, sin dejar a i flotando alrededor. Pero otra es tomar información arbitraria y hacerla completamente inofensiva. La mayoría de las técnicas en esta página dejarán cosas como comentarios sin cerrar (<!--) y corchetes angulares que no son parte de las etiquetas (blah <<<><blah) intactas. La versión HTMLParser puede incluso dejar etiquetas completas, si están dentro de un comentario sin cerrar.

¿Qué pasa si tu plantilla es {{ firstname }} {{ lastname }}? firstname = '<a' y lastname = 'href="http://evil.com/">' serán dejados pasar por cada etiqueta stripper en esta página (excepto @Medeiros!), porque no son etiquetas completas por sí solas. Eliminar las etiquetas HTML normales no es suficiente.

La strip_tags de Django, una versión mejorada (ver el siguiente encabezado) de la respuesta principal a esta pregunta, da lo siguiente advertencia:

No se proporciona ninguna garantía de que la cadena resultante sea segura para HTML. Así QUE NUNCA marque a salvo el resultado de una llamada strip_tags sin escaparlo primero, por ejemplo con escape().

Siga sus consejos!

Para eliminar etiquetas con HTMLParser, debe ejecutarlo varias veces.

Es fácil eludir la respuesta a esta pregunta.

Mira esta cadena (fuente y debate):

<img<!-- --> src=x onerror=alert(1);//><!-- -->

La primera vez que HTMLParser lo ve, no puede decir que <img...> es una etiqueta. Parece roto, por lo que HTMLParser no se deshace de él. Solo saca el <!-- comments -->, dejándote con

<img src=x onerror=alert(1);//>

Este problema fue revelado al proyecto Django en marzo de 2014. Su viejo strip_tags era esencialmente el mismo que la respuesta principal a esta pregunta. Su nueva versión básicamente lo ejecuta en un bucle hasta que ejecutarlo de nuevo no cambia la string:

# _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

Por supuesto, nada de esto es un problema si siempre se escapa el resultado de strip_tags().

Actualización del 19 de marzo de 2015 : Hubo un error en las versiones de Django anteriores a 1.4.20, 1.6.11, 1.7.7 y 1.8c1. Estas versiones podrían introducir un bucle infinito en la función strip_tags (). La versión fija se reproduce arriba. Más detalles aquí.

Cosas buenas para copiar o usar

Mi código de ejemplo no maneja entidades HTML - el Django y Las versiones empaquetadas de MarkupSafe sí.

Mi código de ejemplo se extrae de la excelente biblioteca MarkupSafe para la prevención de scripts entre sitios. Es conveniente y rápido (con aceleraciones de C a su versión nativa de Python). Está incluido en Google App Engine , y utilizado por Jinja2 (2.7 y arriba) , Mako, Pilones y más. Funciona fácilmente con las plantillas de Django de Django 1.7.

Strip_tags de Django y otras utilidades html de una versión reciente son buenas, pero las encuentro menos convenientes que MarkupSafe. Son bastante independientes, podrías copiar lo que necesitas de este archivo.

Si necesitas quitar casi todas las etiquetas, la biblioteca Bleach es buena. Puedes hacer que imponga reglas como " mis usuarios pueden poner las cosas en cursiva, pero no pueden hacer iframes."

Comprender las propiedades de su etiqueta stripper! ¡Hazle pruebas de pelusa! Aquí está el código que usé para hacer la investigación para esto respuesta.

sheepish note - La pregunta en sí es acerca de la impresión en la consola, pero este es el principal resultado de Google para "python strip html from string", por lo que esta respuesta es del 99% sobre la web.

 28
Author: rescdsk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-08 14:46:48

Necesitaba una forma de eliminar las etiquetas y decodificar entidades HTML a texto sin formato. La siguiente solución se basa en la respuesta de Eloff (que no pude usar porque elimina entidades).

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

Una prueba rápida:

html = u'<a href="#">Demo <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>'
print repr(html_to_text(html))

Resultado:

u'Demo (\xac \u0394\u03b7\u03bc\u03ce)'

Manejo de errores:

  • Una estructura HTML no válida puede causar un HTMLParseError.
  • Las entidades HTML con nombre no válido (como &#apos;, que es válido en XML y XHTML, pero no HTML simple) causarán un ValueError salvedad.
  • Las entidades HTML numéricas que especifican puntos de código fuera del rango Unicode aceptable por Python (como, en algunos sistemas, caracteres fuera del Plano Multilingüe Básico ) causarán una excepción ValueError.

Nota de seguridad: No confunda HTML stripping (conversión de HTML en texto plano) con HTML sanitizing (conversión de texto plano en HTML). Esta respuesta eliminará HTML y decodificará entidades en texto sin formato - eso no hace que el resultado seguro de usar en un contexto HTML.

Ejemplo: &lt;script&gt;alert("Hello");&lt;/script&gt; se convertirá a <script>alert("Hello");</script>, que es un comportamiento 100% correcto, pero obviamente no es suficiente si el texto sin formato resultante se inserta en una página HTML.

La regla no es difícil: Cada vez que inserte una cadena de texto sin formato en la salida HTML, debe siempre escapar HTML (usando cgi.escape(s, True)), incluso si "sabe" que no contiene HTML (por ejemplo, porque eliminó el contenido HTML).

(Sin embargo, el OP preguntó acerca de la impresión del resultado en la consola, en cuyo caso no se necesita ningún escape HTML.)

 26
Author: Søren Løvborg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-09 13:29:22

¿Por qué todos ustedes lo hacen de la manera difícil? Puede utilizar la función 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)
 22
Author: Aminah Nuraini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-08 16:31:12

Hay una manera sencilla de hacer esto:

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

La idea se explica aquí: http://youtu.be/2tu9LTDujbw

Puedes verlo trabajando aquí: http://youtu.be/HPkNPcYed9M?t=35s

PS-Si estás interesado en la clase (sobre depuración inteligente con python) te doy un enlace: http://www.udacity.com/overview/Course/cs259/CourseRev/1 . ¡Es gratis!

¡De nada! :)

 17
Author: Medeiros,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-22 17:27:41

Si necesita preservar entidades HTML (es decir, &amp;), agregué el método "handle_entityref" a La respuesta de 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()
 16
Author: Robert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:53

Si quieres eliminar todas las etiquetas HTML, la forma más fácil que encontré es usando 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)) 

Probé el código de la respuesta aceptada pero estaba obteniendo "RuntimeError: maximum recursion depth exceeded", lo que no sucedió con el bloque de código anterior.

 12
Author: Vasilis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-21 14:11:27

Un lxml.solución basada en html (lxml es una biblioteca nativa y por lo tanto mucho más rápida que cualquier solución python pura).

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

Véase también http://lxml.de/lxmlhtml.html#cleaning-up-html para qué exactamente el lxml.el limpiador sí.

Si necesita más control sobre lo que se desinfecta exactamente antes de convertir a texto, es posible que desee usar lxml Cleaner explícitamente pasando las opciones que desea en el constructor, por ejemplo:

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
Author: ccpizza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-11 06:33:34

El Hermoso paquete de Sopa hace esto inmediatamente por ti.

from bs4 import BeautifulSoup

soup = BeautifulSoup(html)
text = soup.get_text()
print(text)
 5
Author: runawaykid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-25 07:35:13

Puede usar un analizador HTML diferente ( como lxml, o Beautiful Soup) one uno que ofrezca funciones para extraer solo texto. O bien, puede ejecutar una expresión regular en su cadena de línea que elimina las etiquetas. Véase http://www.amk.ca/python/howto/regex / para más.

 2
Author: Jason Coon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-29 00:50:07

He utilizado la respuesta de Eloff con éxito para Python 3.1 [muchas gracias!].

Actualicé a Python 3.2.3, y me encontré con errores.

La solución, proporcionada aquí gracias al respondedor Thomas K, es insertar super().__init__() en el siguiente código:

def __init__(self):
    self.reset()
    self.fed = []

... para que se vea así:

def __init__(self):
    super().__init__()
    self.reset()
    self.fed = []

... y funcionará para Python 3.2.3.

Nuevamente, gracias a Thomas K por la corrección y por el código original de Eloff proporcionado anteriormente!

 1
Author: MilesNielsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:48

Las soluciones con HTML-Parser son todas rompibles, si se ejecutan solo una vez:

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

Resultados en:

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

Lo que pretendes prevenir. si usa un analizador HTML, cuente las etiquetas hasta que se reemplace el cero:

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
Author: user3232060,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-24 12:58:15

Esta es una solución rápida y se puede optimizar aún más, pero funcionará bien. Este código reemplazará todas las etiquetas no vacías con "" y eliminará todas las etiquetas html de un texto de entrada dado .Puedes ejecutarlo usando ./file.py entrada salida

    #!/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
Author: kiran Mohan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-07 03:51:25

Una adaptación de python 3 de la respuesta de 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()
 1
Author: CpILL,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-16 08:02:16

Para un proyecto, necesitaba eliminar HTML, pero también css y js. Por lo tanto, hice una variación de Eloffs respuesta:

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
Author: mousetail,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-15 12:52:41

Puedes escribir tu propia función:

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
Author: Yuda Prawira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-20 21:39:52

Estoy analizando los readmes de Github y encuentro que lo siguiente realmente funciona bien:

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 ''

Y luego

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))

Elimina todos los markdown y html correctamente.

 0
Author: PascalVKooten,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-02 10:10:52

Usando BeautifulSoup, html2text o el código de @Eloff, la mayoría de las veces, sigue siendo algunos elementos html, código javascript...

Así que puede usar una combinación de estas bibliotecas y eliminar el formato markdown (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

Funciona bien para mí, pero se puede mejorar, por supuesto...

 0
Author: hayj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-27 14:41:49

Este método funciona perfectamente para mí y no requiere instalaciones adicionales:

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("&nbsp;", " ") ## Get rid of the remnants of certain formatting(subscript,superscript,etc).
 -1
Author: John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-02 01:23:05