Как выполнить декодирование / кодирование HTML с помощью Python / Django?

128

У меня есть строка в кодировке 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" />'''

Я хочу изменить это на:

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

Я хочу, чтобы это было зарегистрировано как HTML, чтобы оно отображалось браузером как изображение, а не как текст.

Строка хранится таким образом, потому что я использую названный инструмент для очистки BeautifulSoupвеб-страниц, он «сканирует» веб-страницу и получает от нее определенный контент, а затем возвращает строку в этом формате.

Я нашел, как это сделать на C #, но не на Python . Кто-нибудь может мне помочь?

Связанный

rksprst
источник

Ответы:

118

Учитывая вариант использования Django, на этот вопрос есть два ответа. Вот его django.utils.html.escapeфункция для справки:

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

Чтобы изменить это, функция Cheetah, описанная в ответе Джейка, должна работать, но в ней отсутствует одинарная кавычка. Эта версия включает обновленный кортеж с обратным порядком замены, чтобы избежать симметричных проблем:

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)

Однако это не общее решение; он подходит только для строк, закодированных с помощью django.utils.html.escape. В более общем плане рекомендуется придерживаться стандартной библиотеки:

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

В качестве предложения: может иметь смысл хранить HTML без экранирования в вашей базе данных. Если возможно, стоит подумать о том, чтобы получить обратно неэкранированные результаты из BeautifulSoup, и вообще избежать этого процесса.

В Django экранирование происходит только во время отрисовки шаблона; поэтому, чтобы предотвратить побег, вы просто скажете движку шаблонов не экранировать вашу строку. Для этого используйте в своем шаблоне одну из следующих опций:

{{ context_var|safe }}
{% autoescape off %}
    {{ context_var }}
{% endautoescape %}
Даниэль Нааб
источник
1
Почему бы не использовать Django или Cheetah?
Мат,
4
Нет ли противоположности django.utils.html.escape?
Мат,
12
Я думаю, что экранирование происходит только в Django во время рендеринга шаблона. Следовательно, нет необходимости в отмене экранирования - вы просто говорите движку шаблонов, чтобы он не сбегал. либо {{context_var | safe}}, либо {% autoescape off%} {{context_var}} {% endautoescape%}
Дэниел Нааб
3
@ Дэниел: Пожалуйста, измените свой комментарий на ответ, чтобы я мог проголосовать за него! | safe было именно тем, что я (и я уверен, что другие) искал, отвечая на этот вопрос.
Уэйн Коортс,
1
html.parser.HTMLParser().unescape()устарел в 3.5. html.unescape()Вместо этого используйте .
pjvandehaar
114

Со стандартной библиотекой:

  • 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;"))
Цзянге Чжан
источник
12
Я думаю, что это наиболее простой и правильный ответ «с батареей». Я не знаю, почему люди голосуют за эту штуку с Django / Cheetah.
Daniel Baktiar
Я тоже так думаю, за исключением того, что этот ответ не кажется полным. HTMLParserнеобходимо разделить на подклассы, указать, что делать со всеми частями любого объекта, который он загружает, а затем передать объект для анализа, как показано здесь . Кроме того, вы все равно захотите использовать name2codepointdict для преобразования каждого идентификатора html в фактический символ, который он представляет.
Marconius 09
Ты прав. Неподклассы HTMLParserне могли работать так, как мы хотели, если бы мы поместили в них HTML-сущность. Может быть , я должен переименовать , htmlparserчтобы _htmlparserдля того , чтобы скрыть его, и только выставить unescapeметод , чтобы быть точно так же как вспомогательная функция.
Jiangge Zhang
3
Примечание для 2015 года: HTMLParser.unescape устарел в py 3.4 и удален в 3.5. использовать from html import unescapeвместо
Каролис Рыселис
2
Обратите внимание, что это не обрабатывает специальные символы, такие как немецкие умляуты ("Ü")
576i
80

Для кодировки html есть cgi.escape из стандартной библиотеки:

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

Для декодирования html я использую следующее:

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)

Для чего-то более сложного я использую BeautifulSoup.

user26294
источник
20

Используйте решение Дэниела, если набор закодированных символов относительно ограничен. В противном случае используйте одну из многочисленных библиотек анализа HTML.

Мне нравится BeautifulSoup, потому что он может обрабатывать искаженный XML / HTML:

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

для вашего вопроса в их документации есть пример

from BeautifulSoup import BeautifulStoneSoup
BeautifulStoneSoup("Sacr&eacute; bl&#101;u!", 
                   convertEntities=BeautifulStoneSoup.HTML_ENTITIES).contents[0]
