Jak wykonać dekodowanie / kodowanie HTML za pomocą Python / Django?

131

Mam ciąg zakodowany w formacie 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" />'''

Chcę to zmienić na:

<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" /> 

Chcę, aby został zarejestrowany jako HTML, aby był renderowany jako obraz przez przeglądarkę zamiast być wyświetlany jako tekst.

Ciąg jest przechowywany w ten sposób, ponieważ używam narzędzia do skrobania stron internetowych o nazwie BeautifulSoup, które „skanuje” stronę internetową i pobiera z niej określoną zawartość, a następnie zwraca ciąg w tym formacie.

Znalazłem, jak to zrobić w C #, ale nie w Pythonie . Czy ktoś może mi pomóc?

Związane z

rksprst
źródło

Odpowiedzi:

123

Biorąc pod uwagę przypadek użycia Django, są na to dwie odpowiedzi. Oto jego django.utils.html.escapefunkcja w celach informacyjnych:

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

Aby to odwrócić, funkcja Cheetah opisana w odpowiedzi Jake'a powinna działać, ale brakuje pojedynczego cudzysłowu. Ta wersja zawiera zaktualizowaną krotkę z odwróconą kolejnością zastępowania, aby uniknąć problemów z symetrią:

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)

Nie jest to jednak rozwiązanie ogólne; jest odpowiedni tylko dla łańcuchów zakodowanych za pomocą django.utils.html.escape. Mówiąc bardziej ogólnie, dobrym pomysłem jest pozostanie przy standardowej bibliotece:

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

Jako sugestia: bardziej sensowne może być przechowywanie kodu HTML bez zmiany znaczenia w bazie danych. Warto byłoby przyjrzeć się, jeśli to możliwe, odzyskać niezakłócone wyniki z BeautifulSoup i całkowicie uniknąć tego procesu.

W Django ucieczka występuje tylko podczas renderowania szablonu; aby zapobiec ucieczce, po prostu powiedz silnikowi szablonów, aby nie uciekał ze struny. Aby to zrobić, użyj jednej z następujących opcji w swoim szablonie:

{{ context_var|safe }}
{% autoescape off %}
    {{ context_var }}
{% endautoescape %}
Daniel Naab
źródło
1
Dlaczego nie użyć Django lub Cheetah?
Mat
4
Czy nie ma przeciwieństwa django.utils.html.escape?
Mat
12
Myślę, że ucieczka występuje tylko w Django podczas renderowania szablonu. Dlatego nie ma potrzeby ucieczki - wystarczy powiedzieć silnikowi szablonów, aby nie uciekał. albo {{context_var | safe}} albo {% autoescape off%} {{context_var}} {% endautoescape%}
Daniel Naab
3
@Daniel: Zmień swój komentarz na odpowiedź, abym mógł zagłosować! | bezpieczny był dokładnie tym, czego ja (i jestem pewien, że inni) szukałem w odpowiedzi na to pytanie.
Wayne Koorts
2
html.parser.HTMLParser().unescape()jest przestarzałe w 3.5. Użyj html.unescape()zamiast tego.
pjvandehaar
116

Z biblioteką standardową:

  • Ucieczka HTML

    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
źródło
12
Myślę, że jest to najprostsza, „w zestawie bateria” i poprawna odpowiedź. Nie wiem, dlaczego ludzie głosują na te Django / Cheetah.
Daniel Baktiar,
Ja też tak uważam, z tym że ta odpowiedź nie wydaje się być kompletna. HTMLParsermusi zostać podzielona na podklasy, powiedzieć, co zrobić ze wszystkimi częściami dowolnego obiektu, do którego jest podawany, a następnie podać obiekt do przeanalizowania, jak widać tutaj . Ponadto nadal będziesz chciał używać name2codepointdykt do konwersji każdej tożsamości HTML na rzeczywisty znak, który reprezentuje.
Marconius
Masz rację. Niesubklasa HTMLParsernie mogłaby działać tak, jak chcieliśmy, gdybyśmy umieścili w niej encję HTML. Może powinienem zmienić nazwę htmlparserna _htmlparser, aby to ukryć, i ujawnić unescapemetodę tylko jako funkcję pomocniczą.
Jiangge Zhang,
3
Uwaga za rok 2015, HTMLParser.unescape jest przestarzały w py 3.4 i usunięty w 3.5. użyj from html import unescapezamiast tego
Karolis Ryselis
2
Zwróć uwagę, że to nie obsługuje znaków specjalnych, takich jak niemieckie umlauty („Ü”)
576i
80

Do kodowania html jest cgi.escape ze standardowej biblioteki:

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

Do dekodowania html używam:

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)

Do czegoś bardziej skomplikowanego używam BeautifulSoup.

user26294
źródło
20

Użyj rozwiązania Daniela, jeśli zestaw zakodowanych znaków jest stosunkowo ograniczony. W przeciwnym razie użyj jednej z wielu bibliotek analizujących HTML.

Podoba mi się BeautifulSoup, ponieważ może obsłużyć zniekształcony XML / HTML:

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

jeśli chodzi o twoje pytanie, w ich dokumentacji jest przykład

from BeautifulSoup import BeautifulStoneSoup
BeautifulStoneSoup("Sacr&eacute; bl&#101;u!", 
                   convertEntities=BeautifulStoneSoup.HTML_ENTITIES).contents[0]
