Как ограничить скорость загрузки в запросах python3?

Я использую запросы для загрузки большого (~ 50 МБ) файла на небольшое встроенное устройство под управлением Linux.

Файл должен быть записан на прикрепленную MMC.

К сожалению, скорость записи MMC ниже, чем скорость сети, и я вижу увеличение потребления памяти, а в некоторых случаях у меня даже было ядро, неспособное обработать ошибку page....

Устройство имеет только 128 МБ оперативной памяти.

Код, который я использую:

            with requests.get(URL,  stream=True) as r:
                if r.status_code != 200:
                    log.error(f'??? download returned {r.status_code}')
                    return -(offset + r.status_code)
                siz = 0
                with open(sfn, 'wb') as fo:
                    for chunk in r.iter_content(chunk_size=4096):
                        fo.write(chunk)
                        siz += len(chunk)
                return siz

Как я могу временно остановить сервер, пока я записываю в MMC?


person ZioByte    schedule 14.02.2021    source источник
comment
Отвечает ли это на ваш вопрос? Загрузить большой файл в python с запросами   -  person python_user    schedule 14.02.2021
comment
@python_user: если я что-то не упустил, код эквивалентен тому, что я уже использую. Моя проблема заключается в том, что часть записи на диск медленнее, чем получение из сети. В этом состоянии, по-видимому, запросы (или что-то еще ниже) продолжают выделять память для буферизации входящих данных, которые еще не обработаны. Мне нужен способ замедлить источник (например, задержать отправку кадра ACK)   -  person ZioByte    schedule 14.02.2021
comment
Также связано: stackoverflow.com/questions/17691231/   -  person fuenfundachtzig    schedule 14.02.2021


Ответы (3)


                if r.status_code != 200:
                    log.error(f'??? download returned {r.status_code}')
                    return -(offset + r.status_code)
                siz = 0
                with open(sfn, 'wb') as fo:
                    for chunk in r.iter_content(chunk_size=4096):
                        fo.write(chunk)
                        siz += len(chunk)
                return siz

Вы можете переписать его как сопрограмму

import requests

def producer(URL,temp_data,n):
    with requests.get(URL,  stream=True) as r:
        if r.status_code != 200:
            log.error(f'??? download returned {r.status_code}')
            return -(offset + r.status_code)
        for chunk in r.iter_content(chunk_size=n):
            temp_data.append(chunk)
            yield #waiting to finish the consumer
            

def consumer(temp_data,fname):
    with open(fname, 'wb') as fo:
        while True:
            while len(temp_data) > 0:
                for data in temp_data:
                    fo.write(data)
                    temp_data.remove(data) # To remove it from the list
                    # You can add sleep here
                    yield #waiting for more data


def coordinator(URL,fname,n=4096):
    temp_data = list()
    c = consumer(temp_data,fname)
    p = producer(URL,temp_data,n)
    while True:
        try:
            #getting data
            next(p)
        except StopIteration:
            break
        finally:
            #writing data
            next(c)

Это все функции, которые вам нужны. Чтобы назвать это

URL = "URL"
fname = 'filename'
coordinator(URL,fname)
person SaGaR    schedule 14.02.2021
comment
Спасибо. Я обязательно попробую это, но я не понимаю, как это замедлит процесс. Мои операции записи и так слишком медленные, как это может помочь замедлить их еще больше? По-видимому, запросы не будут ждать, пока я опрошу r.iter_content(), чтобы заполнить его. Мне нужно где-то вставить спящий вызов в код запроса (IFF, я правильно понимаю). - person ZioByte; 14.02.2021
comment
Да. Вы можете добавить спящий вызов перед вызовом следующего (объекта) в цикле for. Подожди немного, я переписываю - person SaGaR; 14.02.2021
comment
@ZioByte Готово, теперь вы можете его использовать. Это также может решить вашу проблему с памятью, поскольку он записывает фрагменты один за другим. - person SaGaR; 14.02.2021
comment
@ e3n Но если вы спите (), не будет ли ОС просто буферизовать полученные пакеты где-то в памяти, сохраняя проблему с памятью? - person Anonymous1847; 14.02.2021
comment
@ Anonymous1847 У меня нет большого опыта работы с файлами. Но здесь мы получаем chunk и потребляем его вручную, поэтому я не думаю, что должна быть какая-то проблема, связанная с памятью. И сон необязателен. я не рекомендую это, но, возможно, устройство @Ziobyte медленное, поэтому оно может ему понадобиться - person SaGaR; 14.02.2021

Если веб-сервер поддерживает поле http Range, вы можете запросить загрузку только части большого файла, а затем просмотреть весь файл по частям.

Взгляните на этот вопрос, где Джеймс Миллс приводит следующий пример кода:

from requests import get

url = "http://download.thinkbroadband.com/5MB.zip"
headers = {"Range": "bytes=0-100"}  # first 100 bytes

r = get(url, headers=headers)

Поскольку ваша проблема связана с памятью, вы захотите запретить серверу отправлять вам весь файл сразу, так как это, безусловно, будет буферизовано каким-то кодом на вашем устройстве. Если вы не можете заставить запросы отбрасывать часть получаемых данных, это всегда будет проблемой. Дополнительные буферы после requests не помогут.

person fuenfundachtzig    schedule 14.02.2021
comment
Хорошая попытка (я проголосовал), но мой сервер не поддерживает ни Range, ни Restart :( - person ZioByte; 15.02.2021
comment
Это неудачно. Если вы просто хотите загрузить файл на диск, рассмотрите возможность использования не requests, а внешнего инструмента, такого как curl, который вы можете вызывать через os.system (или subprocess и т. д.). curl поддерживает ограничения пропускной способности, ср. unix.stackexchange.com/questions/39218 - person fuenfundachtzig; 15.02.2021

Вы можете попробовать уменьшить размер буфера приема TCP с помощью этой команды bash:

echo 'net.core.rmem_max=1000000' >> /etc/sysctl.conf

(1 МБ, вы можете настроить это)

Это предотвращает накопление огромного буфера на данном этапе процесса.

Затем напишите код для чтения только из стека TCP и записи в MMC через определенные промежутки времени, чтобы предотвратить создание буферов в другом месте системы, таких как буфер записи MMC - например, ответ @e3n.

Надеюсь, это должно привести к тому, что пакеты будут отброшены, а затем повторно отправлены сервером, как только буфер снова откроется.

person Anonymous1847    schedule 14.02.2021
comment
Хм... интересно, мое текущее значение (вообще нет /etc/sysctl.conf) очень низкое: net.core.rmem_max = 180224. Я предполагаю, что что-то выполняет буферизацию где-то выше базового TCP и ниже уровня моего приложения. - person ZioByte; 15.02.2021
comment
@ZioByte Возможно, библиотека Python что-то буферизует. Возможно, есть возможность уменьшить размер буфера? Или вы можете попробовать другую библиотеку. - person Anonymous1847; 15.02.2021