过滤掉python中的某些字节

时间:2012-01-04 20:03:42

标签: python xml text unicode lxml

我在我的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查询,我无法控制数据的格式化。

4 个答案:

答案 0 :(得分:22)

正如链接问题的答案所说,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))

答案 1 :(得分:10)

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

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

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

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


更新:正如评论中所指出的,此答案中的正则表达式之前删除了一些字符,这些字符应该在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日更新:我从一些OSX用户那里了解到,这段代码不适用于所谓的Python的窄版本,显然OSX有时会这样做。您可以通过运行import sys; sys.maxunicode来检查此问题。如果它打印65535,则在安装“wide build”之前,此处的代码将不起作用。 See more about this here.

答案 2 :(得分:3)

我认为这是苛刻/过度杀戮而且似乎非常缓慢,但是我的程序仍然很快并且在努力理解出错之后(即使在我尝试实现@ John的clean_string实现之后),我只是调整了他的答案使用以下方法清除ASCII-unprintable(Python 2.7):

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

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

答案 3 :(得分:-3)

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

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

该解决方案适合我。您可能还需要考虑John Machin的解决方案。

祝你好运!