¿Cómo realizo la decodificación / codificación HTML usando Python / Django?

127

Tengo una cadena que está codificada en HTML:

'''<img class="size-medium wp-image-113"\
 style="margin-left: 15px;" title="su1"\
 src="http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg"\
 alt="" width="300" height="194" />'''

Quiero cambiar eso a:

<img class="size-medium wp-image-113" style="margin-left: 15px;" 
  title="su1" src="http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg" 
  alt="" width="300" height="194" /> 

Quiero que esto se registre como HTML para que el navegador lo represente como una imagen en lugar de mostrarlo como texto.

La cadena se almacena así porque estoy usando una herramienta de raspado web llamada BeautifulSoup, "escanea" una página web y obtiene cierto contenido de ella, luego devuelve la cadena en ese formato.

He encontrado cómo hacer esto en C # pero no en Python . ¿Alguien me puede ayudar?

Relacionado

rksprst
fuente

Respuestas:

118

Dado el caso de uso de Django, hay dos respuestas a esto. Aquí está su django.utils.html.escapefunción, para referencia:

def escape(html):
    """Returns the given HTML with ampersands, quotes and carets encoded."""
    return mark_safe(force_unicode(html).replace('&', '&amp;').replace('<', '&l
t;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))

Para revertir esto, la función Cheetah descrita en la respuesta de Jake debería funcionar, pero le falta la comilla simple. Esta versión incluye una tupla actualizada, con el orden de reemplazo invertido para evitar problemas simétricos:

def html_decode(s):
    """
    Returns the ASCII decoded version of the given HTML string. This does
    NOT remove normal HTML tags like <p>.
    """
    htmlCodes = (
            ("'", '&#39;'),
            ('"', '&quot;'),
            ('>', '&gt;'),
            ('<', '&lt;'),
            ('&', '&amp;')
        )
    for code in htmlCodes:
        s = s.replace(code[1], code[0])
    return s

unescaped = html_decode(my_string)

Esto, sin embargo, no es una solución general; solo es apropiado para cadenas codificadas con django.utils.html.escape. En términos más generales, es una buena idea seguir con la biblioteca estándar:

# Python 2.x:
import HTMLParser
html_parser = HTMLParser.HTMLParser()
unescaped = html_parser.unescape(my_string)

# Python 3.x:
import html.parser
html_parser = html.parser.HTMLParser()
unescaped = html_parser.unescape(my_string)

# >= Python 3.5:
from html import unescape
unescaped = unescape(my_string)

Como sugerencia: puede tener más sentido almacenar el HTML sin escape en su base de datos. Merecería la pena estudiar para obtener resultados sin escape de BeautifulSoup, si es posible, y evitar este proceso por completo.

Con Django, el escape solo ocurre durante la representación de la plantilla; así que para evitar escapar, simplemente dile al motor de plantillas que no escape de tu cadena. Para hacer eso, use una de estas opciones en su plantilla:

{{ context_var|safe }}
{% autoescape off %}
    {{ context_var }}
{% endautoescape %}
Daniel Naab
fuente
1
¿Por qué no usar Django o Cheetah?
Mat
44
¿No hay opuesto a django.utils.html.escape?
Mat
12
Creo que el escape solo ocurre en Django durante el renderizado de plantillas. Por lo tanto, no hay necesidad de escapar; simplemente le dice al motor de plantillas que no escape. ya sea {{context_var | safe}} o {% autoescape off%} {{context_var}} {% endautoescape%}
Daniel Naab
3
@Daniel: ¡Cambie su comentario a una respuesta para que pueda votar! seguro fue exactamente lo que yo (y estoy seguro de que otros) estaba buscando en respuesta a esta pregunta.
Wayne Koorts
1
html.parser.HTMLParser().unescape()está en desuso en 3.5. Usar en su html.unescape()lugar.
pjvandehaar
114