# u'Sacr\xe9 bleu!'
Винсент
источник
BeautifulSoup не конвертирует шестнадцатеричные сущности (& # x65;) stackoverflow.com/questions/57708/…
jfs
1
Для BeautifulSoup4 эквивалент будет:from bs4 import BeautifulSoup BeautifulSoup("Sacr&eacute; bl&#101;u!").contents[0]
radicand
10

В Python 3.4+:

import html

html.unescape(your_string)
Коллин Андерсон
источник
6

Комментарий Даниила в качестве ответа:

"экранирование происходит только в Django во время рендеринга шаблона. Следовательно, в этом нет необходимости - вы просто указываете движку шаблонов не экранировать. либо {{context_var | safe}}, либо {% autoescape off%} {{context_var}} { % endautoescape%} "

dfrankow
источник
Работает, за исключением того, что в моей версии Django нет «безопасной». Вместо этого я использую «побег». Полагаю, это то же самое.
Виллем
1
@willem: все наоборот!
Ашера
5

Я нашел прекрасную функцию по адресу: 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
источник
Преимущество использования re заключается в том, что вы можете сопоставить оба & # 039; и & # 39; используя тот же поиск.
Нил Штублен
Это не обрабатывает то, &#xA0;что должно декодировать то же самое, что &#160;и &nbsp;.
Майк Сэмюэл
3

Если кто-то ищет простой способ сделать это с помощью шаблонов django, вы всегда можете использовать такие фильтры:

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

У меня были данные от поставщика, и все, что я опубликовал, содержало html-теги, фактически написанные на отображаемой странице, как если бы вы смотрели на источник. Приведенный выше код мне очень помог. Надеюсь, это поможет другим.

Ура !!

Крис Харти
источник
3

Хотя это действительно старый вопрос, он может сработать.

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" />'
Джеймс
источник
1
Это была единственная программа, которая могла декодировать суррогатные пары, закодированные как объекты html, например "&#55349;&#56996;". Затем, за другим result.encode('utf-16', 'surrogatepass').decode('utf-16'), я наконец получил обратно оригинал.
rescdsk 06
1

Я нашел это в исходном коде Cheetah ( здесь )

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

Не уверен, почему они меняют список, я думаю, это связано с тем, как они кодируют, поэтому с вами, возможно, нет необходимости в обратном. Также, если бы я был на вашем месте, я бы изменил htmlCodes на список кортежей, а не на список списков ... хотя это происходит в моей библиотеке :)

Я заметил, что ваш заголовок тоже просил кодировать, так что вот функция кодирования 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
Джейк
источник
2
Список перевернут, потому что замены декодирования и кодирования всегда должны выполняться симметрично. Без реверса вы могли бы, например. преобразовать '& amp; lt;' в '& lt;', а затем на следующем шаге неправильно преобразовать это в '<'.
bobince
1

Вы также можете использовать django.utils.html.escape

from django.utils.html import escape

something_nice = escape(request.POST['something_naughty'])
Сет Готтлиб
источник
ОП спрашивал о том, как не сбежать, а не о побеге.
пластилин
В названии он тоже просил кодировку - просто нашел ваш ответ и благодарен за него.
Саймон Штайнбергер
1
Не то, что спрашивал ОП, но я нашел это полезным.
прямоугольник
0

Ниже представлена ​​функция Python, использующая модуль htmlentitydefs. Это не идеально. Версия, htmlentitydefsкоторая у меня есть, является неполной, и она предполагает, что все объекты декодируются в одну кодовую точку, что неверно для таких объектов, как &NotEqualTilde;:

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

NotEqualTilde;     U+02242 U+00338    ≂̸

С учетом этих предостережений, вот код.

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;"
Майк Сэмюэл
источник
0

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

пример

Я сохранил ваш html-код scraped_htmlи 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> = 1.0

экранирования в

Чтобы отменить экранирование вашего очищенного html-кода, вы можете использовать django.utils.text.unescape_entities, который:

Преобразуйте все именованные и числовые ссылки на символы в соответствующие символы Юникода.

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

побег

Чтобы избежать вашего чистого html-кода, вы можете использовать django.utils.html.escape, который:

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

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

питон

Вам нужен Python> = 3.4

экранирования в

Чтобы отменить экранирование вашего очищенного html-кода, вы можете использовать html.unescape, который:

Преобразовать все ссылки на имена и числовые символьные (например &gt;, &#62;, &x3e;) в строку s в соответствующие символы Unicode.

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

побег

Чтобы избежать вашего чистого html-кода, вы можете использовать html.escape, который:

Преобразование символов &, <и >в строковых с до HTML-безопасных последовательностей.

>>> from html import escape
>>> scraped_html == escape(clean_html)
True
Паоло Мелькиорре
источник