Фильтрация определенных байтов в 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, из которого я не контролирую способ форматирования данных.
Ответы
Ответ 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
))
Я не уверен, что я сделал неправильно с лучшим вариантом, но я просто хотел двигаться дальше...
Ответ 4
вы можете обратиться к решению на этом веб-сайте:
https://mailman-mail5.webfaction.com/pipermail/lxml/2011-July/006090.html
Это решение работает для меня. Возможно, вам также придется рассмотреть решение Джона Мачина.
Удачи!