요청이 있는 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-workflow 및 Response.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
그리고.deflate
transfer-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로부터의 판독과 파일에의 기입은 모두,asyncio
aiohttp
에서 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
'programing' 카테고리의 다른 글
3가지 다른 점은 (0) | 2022.09.25 |
---|---|
값 오류:요소가 두 개 이상인 배열의 참값 값이 모호합니다.a.any() 또는 a.all()을 사용합니다. (0) | 2022.09.25 |
Ruby on Rails 3 소켓 '/tmp/mysql'을 통해 로컬 MySQL 서버에 연결할 수 없습니다.OSX에서 '삭 (0) | 2022.09.25 |
C에서 extern 키워드를 올바르게 사용하는 방법 (0) | 2022.09.25 |
MYSQL을 사용하여 난수 생성 (0) | 2022.09.25 |