import os
import shutil
from urllib.request import ProxyHandler, build_opener

proxy = 'http://proxy.example.net:8080'
opener = build_opener(ProxyHandler({
    'http': proxy,
    'https': proxy,
}))

temporary = 'archive.zip.part'
destination = 'archive.zip'

with opener.open('https://downloads.example.net/archive.zip',
                 timeout=20) as response:
    with open(temporary, 'wb') as output:
        shutil.copyfileobj(response, output)

os.replace(temporary, destination)

URL とファイル名はアプリケーションに合わせて置き換えてください。プロキシ認証なしで許可された公開ファイルのダウンロードでは、HTTP プロキシ一覧がプロキシ URL の候補エンドポイントを提供できます。この専用の urllib オープナーは、通常の証明書検証を伴う HTTPS 宛先に対して HTTP プロキシを使用します。グローバルなオープナーはインストールしません。認証が必要な場合は、対応する設定が必要です。

copyfileobj はチャンク単位でコピーします。ファイルコンテキストを抜けるとファイルが閉じられ、os.replace が最終名を設定する前にバッファされた書き込みがフラッシュされます。

要求またはコピーで例外が発生した場合、名前の変更はスキップされます。クリーンアップ方針に応じて部分ファイルが残ることがあり、このフローではその失敗時に既存の最終ファイルを変更しません。成功時には置換がその最終ファイルを上書きできるため、方針を意識的に選んでください。

並行ジョブには一意の一時名を使用してください。別々のオープナーオブジェクトは、同じ .part パスを共有するジョブを保護しません。

20 秒の設定は、ダウンロード全体の時間ではなく、対応するソケット操作を制限します。ジョブ全体の期限にはアプリケーション側で別途の処理が必要です。転送の完了も、ダウンロードしたファイルの検証とは別です。

関連記事