Фильтрация определенных байтов в Python - PullRequest
14 голосов
/ 05 января 2012

Я получаю эту ошибку в моей программе на Python: ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters

Этот вопрос, случайный текст из / dev / random, приводящий к ошибке в lxml: все строки должны быть совместимы с XML: Unicode или ASCII, без байтов NULL , объясняет проблему.

Решением было отфильтровать определенные байты, но я не совсем понимаю, как это сделать.

Любая помощь?

Редактировать: извините, если я не дал достаточно информации о проблеме. Строковые данные поступают из внешнего API-запроса, который я не могу контролировать, как форматируются данные.

Ответы [ 4 ]

22 голосов
/ 05 января 2012

Как ответ на связанный вопрос, стандарт 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))
11 голосов
/ 18 сентября 2014

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

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("/5536099/filtratsiya-opredelennyh-baitov-v-python")
"""

regex_test = """re.sub(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]+', '', r.content)"""
regex_setup = """
import requests, re; 
r = requests.get("/5536099/filtratsiya-opredelennyh-baitov-v-python")
"""

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.Эти символы включают в себя что-либо на дополнительной многоязычной плоскости , которая включает древние сценарии, такие как клинопись, иероглифы и (странно) эмодзи.

Правильное регулярное выражение теперь вышеБыстрый тест для этого в будущем использует 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)

Мои извинения за ошибку.Я могу только предложить, что я нашел этот ответ в другом месте и поместил его здесь.Это была чужая ошибка, но я ее размножил.Мои искренние извинения всем, кого это затронуло.

Обновление 2, 2017-12-12 : я узнал от некоторых пользователей OSX, что этот код не будет работать на так называемых узких сборкахPython, который, по-видимому, OSX иногда имеет.Вы можете проверить это, запустив import sys; sys.maxunicode.Если он напечатает 65535, код здесь не будет работать, пока вы не установите «широкую сборку». Подробнее об этом здесь.

3 голосов
/ 29 декабря 2013

Я думаю, что это жестко / излишне, и, кажется, мучительно медленно, но моя программа все еще быстра, и после попытки понять, что происходит не так (даже после того, как я попытался реализовать реализацию @ John's cleaned_string), я просто адаптировал его ответ кочистить ASCII-непечатаемый с помощью следующего (Python 2.7):

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

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

0 голосов
/ 12 января 2013

Вы можете обратиться к решению на этом сайте:

https://mailman -mail5.webfaction.com / pipermail / LXML / 2011-июль / 006090.html

Это решение работает для меня. Возможно, вам также придется рассмотреть решение Джона Мачина.

Удачи!

...