Python中最简单的避免HTML的方法是什么?

181

cgi.escape似乎是一种可行的选择。它的效果如何?有没有更好的选择?

9个回答

204

html.escape现在是正确的答案,Python 3.2之前使用的是cgi.escape。它会对以下字符进行转义:

  • < 转为 &lt;
  • > 转为 &gt;
  • & 转为 &amp;

这已经足够处理所有的HTML了。

编辑:如果您还需要转义非ASCII字符以便在另一种编码的文档中使用,就像Craig所说的那样,只需使用:

data.encode('ascii', 'xmlcharrefreplace')

不要忘记先解码dataunicode,使用相应的编码。

但是,根据我的经验,如果从一开始就始终使用unicode,那么这种编码方式就没有用处。最好在末尾使用文档头中指定的编码进行编码(utf-8以实现最大兼容性)。

例如:

>>> cgi.escape(u'<a>bá</a>').encode('ascii', 'xmlcharrefreplace')
'&lt;a&gt;b&#225;&lt;/a&gt;

值得注意的是(感谢Greg),cgi.escape函数还有一个额外的quote参数。当将其设置为True时,cgi.escape函数会转义双引号字符("),这样你就可以在XML/HTML属性中使用结果值。

注意,Python 3.2中已经弃用了cgi.escape函数,推荐使用html.escape函数代替,两者功能类似,只不过quote默认为True。


7
当文本用于HTML属性值时,应考虑向cgi.escape添加附加布尔参数以转义引号。 - Greg Hewgill
实际上,似乎您需要执行cgi.escape(yourunicode).decode('utf-8').encode('ascii', 'xmlcharrefreplace'),否则ascii编解码器无法处理Ω。 - Adrian Ghizaru
@AdrianGhizaru 首先,你试图对 yourunicode 进行解码,但是你声称它已经是 Unicode 编码,所以它应该是 已经被解码 的。这将会调用 隐式 ASCII 编码 或者直接失败,具体取决于 Python 版本。如果你只是使用答案末尾提供的示例 cgi.escape(u'Ω').encode('ascii', 'xmlcharrefreplace'),那么它就可以正常工作了。所以我猜如果你遇到了错误,那么 yourunicode 可能并不是真正的 Unicode 编码,你需要先对它进行解码才能得到 Unicode 编码。 - nosklo
你能详细解释一下解码/编码过程吗?当我尝试使用 text.decode('utf-8').encode('ascii', 'xmlcharrefreplace') 时,出现了错误信息 "UnicodeDecodeError: 'utf8' codec can't decode byte 0xfc in position 76: invalid start byte"。尝试使用 decode('Unicode') 则会提示未知编码:Unicode。 - 576i
注意:cgi.escape在Python 3.8中已被移除。 - 0x5453
显示剩余6条评论

174

Python 3.2引入了一个新的html模块,用于从HTML标记中转义保留字符。

它只有一个函数escape()

>>> import html
>>> html.escape('x > 2 && x < 7 single quote: \' double quote: "')
'x &gt; 2 &amp;&amp; x &lt; 7 single quote: &#x27; double quote: &quot;'

quote=True 是什么意思? - 2rs2ts
3
@SalmanAbbas 你是否担心引号未被转义? 请注意,默认情况下,html.escape()会转义引号(相比之下,cgi.quote()不会这样做,只有在被告知时才会转义双引号)。因此,我必须显式地设置一个可选参数来将某些内容注入到带属性的标签上,即使用html.escape()使其对属性不安全: t = '" onclick="alert()'; t = html.escape(t, quote=False); s = f'<a href="about.html" class="{t}">foo</a>' - maxschlepzig
@maxschlepzig 我认为Salman的意思是说escape()不足以使属性安全。换句话说,这样做并不安全:<a href=" {{ html.escape(untrusted_text) }} "> - pianoJames
@pianoJames,我了解了。我认为检查链接值是一种特定于域的语义验证,而不是像转义那样的词汇验证。除了内联JavaScript之外,您真的不希望从不受信任的用户输入创建链接,而不进行进一步的URL特定验证(例如,因为垃圾邮件发送者)。防止在href等属性中使用内联JavaScript的简单方法是设置禁止其执行的内容安全策略。 - maxschlepzig
@pianoJames 这是安全的,因为 html.escape 会转义单引号和双引号。 - Flimm

12

如果您希望在URL中避免HTML:

这可能不是OP想要的(问题没有清楚地指示需要在哪种上下文中使用转义),但Python的原生库urllib有一种方法可以安全地转义需要包含在URL中的HTML实体。

以下是一个例子:

#!/usr/bin/python
from urllib import quote

x = '+<>^&'
print quote(x) # prints '%2B%3C%3E%5E%26'

在这里查找文档


11
这是错误的转义方式;我们需要的是HTML 转义,而不是 URL 编码 - Chaosphere2112
8
无论如何,这正是我正在寻找的;-) - Brad
在Python 3中,此功能已移至urllib.parse.quote。https://docs.python.org/3/library/urllib.parse.html#url-quoting - Mark Peschel

