programing

요청이 있는 python에서 대용량 파일 다운로드

newsource 2022. 9. 25. 00:27

요청이 있는 python에서 대용량 파일 다운로드

요청은 정말 좋은 라이브러리입니다.대용량 파일 다운로드(1GB 이상)에 사용하고 싶습니다.문제는 파일 전체를 메모리에 보관할 수 없다는 것입니다.단위로 읽어야 합니다.이것은 다음 코드의 문제입니다.

import requests

def DownloadFile(url)
    local_filename = url.split('/')[-1]
    r = requests.get(url)
    f = open(local_filename, 'wb')
    for chunk in r.iter_content(chunk_size=512 * 1024): 
        if chunk: # filter out keep-alive new chunks
            f.write(chunk)
    f.close()
    return 

어떤 이유로 인해 이 방법은 작동하지 않습니다. 파일에 저장되기 전에 응답을 메모리에 로드합니다.

다음 스트리밍 코드를 사용하면 다운로드된 파일의 크기에 관계없이 Python 메모리 사용이 제한됩니다.

def download_file(url):
    local_filename = url.split('/')[-1]
    # NOTE the stream=True parameter below
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192): 
                # If you have chunk encoded response uncomment if
                # and set chunk_size parameter to None.
                #if chunk: 
                f.write(chunk)
    return local_filename

반환된 바이트 수는 다음과 같습니다.iter_content꼭 그런 것은 아니다chunk_size; 이것은 종종 훨씬 더 큰 난수가 될 것으로 예상되며, 모든 반복에서 다를 것으로 예상됩니다.

자세한 내용은 body-content-workflowResponse.iter_content를 참조하십시오.

및 을 사용하면 훨씬 쉬워집니다.

import requests
import shutil

def download_file(url):
    local_filename = url.split('/')[-1]
    with requests.get(url, stream=True) as r:
        with open(local_filename, 'wb') as f:
            shutil.copyfileobj(r.raw, f)

    return local_filename

이렇게 하면 과도한 메모리를 사용하지 않고 파일을 디스크로 스트리밍할 수 있으며 코드는 간단합니다.

주의: 설명서에 따르면Response.raw디코딩되지 않음gzip그리고.deflatetransfer-encoding(전송 부호화)을 실시하기 때문에, 수동으로 실시할 필요가 있습니다.

작전본부가 부탁한 건 아니지만...그것을 하는 것은 터무니없이 쉽다urllib:

from urllib.request import urlretrieve

url = 'http://mirror.pnl.gov/releases/16.04.2/ubuntu-16.04.2-desktop-amd64.iso'
dst = 'ubuntu-16.04.2-desktop-amd64.iso'
urlretrieve(url, dst)

또는 임시 파일에 저장하는 경우 다음과 같이 하십시오.

from urllib.request import urlopen
from shutil import copyfileobj
from tempfile import NamedTemporaryFile

url = 'http://mirror.pnl.gov/releases/16.04.2/ubuntu-16.04.2-desktop-amd64.iso'
with urlopen(url) as fsrc, NamedTemporaryFile(delete=False) as fdst:
    copyfileobj(fsrc, fdst)

나는 그 과정을 지켜보았다.

watch 'ps -p 18647 -o pid,ppid,pmem,rsz,vsz,comm,args; ls -al *.iso'

그리고 파일이 커지는 걸 봤는데 메모리 사용량이 17MB에 머물렀어요. 제가 뭘 놓쳤나요?

청크기가 너무 클 수 있습니다.한 번에 1024바이트 정도 드롭해 본 적이 있습니까?(또,with구문을 정리하다)

def DownloadFile(url):
    local_filename = url.split('/')[-1]
    r = requests.get(url)
    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024): 
            if chunk: # filter out keep-alive new chunks
                f.write(chunk)
    return 

덧붙여서, 응답이 메모리에 로드된 것을 어떻게 추측하고 있습니까?

