2023년 4월 2일 일요일

requests 를 이용한 파일 다운로드 하기, file name 정하기

 requests 를 이용할때 http상에 존재하는 파일을 다운로드 하는 방법입니다.

저장할 파일이름 설정도 필요하기 때문에 정리해 보았습니다.


다운로드 하는 파일 이름 구하기 

http 6266 규격의 Content-Disposition: 여기로부터 파일 이름을 구할 수 있습니다.

https://www.rfc-editor.org/rfc/rfc6266

하지만 이 규격은 필수 규격이 아니라 해당 규격대로 오지 않는 경우가 있습니다. 이 부분을 고려해서 구현하였습니다.

pip install pyrfc6266 

pyrfc6266 패키지를 이용하여 requests 결과를 넘기면 파일 이름을 얻을 수 있습니다.

r = requests.get(url, stream=True)
filename = pyrfc6266.requests_response_to_filename(r)


다운로드 하는 파일을 조금씩 저장하기

requests 패키지는 기본적으로 웹페이지 로딩이 완료되면 다음으로 넘어가게 되어 있습니다. 만약 다운로드 하는 파일이 큰 경우 해당 크기 만큼 메모리가 필요하기 때문에 다운로드 안되는 경우도 존재합니다. 

이때 사용하는 것이 stream=True 인자입니다.

r = requests.get(url, stream=True)
for chunk in r.iter_content(chunk_size=1024 * 8):
if chunk:
f.write(chunk)

위와 같은 형태로 특정 크기( chunk ) 단위로 파일 저장이 가능합니다.

파일이 메모리상에만 존재하는 경우가 있기 때문에 flush를 해주는 경우가 안전성을 위해서 좋습니다. 이것을 구현한 것이 아래 코드입니다.

f.flush()
os.fsync(f.fileno())


다음은 전체 코드입니다.

import requests
import os
import pyrfc6266
import urllib.parse


# url 은 get 방식으로 다운로드 하기 위한 주소가 됩니다.
# path 는 다운로드 경로가 됩니다.
# 성공시 Full path 파일 이름이 넘어옵니다.
def download_get(url, dest_folder="."):
    if not os.path.exists(dest_folder):
        os.makedirs(dest_folder)  # create folder if it does not exist
    r = requests.get(url, stream=True)
    filename = pyrfc6266.requests_response_to_filename(r)
    print(r.headers, filename)
    if filename == "" or filename is None:
        filename = url.split('/')[-1].replace(" ", "_")
    elif '%' in filename:
        filename = urllib.parse.unquote(filename)

    file_path = os.path.join(dest_folder, filename)

    if r.ok:
        print("saving to", os.path.abspath(file_path))
        with open(file_path, 'wb') as f:
            for chunk in r.iter_content(chunk_size=1024 * 512):
                if chunk:
                    f.write(chunk)
                    f.flush()
                    os.fsync(f.fileno())
    else:
        print("Download failed: status code {}\n{}".format(r.status_code, r.text))
        return None
    return file_path

if __name__ == "__main__":
    url = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png'
    print(download_get(url, "."))
    url = 'https://cdn.hancom.com/link/docs/%ED%95%9C%EA%B8%80%EB%AC%B8%EC%84%9C%ED%8C%8C%EC%9D%BC%ED%98%95%EC%8B%9D3.0_HWPML_revision1.2.hwp'
    print(download_get(url, "."))


실행 결과

{'Accept-Ranges': 'bytes', 'Content-Type': 'image/png', 'Cross-Origin-Resource-Policy': 'cross-origin', 'Cross-Origin-Opener-Policy-Report-Only': 'same-origin; report-to="static-on-bigtable"', 'Report-To': '{"group":"static-on-bigtable","max_age":2592000,"endpoints":[{"url":"https://csp.withgoogle.com/csp/report-to/static-on-bigtable"}]}', 'Content-Length': '5969', 'Date': 'Sun, 02 Apr 2023 10:07:31 GMT', 'Expires': 'Sun, 02 Apr 2023 10:07:31 GMT', 'Cache-Control': 'private, max-age=31536000', 'Last-Modified': 'Tue, 22 Oct 2019 18:30:00 GMT', 'X-Content-Type-Options': 'nosniff', 'Server': 'sffe', 'X-XSS-Protection': '0', 'Alt-Svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000'} googlelogo_color_272x92dp.png
saving to C:\Users\Documents\GitHub\sourcecode\python\example\_52_requests\googlelogo_color_272x92dp.png
.\googlelogo_color_272x92dp.png
{'Content-Type': 'application/octet-stream', 'Accept-Ranges': 'bytes', 'X-Agile-Brick-Id': '480527996', 'X-Agile-Checksum': '1121cdabe5929d7aa68704d0516488852bcdc78b4f4b368ad054e8c121d04023', 'X-Agile-Request-Id': '5aaf564a732da062c1c10bf592122357, 24385af8bc6afbe50e767494734b7cfb', 'X-Agile-Source': '111.119.25.183:1987', 'Server': 'CloudStorage', 'Age': '188002', 'Date': 'Sun, 02 Apr 2023 10:07:32 GMT', 'Last-Modified': 'Mon, 10 Nov 2014 01:08:00 GMT', 'X-LLID': '524b2b419aadef6e7827af6fb89c006a', 'Content-Length': '1310720', 'Access-Control-Allow-Origin': '*'} %ED%95%9C%EA%B8%80%EB%AC%B8%EC%84%9C%ED%8C%8C%EC%9D%BC%ED%98%95%EC%8B%9D3.0_HWPML_revision1.2.hwp
saving to C:\Users\Documents\GitHub\sourcecode\python\example\_52_requests\한글문서파일형식3.0_HWPML_revision1.2.hwp
.\한글문서파일형식3.0_HWPML_revision1.2.hwp


댓글 없음:

댓글 쓰기