# u'Sacr\xe9 bleu!'
Vincent
źródło
BeautifulSoup nie konwertuje encji szesnastkowych (& # x65;) stackoverflow.com/questions/57708/ ...
jfs
1
Dla BeautifulSoup4 odpowiednikiem byłoby:from bs4 import BeautifulSoup BeautifulSoup("Sacr&eacute; bl&#101;u!").contents[0]
radicand
11

W Pythonie 3.4+:

import html

html.unescape(your_string)
Collin Anderson
źródło
8

Zobacz na dole tej strony na wiki Pythona , są co najmniej 2 opcje dla "unescape" html.

zgoda
źródło
6

Komentarz Daniela jako odpowiedź:

"ucieczka występuje tylko w Django podczas renderowania szablonu. Dlatego nie ma potrzeby używania unescape - po prostu mówisz silnikowi szablonów, aby nie uciekał. albo {{context_var | safe}} albo {% autoescape off%} {{context_var}} { % endautoescape%} "

dfrankow
źródło
Działa, z wyjątkiem tego, że moja wersja Django nie ma „bezpiecznego”. Zamiast tego używam „escape”. Zakładam, że to to samo.
willem
1
@willem: są przeciwieństwem!
Asherah
5

Znalazłem fajną funkcję pod adresem : 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
źródło
Zaletą używania re jest to, że możesz dopasować oba & # 039; i & # 39; używając tego samego wyszukiwania.
Neal Stublen
To nie obsługuje tego, &#xA0;co powinno dekodować do tego samego, co &#160;i &nbsp;.
Mike Samuel
3

Jeśli ktoś szuka prostego sposobu na zrobienie tego za pomocą szablonów django, zawsze możesz użyć filtrów takich jak ten:

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

Miałem pewne dane pochodzące od dostawcy i wszystko, co opublikowałem, zawierało tagi HTML faktycznie zapisane na renderowanej stronie, tak jakbyś patrzył na źródło. Powyższy kod bardzo mi pomógł. Mam nadzieję, że to pomoże innym.

Twoje zdrowie!!

Chris Harty
źródło
3

Chociaż jest to naprawdę stare pytanie, może się udać.

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
źródło
1
Był to jedyny, który był w stanie zdekodować pary zastępcze zakodowane jako jednostki html, takie jak "&#55349;&#56996;". Potem w result.encode('utf-16', 'surrogatepass').decode('utf-16')końcu odzyskałem oryginał.
rescdsk
1

Znalazłem to w kodzie źródłowym Cheetah ( tutaj )

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

Nie jestem pewien, dlaczego odwracają listę, myślę, że ma to związek ze sposobem, w jaki kodują, więc z tobą może nie trzeba tego odwracać. Również na twoim miejscu zmieniłbym htmlCodes na listę krotek, a nie listę list ... to jednak dzieje się w mojej bibliotece :)

Zauważyłem, że twój tytuł również został poproszony o kodowanie, więc oto funkcja kodowania 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
źródło
2
Lista jest odwrócona, ponieważ zamiany dekodowania i kodowania zawsze muszą być wykonywane symetrycznie. Bez cofania mógłbyś np. konwertuj „& amp; lt;” na „& lt;”, a następnie w następnym kroku niepoprawnie przekonwertuj go na „<”.
bobince
1

Możesz także użyć django.utils.html.escape

from django.utils.html import escape

something_nice = escape(request.POST['something_naughty'])
Seth Gottlieb
źródło
OP zapytał o wycofanie się, a nie ucieczkę.
claymation
W tytule itsellf poprosił również o zakodowanie - właśnie znalazłem odpowiedź i jestem za nią wdzięczny.
Simon Steinberger,
1
Nie to, o co prosił OP, ale uznałem to za przydatne.
prostokątny
0

Poniżej znajduje się funkcja Pythona, która używa module htmlentitydefs. To nie jest doskonałe. Wersja htmlentitydefs, którą mam, jest niekompletna i zakłada, że ​​wszystkie jednostki dekodują do jednego punktu kodowego, co jest niewłaściwe dla jednostek takich jak &NotEqualTilde;:

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

NotEqualTilde;     U+02242 U+00338    ≂̸

Mając jednak te zastrzeżenia, oto kod.

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
źródło
0

To najłatwiejsze rozwiązanie tego problemu -

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

Z tej strony .

uśmiech
źródło
0

Szukając najprostszego rozwiązania tego pytania w Django i Pythonie, odkryłem, że możesz użyć wbudowanych ich funkcji do ucieczki / unescape kodu html.

Przykład

Zapisałem Twój kod html w scraped_htmli 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

Potrzebujesz Django> = 1.0

unescape

Aby uwolnić się od zeskrobanego kodu HTML, możesz użyć django.utils.text.unescape_entities, które:

Konwertuj wszystkie nazwane i numeryczne odwołania do znaków na odpowiednie znaki Unicode.

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

ucieczka

Aby uniknąć czystego kodu HTML, możesz użyć django.utils.html.escape, który:

Zwraca podany tekst z ampersandami, cudzysłowami i nawiasami ostrymi zakodowanymi do użycia w HTML.

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

Pyton

Potrzebujesz Pythona> = 3.4

unescape

Aby uwolnić się od zeskrobanego kodu HTML, możesz użyć html.unescape, który:

Konwersja wszystkich odniesień nazwany i numeryczny znaków (np &gt;, &#62;, &x3e;) w ciągu s do odpowiednich znaków Unicode.

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

ucieczka

Aby uniknąć czystego kodu HTML, możesz użyć html.escape, który:

Konwersja znaków &, <a >w łańcuchu s do HTML-safe sekwencji.

>>> from html import escape
>>> scraped_html == escape(clean_html)
True
Paolo Melchiorre
źródło