Con la biblioteca estándar:

  • HTML Escape

    try:
        from html import escape  # python 3.x
    except ImportError:
        from cgi import escape  # python 2.x
    
    print(escape("<"))
    
  • HTML Unescape

    try:
        from html import unescape  # python 3.4+
    except ImportError:
        try:
            from html.parser import HTMLParser  # python 3.x (<3.4)
        except ImportError:
            from HTMLParser import HTMLParser  # python 2.x
        unescape = HTMLParser().unescape
    
    print(unescape("&gt;"))
    
Jiangge Zhang
fuente
12
Creo que esta es la respuesta correcta más sencilla, 'batería incluida'. No sé por qué la gente vota esa cosa de Django / Cheetah.
Daniel Baktiar
Creo que también, excepto que esta respuesta no parece ser completa. HTMLParsernecesita subclasificar, decir qué hacer con todas las partes de cualquier objeto que se alimente y luego alimentar el objeto a analizar, como se ve aquí . Además, aún querrá usar el name2codepointdict para convertir cada identidad html al carácter real que representa.
Marconius
Tienes razón. Lo no subclasificado HTMLParserno podría funcionar como quisiéramos si le pusiéramos una entidad HTML. Tal vez debería cambiarle el nombre htmlparserpara _htmlparserocultarlo, y solo exponer el unescapemétodo para que sea como una función auxiliar.
Jiangge Zhang
3
Una nota para el año 2015, HTMLParser.unescape está en desuso en py 3.4 y se elimina en 3.5. utilizar from html import unescapeen su lugar
Karolis Ryselis
2
Tenga en cuenta que esto no maneja caracteres especiales como Umlauts alemanes ("Ü")
576i
80

Para la codificación html, hay cgi.escape de la biblioteca estándar:

>> help(cgi.escape)
cgi.escape = escape(s, quote=None)
    Replace special characters "&", "<" and ">" to HTML-safe sequences.
    If the optional flag quote is true, the quotation mark character (")
    is also translated.

Para la decodificación html, uso lo siguiente:

import re
from htmlentitydefs import name2codepoint
# for some reason, python 2.5.2 doesn't have this one (apostrophe)
name2codepoint['#39'] = 39

def unescape(s):
    "unescape HTML code refs; c.f. http://wiki.python.org/moin/EscapingHtml"
    return re.sub('&(%s);' % '|'.join(name2codepoint),
              lambda m: unichr(name2codepoint[m.group(1)]), s)

Para cualquier cosa más complicada, uso BeautifulSoup.

usuario26294
fuente
20

Use la solución de daniel si el conjunto de caracteres codificados está relativamente restringido. De lo contrario, use una de las numerosas bibliotecas de análisis HTML.

Me gusta BeautifulSoup porque puede manejar XML / HTML con formato incorrecto:

http://www.crummy.com/software/BeautifulSoup/

para su pregunta, hay un ejemplo en su documentación

from BeautifulSoup import BeautifulStoneSoup
BeautifulStoneSoup("Sacr&eacute; bl&#101;u!", 
                   convertEntities=BeautifulStoneSoup.HTML_ENTITIES).contents[0]
# u'Sacr\xe9 bleu!'
vincent
fuente
BeautifulSoup no convierte entidades hexadecimales (& # x65;) stackoverflow.com/questions/57708/…
jfs el
1
Para BeautifulSoup4, el equivalente sería:from bs4 import BeautifulSoup BeautifulSoup("Sacr&eacute; bl&#101;u!").contents[0]
Radicand
10

En Python 3.4+:

import html

html.unescape(your_string)
Collin Anderson
fuente
6

El comentario de Daniel como respuesta:

"el escape solo ocurre en Django durante la representación de la plantilla. Por lo tanto, no hay necesidad de un escape; simplemente le dice al motor de plantillas que no escape. {{context_var | safe}} o {% autoescape off%} {{context_var}} { % endautoescape%} "

dfrankow
fuente
Funciona, excepto que mi versión de Django no tiene 'seguro'. Yo uso 'escape' en su lugar. Supongo que es lo mismo.
willem
1
@willem: son lo contrario!
Asherah
5

Encontré una buena función en: http://snippets.dzone.com/posts/show/4569

def decodeHtmlentities(string):
    import re
    entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});")

    def substitute_entity(match):
        from htmlentitydefs import name2codepoint as n2cp
        ent = match.group(2)
        if match.group(1) == "#":
            return unichr(int(ent))
        else:
            cp = n2cp.get(ent)

            if cp:
                return unichr(cp)
            else:
                return match.group()

    return entity_re.subn(substitute_entity, string)[0]
