Мне нужно обрабатывать большие XML-ответы в виде потока. Несжатые ответы могут иметь размер в несколько сотен мегабайт, поэтому полностью загружать их в память перед передачей синтаксическому анализатору XML не представляется возможным.
Я использую lxml для синтаксического анализа и просто передаю response.raw
его функции iterparse()
, как описано где-то в документации по запросам. Это отлично подходит для несжатых ответов.
К сожалению, API, который я вызываю, не очень хорош. Поэтому иногда он возвращает Content-Encoding: gzip
даже если я явно запрашиваю несжатые данные. Кроме того, степень сжатия этих чрезвычайно повторяющихся и подробных XML-файлов действительно хороша (10x +), поэтому я бы очень хотел использовать сжатые ответы.
Возможно ли это с запросами? Я не нашел его в документации. При более глубоком изучении urllib3 его метод HTTPResponse.read (), похоже, поддерживает параметр decode_content
. Если не установлен, urllib3 возвращается к тому, что установлено в конструкторе. Когда запросы вызывают конструктор в request.adapters.HTTPAdapter.send () , он явно устанавливает для decode_content
значение False.
Есть ли причина, по которой запросы делают это?
Как ни странно, iter_content()
самом деле устанавливает decode_content=True
при чтении. Почему здесь? Все это кажется немного произвольным. Я действительно не понимаю мотивацию делать это здесь так, а там иначе.
Лично я, конечно, не могу использовать iter_content()
потому что мне нужен файловый объект для lxml.
Я ранее писал свой собственный файловый объект, который я могу подключать между запросами и lxml, но, конечно, буферизация сложна, и я чувствую, что люди более умные, чем я, писали это раньше, поэтому я бы предпочел не катить свой собственный .
Что вы посоветуете, как с этим справиться? Следует ли изменить запросы по умолчанию на установку decode_content=True
в urllib3?
Нет, он не должен устанавливать это по умолчанию по целому ряду причин. Что вам нужно сделать, так это использовать functools.partial
для замены метода read
в ответе (или просто обернуть его другим способом), чтобы вы сделали что-то вроде:
response.raw.read = functools.partial(response.raw.read, decode_content=True)
а затем передайте парсеру response.raw
.
@ sigmavirus24 Спасибо, это определенно элегантное решение проблемы, о которой я говорил выше!
Я бы рекомендовал добавить это в документацию по запросам, например, в FAQ: http://docs.python-requests.org/en/latest/community/faq/#encoded -data
В настоящее время утверждение «Запросы автоматически распаковывают ответы в формате gzip» неверно для случая stream=True
и может привести к неожиданностям.
Что касается моей проблемы, как вы читали о проблеме urllib3, реализация распаковки gzip urllib3 имеет свои собственные небольшие особенности, которые я должен исправить в моем коде, но это больше не проблема для запросов.
но это больше не проблема для запросов.
Как вы думаете, это можно закрыть?
@ sigmavirus24 Я считаю, что это следует задокументировать, поскольку текущая документация неверна.
Но если вы не согласны с этим, да, закройте!
Документация могла бы быть более понятной. Для меня (и это полностью потому, что я основной разработчик) первый абзац обращается к 90% пользователей, которые никогда не коснутся необработанного ответа, а второй абзац противоречит первому, говоря «но если вам нужно получить доступ к сырые данные, они есть для вас ". Как я уже сказал, это очевидно для меня, но я вижу, как это можно прояснить. Я поработаю над этим сегодня вечером.
Для меня это больше, что я бы интерпретировал «необработанные данные» как «необработанные данные», то есть распакованный поток. Мне просто нужно прочитать это по частям, которые мне нужны. В отличие от .content
, который представляет собой распакованный BLOB-объект (также полезная нагрузка, но в другой форме).
Фактическая распаковка кажется мне проблемой библиотеки HTTP - деталью реализации HTTP, если хотите, которая, как я ожидаю, абстрагируется от запросов. Независимо от того, читаю ли я полезную нагрузку из запросов как поток или как предварительно полученный блок данных, не имеет значения. В любом случае запросы будут абстрагироваться от «сжатия» деталей реализации.
(Это предположение также лежало в основе моего первоначального запроса на установку по умолчанию decode_content
на True
. Конечно, теперь, когда я вижу, что это за ненадежная абстракция, я больше не предлагаю этого.)
Но да, я абсолютно согласен с тем, что 99% ваших пользователей никогда не будут затронуты этой деталью.
Не стесняйтесь закрывать этот выпуск.
Так что это на самом деле приводит к тому, что какое-то время вертится у меня в голове и чего я еще не предлагал, потому что это будет существенное изменение API.
Мне не нравится тот факт, что мы предлагаем людям использовать r.raw
потому что это объект, который мы не документируем, и это объект, предоставляемый urllib3
(который, как мы заявляли ранее, подробнее о реализации). Имея это в виду, я раздумывал над идеей предоставления методов для объекта Response
которые просто прокси для urllib3
методов ( read
будет просто прокси для raw.read
и т. Д.). Это дает нам дополнительную гибкость в отношении urllib3
и позволяет нам обрабатывать (от имени пользователей) изменение API в urllib3
(что исторически почти никогда не было проблемой, поэтому нет никаких срочность в этом).
С учетом сказанного, на мой взгляд, у нас уже достаточно методов для объекта Response, и расширение нашего API не является идеальным. Лучший API - это тот API, из которого уже нечего удалять. Так что я постоянно сомневаюсь в этом.
Это предположение также лежало в основе моего первоначального запроса на значение по умолчанию для decode_content значение True. Конечно, теперь, когда я вижу, какая это дырявая абстракция, я больше не предлагаю этого.
Для тех, кто находит это и может не знать, почему это правда, позвольте мне объяснить.
Есть несколько пользователей запросов, которые отключают автоматическую декомпрессию, чтобы проверить длину ответа или сделать с ним другие важные вещи. Одним из потребителей первого типа является OpenStack. Многие клиенты OpenStack проверяют отправленный клиенту заголовок Content-Length
и фактическую длину полученного тела. Для них обработка декомпрессии - это справедливый компромисс, позволяющий быть уверенным, что они получают и обрабатывают правильный ответ.
Другой потребитель - это Betamax (или любой другой инструмент, который (пере) конструирует объекты Response), потому что, когда он обрабатывает полный процесс создания полностью действительного ответа, ему нужно, чтобы контент был в сжатом формате.
Я уверен, что есть другие, о которых ни @Lukasa, ни я не знаем, также сильно полагаются на такое поведение.
Решили ту же проблему сегодня и в итоге сделали то же предположение, что на данный момент другого способа потоковой передачи ответов нет.
Вместо нескольких новых методов в Response, почему бы не один новый атрибут, например, response.stream
который играл бы ту же роль прокси для .raw
? Это также хорошо отражает параметр / параметр stream=True
и не влияет на пользователей, которым требуется текущее поведение .raw
.
Я делал это в прошлом
r = requests.get('url', stream=True)
r.raw.decode_content = True
...
Обратите внимание, что обходной путь @ sigmavirus24 нарушает семантику метода tell
, который возвращает неправильные смещения.
Я столкнулся с этим при потоковой передаче ответа в виде возобновляемой загрузки в Google Cloud Storage API, который использует tell()
для определения количества только что прочитанных байтов ( здесь ).
Самый полезный комментарий
Я делал это в прошлом