9

还有一个非常优秀的markupsafe包

>>> from markupsafe import Markup, escape
>>> escape("<script>alert(document.cookie);</script>")
Markup(u'&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
markupsafe软件包经过精心设计,可能是最通用和最符合Pythonic的转义方式,我个人认为,因为:
  1. 返回(Markup)是从unicode派生的类(即isinstance(escape('str'), unicode) == True
  2. 它可以正确处理Unicode输入
  3. 它适用于Python (2.6, 2.7, 3.3, and pypy)
  4. 它尊重对象的自定义方法(即带有__html__属性的对象)和模板重载(__html_format__)。

7

cgi.escape可以用于在有限的意义上转义HTML标签和字符实体。

但是,您可能还需要考虑编码问题:如果要引用的HTML在特定编码中具有非ASCII字符,则还必须确保在引用时以明智的方式表示这些字符。也许您可以将它们转换为实体。否则,您应该确保在“源”HTML和嵌入页面之间进行正确的编码转换,以避免损坏非ASCII字符。


6

无需使用任何库,纯Python实现,可以将文本安全地转换为HTML文本:

text.replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;'
        ).replace('\'','&#39;').replace('"','&#34;').encode('ascii', 'xmlcharrefreplace')

2
您的顺序有误,&lt; 将被转义为 &amp;lt; - Jason S
@jason s 谢谢你的修复! - speedplane

2

虽然不是最简单的方法,但仍然很直接。与cgi.escape模块的主要区别在于,如果您的文本中已经有&amp;,它仍将正常工作。从对它的评论中可以看出:

cgi.escape版本

def 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.'''
    s = s.replace("&", "&amp;") # Must be done first!
    s = s.replace("<", "&lt;")
    s = s.replace(">", "&gt;")
    if quote:
        s = s.replace('"', "&quot;")
    return s

正则表达式版本

QUOTE_PATTERN = r"""([&<>"'])(?!(amp|lt|gt|quot|#39);)"""
def escape(word):
    """
    Replaces special characters <>&"' to HTML-safe sequences. 
    With attention to already escaped characters.
    """
    replace_with = {
        '<': '&gt;',
        '>': '&lt;',
        '&': '&amp;',
        '"': '&quot;', # should be escaped in attributes
        "'": '&#39'    # should be escaped in attributes
    }
    quote_pattern = re.compile(QUOTE_PATTERN)
    return re.sub(quote_pattern, lambda x: replace_with[x.group(0)], word)

1

cgi.escape 扩展版

这个版本改进了 cgi.escape。它还保留了空格和换行符。返回一个 unicode 字符串。

def escape_html(text):
    """escape strings for display in HTML"""
    return cgi.escape(text, quote=True).\
           replace(u'\n', u'<br />').\
           replace(u'\t', u'&emsp;').\
           replace(u'  ', u' &nbsp;')

例如
>>> escape_html('<foo>\nfoo\t"bar"')
u'&lt;foo&gt;<br />foo&emsp;&quot;bar&quot;'

1

对于Python 2.7中的旧代码,可以通过BeautifulSoup4实现:

>>> bs4.dammit import EntitySubstitution
>>> esub = EntitySubstitution()
>>> esub.substitute_html("r&d")
'r&amp;d'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接