slowkvant
fuente
El beneficio de usar re es que puedes combinar ambos & # 039; y & # 39; usando la misma búsqueda.
Neal Stublen
Esto no maneja lo &#xA0;que debería decodificar a la misma cosa que &#160;y &nbsp;.
Mike Samuel
3

Si alguien está buscando una manera simple de hacerlo a través de las plantillas de django, siempre puede usar filtros como este:

<html>
{{ node.description|safe }}
</html>

Recibí algunos datos de un proveedor y todo lo que publiqué tenía etiquetas html realmente escritas en la página representada como si estuviera mirando la fuente. El código anterior me ayudó mucho. Espero que esto ayude a otros.

¡¡Salud!!

Chris Harty
fuente
3

Aunque esta es una pregunta muy antigua, puede funcionar.

Django 1.5.5

In [1]: from django.utils.text import unescape_entities
In [2]: unescape_entities('&lt;img class=&quot;size-medium wp-image-113&quot; style=&quot;margin-left: 15px;&quot; title=&quot;su1&quot; src=&quot;http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg&quot; alt=&quot;&quot; width=&quot;300&quot; height=&quot;194&quot; /&gt;')
Out[2]: u'<img class="size-medium wp-image-113" style="margin-left: 15px;" title="su1" src="http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg" alt="" width="300" height="194" />'
James
fuente
1
Este fue el único que fue capaz de decodificar pares sustitutos codificados como entidades html, como "&#55349;&#56996;". Luego, después de otro result.encode('utf-16', 'surrogatepass').decode('utf-16'), finalmente tuve la espalda original.
rescdsk
1

Encontré esto en el código fuente de Cheetah ( aquí )

htmlCodes = [
    ['&', '&amp;'],
    ['<', '&lt;'],
    ['>', '&gt;'],
    ['"', '&quot;'],
]
htmlCodesReversed = htmlCodes[:]
htmlCodesReversed.reverse()
def htmlDecode(s, codes=htmlCodesReversed):
    """ Returns the ASCII decoded version of the given HTML string. This does
        NOT remove normal HTML tags like <p>. It is the inverse of htmlEncode()."""
    for code in codes:
        s = s.replace(code[1], code[0])
    return s

No estoy seguro de por qué invierten la lista, creo que tiene que ver con la forma en que codifican, por lo que con usted puede que no sea necesario revertirla. Además, si fuera usted, cambiaría htmlCodes para que sea una lista de tuplas en lugar de una lista de listas ... aunque esto va en mi biblioteca :)

Noté que tu título también pedía codificación, así que aquí está la función de codificación de Cheetah.

def htmlEncode(s, codes=htmlCodes):
    """ Returns the HTML encoded version of the given string. This is useful to
        display a plain ASCII text string on a web page."""
    for code in codes:
        s = s.replace(code[0], code[1])
    return s
Jake
fuente
2
La lista se invierte porque los reemplazos de decodificación y codificación siempre deben realizarse simétricamente. Sin la inversión, por ejemplo. convertir '& amp; lt;' a '& lt;', luego, en el siguiente paso, convierta incorrectamente eso a '<'.
bobince
1

También puedes usar django.utils.html.escape

from django.utils.html import escape

something_nice = escape(request.POST['something_naughty'])
Seth Gottlieb
fuente
OP preguntó por no escapar, no escapar.
Claymation
En el título, itsellf, también solicitó la codificación: acaba de encontrar su respuesta y se lo agradezco.
Simon Steinberger
1
No es lo que pidió el OP, pero esto me pareció útil.
rectangletangle
0

