2012-01-04 2 views
13

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

+1

У вас также есть случайные данные на входе, как в вопросе, на который вы ссылаетесь? –

ответ

21

В ответ на связанный вопрос сказал, стандарт 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)) 
+0

Похоже, это может сработать. К сожалению, данные поступают из внешнего api, и ошибка возникает очень редко. Поэтому у меня нет способа воспроизвести условия, вызвавшие ошибку. Я должен подождать, пока обстоятельства не станут такими же, прежде чем я смогу проверить это решение. Спасибо за ответ, хотя, я довольно новичок в python и понимаю реализацию вашего решения (и как он решает мою проблему) даст мне возможность лучше понять язык. – y3di

+0

@ y3di: Пока вы ожидаете, что ошибка повторится, вы можете изменить свой код, чтобы уловить ошибку и записать оскорбительный XML-документ в файл журнала. Таким образом, вы можете точно узнать, что такое незаконный символ (ы), и решить, является ли более целесообразным удаление их или замена других символов. В любом случае на ваш вопрос (на самом деле: как отфильтровать незаконные символы из XML-документа) был дан ответ, и вы должны подумать о его принятии (нажмите «галочку» слева от ответа). –

+1

Для целей атрибуции я просто подключаю сюда сущность с кодом для фильтрации этих символов из строк, поскольку мне пришлось использовать это в последнее время. Спасибо за Ваш ответ. https://gist.github.com/rcalsaverini/6212717 –

3

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

лучше, спасибо –

+0

, вы можете использовать' bytes.translate() 'для удаления/замены байтов, которые не являются в 'string.printable'. В отличие от решения @John Machin; он не работает для произвольного текста в Юникоде. – jfs

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 секунд.


Update: Как указано в комментариях, регулярное выражение в этом ответе ранее удалены некоторые символы, которые должны быть разрешены в 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) 

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

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

+0

Вы уверены, что регулярное выражение работает так, как ожидалось? Я считаю, что последние две буквы 'F' не анализируются как часть' \ u' escape. Попробуйте это в консоли python: 'print (u '\ u10ffff')' И вот это: 'print (u '\ u10ffzz')' –

+0

Вы правы, и это определенно могло привести к стиранию данных путем удаления unicode с кодовыми точками между 65536 и 1114111. Я обновил ответ. – mlissner

Смежные вопросы