Python이 다른 SO 질문에서 데이터를 파일로 플러시하지 않는 것처럼 들립니다.f.flush()그리고.os.fsync()파일 쓰기와 메모리 빈 공간을 강제합니다.

    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024): 
            if chunk: # filter out keep-alive new chunks
                f.write(chunk)
                f.flush()
                os.fsync(f.fileno())

사용하다wget대신 python 모듈입니다.여기 토막이 있습니다.

import wget
wget.download(url)

위의 Roman의 가장 높은 코멘트에 근거해, 「다운로드 as」와 「재시도」메커니즘을 포함한 나의 실장을 이하에 나타냅니다.

def download(url: str, file_path='', attempts=2):
    """Downloads a URL content into a file (with large file support by streaming)

    :param url: URL to download
    :param file_path: Local file name to contain the data downloaded
    :param attempts: Number of attempts
    :return: New file path. Empty string if the download failed
    """
    if not file_path:
        file_path = os.path.realpath(os.path.basename(url))
    logger.info(f'Downloading {url} content to {file_path}')
    url_sections = urlparse(url)
    if not url_sections.scheme:
        logger.debug('The given url is missing a scheme. Adding http scheme')
        url = f'http://{url}'
        logger.debug(f'New url: {url}')
    for attempt in range(1, attempts+1):
        try:
            if attempt > 1:
                time.sleep(10)  # 10 seconds wait time between downloads
            with requests.get(url, stream=True) as response:
                response.raise_for_status()
                with open(file_path, 'wb') as out_file:
                    for chunk in response.iter_content(chunk_size=1024*1024):  # 1MB chunks
                        out_file.write(chunk)
                logger.info('Download finished successfully')
                return file_path
        except Exception as ex:
            logger.error(f'Attempt #{attempt} failed with error: {ex}')
    return ''

requests좋긴 한데, 어때?socket해결 방법?

def stream_(host):
    import socket
    import ssl
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        context = ssl.create_default_context(Purpose.CLIENT_AUTH)
        with context.wrap_socket(sock, server_hostname=host) as wrapped_socket:
            wrapped_socket.connect((socket.gethostbyname(host), 443))
            wrapped_socket.send(
                "GET / HTTP/1.1\r\nHost:thiscatdoesnotexist.com\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n\r\n".encode())

            resp = b""
            while resp[-4:-1] != b"\r\n\r":
                resp += wrapped_socket.recv(1)
            else:
                resp = resp.decode()
                content_length = int("".join([tag.split(" ")[1] for tag in resp.split("\r\n") if "content-length" in tag.lower()]))
                image = b""
                while content_length > 0:
                    data = wrapped_socket.recv(2048)
                    if not data:
                        print("EOF")
                        break
                    image += data
                    content_length -= len(data)
                with open("image.jpeg", "wb") as file:
                    file.write(image)

다음은 모든 파일 내용을 메모리에 읽지 않고 비동기 청크다운로드의 사용 사례에 대한 추가 접근법입니다.
즉, URL로부터의 판독과 파일에의 기입은 모두,asyncioaiohttp에서 aiofiles을 사용하다

는 ᄃ다에 적용되어야 합니다.Python 3.7그리고 나중에.
만 하면 .SRC_URL ★★★★★★★★★★★★★★★★★」DEST_FILE복사하여 붙여넣기 전에 변수를 선택합니다.

import aiofiles
import aiohttp
import asyncio

async def async_http_download(src_url, dest_file, chunk_size=65536):
    async with aiofiles.open(dest_file, 'wb') as fd:
        async with aiohttp.ClientSession() as session:
            async with session.get(src_url) as resp:
                async for chunk in resp.content.iter_chunked(chunk_size):
                    await fd.write(chunk)

SRC_URL = "/path/to/url"
DEST_FILE = "/path/to/file/on/local/machine"

asyncio.run(async_http_download(SRC_URL, DEST_FILE))

언급URL : https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests