【问题标题】:Filtering out certain bytes in python过滤掉python中的某些字节
【发布时间】:2012-02-02 17:31:36
【问题描述】:

我的 python 程序中出现此错误:ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters

random text from /dev/random raising an error in lxml: All strings must be XML compatible: Unicode or ASCII, no NULL bytes 这个问题解释了这个问题。

解决方案是过滤掉某些字节,但我对如何去做这件事感到困惑。

有什么帮助吗?

编辑:抱歉,如果我没有提供有关该问题的足够信息。字符串数据来自外部 api 查询,我无法控制数据的格式。

【问题讨论】:

  • 您是否也有输入的随机数据,如您所指的问题?

标签: python xml text unicode lxml


【解决方案1】:

正如链接问题的答案所说,XML标准将有效字符定义为:

Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

将其翻译成 Python:

def valid_xml_char_ordinal(c):
    codepoint = ord(c)
    # conditions ordered by presumed frequency
    return (
        0x20 <= codepoint <= 0xD7FF or
        codepoint in (0x9, 0xA, 0xD) or
        0xE000 <= codepoint <= 0xFFFD or
        0x10000 <= codepoint <= 0x10FFFF
        )

然后您可以根据需要使用该功能,例如

cleaned_string = ''.join(c for c in input_string if valid_xml_char_ordinal(c))

【讨论】:

  • 这看起来可能有效。不幸的是,数据来自外部 api,并且很少抛出错误。所以我无法重现导致错误的条件。我必须等到情况相同才能测试此解决方案。不过感谢您的回复,我对 python 还很陌生,了解您的解决方案的实现(以及它如何解决我的问题)将使我有机会更好地理解该语言。
  • @y3di:在等待错误再次发生时,您可能希望更改代码以捕获错误并将有问题的 XML 文档写入日志文件。这样您就可以准确地找出非法字符是什么,并决定是去掉它们还是替换一些其他字符更合适。无论如何,您的问题(实际上是:如何从 XML 文档中过滤掉非法字符)已经得到解答,您应该考虑接受它(点击答案左侧的“勾号”)。
  • 出于归属目的,我只是在这里插入一个要点,其中包含用于从字符串中过滤这些字符的代码,因为我最近不得不使用它。感谢您的回答。 gist.github.com/rcalsaverini/6212717
  • 我发现字符 '\xa9' 不被 lxml.builder.E 接受,但它没有被你的函数过滤掉。 from lxml.builder import E E('a','\xa9') ValueError: 所有字符串必须与 XML 兼容:Unicode 或 ASCII,没有 NULL 字节或控制字符 ''.join(c for c in '\xa9' if valid_xml_char_ordinal(c)) '\xa9'
【解决方案2】:

另一种比上述答案快得多的方法是使用正则表达式,如下所示:

re.sub(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]+', '', text)

与上面的答案相比,在我的测试中它的速度提高了 10 倍以上:

import timeit

func_test = """
def valid_xml_char_ordinal(c):
    codepoint = ord(c)
    # conditions ordered by presumed frequency
    return (
        0x20 <= codepoint <= 0xD7FF or
        codepoint in (0x9, 0xA, 0xD) or
        0xE000 <= codepoint <= 0xFFFD or
        0x10000 <= codepoint <= 0x10FFFF
    );
''.join(c for c in r.content if valid_xml_char_ordinal(c))
"""

func_setup = """
import requests; 
r = requests.get("https://stackoverflow.com/questions/8733233/")
"""

regex_test = """re.sub(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]+', '', r.content)"""
regex_setup = """
import requests, re; 
r = requests.get("https://stackoverflow.com/questions/8733233/")
"""

func_test = timeit.Timer(func_test, setup=func_setup)
regex_test = timeit.Timer(regex_test, setup=regex_setup)

print func_test.timeit(100)
print regex_test.timeit(100)

输出:

> 2.63773989677
> 0.221401929855

因此,理解这一点,我们正在做的是下载该网页一次(您当前正在阅读的页面),然后在其内容上运行函数技术和正则表达式技术各 100 倍。

使用函数式方法大约需要 2.6 秒。
使用正则表达式方法大约需要 0.2 秒。


更新:如 cmets 中所述,此答案中的正则表达式之前删除了一些字符,这些字符在 XML 中应该是允许的。这些字符包括 Supplementary Multilingual Plane 中的任何内容,其中包括古代文字,如楔形文字、象形文字和(奇怪的)表情符号。

正确的正则表达式现在在上面。将来对此的快速测试是使用re.DEBUG,它会打印:

In [52]: re.compile(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]+', re.DEBUG)
max_repeat 1 4294967295
  in
    negate None
    range (32, 55295)
    literal 9
    literal 10
    literal 13
    range (57344, 65533)
    range (65536, 1114111)
Out[52]: re.compile(ur'[^ -\ud7ff\t\n\r\ue000-\ufffd\U00010000-\U0010ffff]+', re.DEBUG)

我为这个错误道歉。我只能提供我在其他地方找到了这个答案并把它放在这里。这是别人的错误,但我传播了它。我向所有受到影响的人表示诚挚的歉意。

2017 年 12 月 12 日第 2 次更新:我从一些 OSX 用户那里了解到,此代码不适用于所谓的 Python 窄版本,而 OSX 有时会这样做。您可以通过运行import sys; sys.maxunicode 来检查这一点。如果它打印 65535,则在您安装“宽版本”之前,此处的代码将不起作用。 See more about this here.

【讨论】:

  • 您确定正则表达式按预期工作吗?我相信最后两个F 字母没有被解析为\u 转义的一部分。在 python 控制台中试试这个:print(u'\u10ffff') 然后这个:print(u'\u10ffzz')
  • 你是对的,这肯定会通过删除代码点在 65536 和 1114111 之间的 unicode 导致数据擦除。我已经更新了答案。
  • 这会引发错误radars_string = re.sub(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]+', '', radars_string) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/re.py", line 155, in sub return _compile(pattern, flags).sub(repl, string, count) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/re.py", line 251, in _compile raise error, v # invalid expression sre_constants.error: bad character range
  • 我怀疑你可能有一个“狭窄”的 Python 版本?不是肯定的,但很容易检查使用广泛的构建是否可以解决问题:python.org/dev/peps/pep-0261
【解决方案3】:

我认为这是苛刻/矫枉过正,而且看起来非常缓慢,但是我的程序仍然很快,并且在努力理解出了什么问题之后(即使在我尝试实现 @John 的 clean_string 实现之后),我只是将他的答案调整为使用以下 (Python 2.7) 清除 ASCII 不可打印:

from curses import ascii
def clean(text):
    return str(''.join(
            ascii.isprint(c) and c or '?' for c in text
            )) 

我不确定我做错了什么更好的选择,但我只是想继续前进......

【讨论】:

  • 您可以使用bytes.translate() 删除/替换不在string.printable 中的字节。与@John Machin 的解决方案不同;它不适用于任意 Unicode 文本。
  • @sage 非常感谢!
【解决方案4】:

您可以参考本网站上的解决方案:

https://mailman-mail5.webfaction.com/pipermail/lxml/2011-July/006090.html

该解决方案对我有用。您可能还需要考虑 John Machin 的解决方案。

祝你好运!

【讨论】:

  • 链接失效
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-02
  • 2017-01-28
  • 1970-01-01
  • 2022-06-14
  • 1970-01-01
  • 2021-06-14
  • 1970-01-01
相关资源
最近更新 更多