Подтвердить что ты не робот

Фильтрация определенных байтов в python

Я получаю эту ошибку в моей программе 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, из которого я не контролирую способ форматирования данных.

4b9b3361

Ответ 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))

Ответ 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

Итак, имея в виду, что мы делаем это, загружаем эту веб-страницу один раз (страница, которую вы сейчас читаете), затем запускаете функциональную технику и технику регулярного выражения по ее содержимому 100X каждый.

Использование функционального метода занимает около 2,6 секунд.
Использование метода regex занимает около 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

Я думаю, что это суровое/чрезмерное, и это кажется мучительно медленным, но моя программа по-прежнему быстро и после попытки понять, что пошло не так (даже после того, как я попытался реализовать реализацию @John cleaned_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
            )) 

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