A continuación se muestra una función de Python que usa el módulo htmlentitydefs. No es perfecto La versión htmlentitydefsque tengo es incompleta y supone que todas las entidades decodifican en un punto de código, lo cual es incorrecto para entidades como &NotEqualTilde;:

http://www.w3.org/TR/html5/named-character-references.html

NotEqualTilde;     U+02242 U+00338    ≂̸

Sin embargo, con esas advertencias, aquí está el código.

def decodeHtmlText(html):
    """
    Given a string of HTML that would parse to a single text node,
    return the text value of that node.
    """
    # Fast path for common case.
    if html.find("&") < 0: return html
    return re.sub(
        '&(?:#(?:x([0-9A-Fa-f]+)|([0-9]+))|([a-zA-Z0-9]+));',
        _decode_html_entity,
        html)

def _decode_html_entity(match):
    """
    Regex replacer that expects hex digits in group 1, or
    decimal digits in group 2, or a named entity in group 3.
    """
    hex_digits = match.group(1)  # '&#10;' -> unichr(10)
    if hex_digits: return unichr(int(hex_digits, 16))
    decimal_digits = match.group(2)  # '&#x10;' -> unichr(0x10)
    if decimal_digits: return unichr(int(decimal_digits, 10))
    name = match.group(3)  # name is 'lt' when '&lt;' was matched.
    if name:
        decoding = (htmlentitydefs.name2codepoint.get(name)
            # Treat &GT; like &gt;.
            # This is wrong for &Gt; and &Lt; which HTML5 adopted from MathML.
            # If htmlentitydefs included mappings for those entities,
            # then this code will magically work.
            or htmlentitydefs.name2codepoint.get(name.lower()))
        if decoding is not None: return unichr(decoding)
    return match.group(0)  # Treat "&noSuchEntity;" as "&noSuchEntity;"
Mike Samuel
fuente
0

Esta es la solución más fácil para este problema:

{% autoescape on %}
   {{ body }}
{% endautoescape %}

De esta página .

smilitude
fuente
0

Al buscar la solución más simple de esta pregunta en Django y Python, descubrí que puedes usar sus funciones incorporadas para escapar / escapar del código html.

Ejemplo

Guarde su código html scraped_htmly clean_html:

scraped_html = (
    '&lt;img class=&quot;size-medium wp-image-113&quot; '
    'style=&quot;margin-left: 15px;&quot; title=&quot;su1&quot; '
    'src=&quot;http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg&quot; '
    'alt=&quot;&quot; width=&quot;300&quot; height=&quot;194&quot; /&gt;'
)
clean_html = (
    '<img class="size-medium wp-image-113" style="margin-left: 15px;" '
    'title="su1" src="http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg" '
    'alt="" width="300" height="194" />'
)

Django

Necesitas Django> = 1.0

escapar

Para deshacerse de su código html raspado, puede usar django.utils.text.unescape_entities que:

Convierta todas las referencias de caracteres nombrados y numéricos a los caracteres unicode correspondientes.

>>> from django.utils.text import unescape_entities
>>> clean_html == unescape_entities(scraped_html)
True

escapar

Para escapar de su código html limpio, puede usar django.utils.html.escape que:

Devuelve el texto dado con signos de unión, comillas y corchetes codificados para su uso en HTML.

>>> from django.utils.html import escape
>>> scraped_html == escape(clean_html)
True

Pitón

Necesitas Python> = 3.4

escapar

Para deshacerse de su código html raspado, puede usar html.unescape que:

Convierta todas las referencias de caracteres nombrados y numéricos (p &gt;. Ej . &#62;, &x3e;) En la cadena s a los caracteres unicode correspondientes.

>>> from html import unescape
>>> clean_html == unescape(scraped_html)
True

escapar

Para escapar de su código html limpio, puede usar html.escape que:

Convertir los personajes &, <y >en s de cuerda a secuencias HTML seguras.

>>> from html import escape
>>> scraped_html == escape(clean_html)
True
Paolo Melchiorre
fuente