Контент-длина заголовка не такая, как при его ручном расчете? - PullRequest
0 голосов
/ 12 июня 2018

Ответ здесь ( Размер необработанного ответа в байтах ) гласит:

Просто возьмите len() содержимого ответа:

>>> response = requests.get('https://github.com/')
>>> len(response.content)
51671

Однако это не дает точную длину контента.Например, проверьте этот код Python:

import sys
import requests

def proccessUrl(url):
    try:
        r = requests.get(url)
        print("Correct Content Length: "+r.headers['Content-Length'])
        print("bytes of r.text       : "+str(sys.getsizeof(r.text)))
        print("bytes of r.content    : "+str(sys.getsizeof(r.content)))
        print("len r.text            : "+str(len(r.text)))
        print("len r.content         : "+str(len(r.content)))
    except Exception as e:
        print(str(e))

#this url contains a content-length header, we will use that to see if the content length we calculate is the same.
proccessUrl("https://stackoverflow.com")

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

Correct Content Length: 51504
bytes of r.text       : 515142
bytes of r.content    : 257623
len r.text            : 257552
len r.content         : 257606

Почему len(r.content) не возвращает правильную длину содержимого?И как мы можем вручную точно рассчитать его, если заголовок отсутствует?

1 Ответ

0 голосов
/ 12 июня 2018

Заголовок Content-Length отражает тело ответа.Это не то же самое, что длина атрибутов text или content, поскольку ответ может быть сжатым .requests распаковывает ответ для вас.

Чтобы получить исходный сжатый необработанный контент, вам придется обойти много внутренней сантехники, а затем вам придется получить доступ к еще нескольким внутренним компонентам, если вы хотите response объект по-прежнему работает правильно.Самый простой способ - включить потоковую передачу, а затем выполнить чтение из необработанного сокета:

from io import BytesIO

r = requests.get(url, stream=True)
# read directly from the raw urllib3 connection
raw_content = r.raw.read()
content_length = len(raw_content)
# replace the internal file-object to serve the data again
r.raw._fp = BytesIO(raw_content)

Демонстрация:

>>> import requests
>>> from io import BytesIO
>>> url = "https://stackoverflow.com"
>>> r = requests.get(url, stream=True)
>>> r.headers['Content-Encoding'] # a compressed response
'gzip'
>>> r.headers['Content-Length']   # the raw response contains 52055 bytes of compressed data
'52055'
>>> r.headers['Content-Type']     # we are served UTF-8 HTML data
'text/html; charset=utf-8'
>>> raw_content = r.raw.read()
>>> len(raw_content)              # the raw content body length
52055
>>> r.raw._fp = BytesIO(raw_content)
>>> len(r.content)    # the decompressed binary content, byte count
258719
>>> len(r.text)       # the Unicode content decoded from UTF-8, character count
258658

Это считывает полный ответ в память, поэтому не используйтеэто если вы ожидаете больших откликов!В этом случае вы могли бы вместо этого использовать shutil.copyfileobj() для копирования данных из файла r.raw во временный буферный временный файл (который переключится на файл на диске при достижении определенного размера),получить размер файла этого файла, а затем вставить этот файл в r.raw._fp.

Функция, которая добавляет заголовок Content-Type к любому запросу, в котором этот заголовок отсутствует, будет выглядеть так:

import requests
import shutil
import tempfile

def ensure_content_length(
    url, *args, method='GET', session=None, max_size=2**20,  # 1Mb
    **kwargs
):
    kwargs['stream'] = True
    session = session or requests.Session()
    r = session.request(method, url, *args, **kwargs)
    if 'Content-Length' not in r.headers:
        # stream content into a temporary file so we can get the real size
        spool = tempfile.SpooledTemporaryFile(max_size)
        shutil.copyfileobj(r.raw, spool)
        r.headers['Content-Length'] = str(spool.tell())
        spool.seek(0)
        # replace the original socket with our temporary file
        r.raw._fp.close()
        r.raw._fp = spool
    return r

Принимает существующий сеанс и позволяет также указать метод запроса.Отрегулируйте max_size по мере необходимости для ваших ограничений памяти.Демонстрация по https://github.com, в которой отсутствует заголовок Content-Length:

>>> r = ensure_content_length('https://github.com/')
>>> r
<Response [200]>
>>> r.headers['Content-Length']
'14490'
>>> len(r.content)
54814

Обратите внимание, что если отсутствует заголовок Content-Encoding или значение для этого заголовка установлено на identity, и Content-Length доступно, тогда только вы можете положиться на Content-Length, являющийся полным размером ответа.Это потому, что тогда, очевидно, сжатие не применяется.

В качестве примечания: вы не должны использовать sys.getsizeof(), если то, что вы ищете, это длина объекта bytes или str (числобайты или символы в этом объекте).sys.getsizeof() дает вам объем внутренней памяти объекта Python, который охватывает не только количество байтов или символов в этом объекте.См. В чем разница между методами len () и sys.getsizeof () в python?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...