1651943760
多くの場合、一部の管理者はファイルのダウンロード速度に制限を設定します。これによりネットワークの負荷が軽減されますが、同時に、特に大きなファイル(1 GBから)をダウンロードする必要がある場合は、ユーザーにとって非常に煩わしいものになります。速度は約1メガビット/秒(125キロバイト/秒)で変動します。これらのデータに基づいて、ダウンロード速度は少なくとも8192秒(2時間16分32秒)になると結論付けます。私たちの帯域幅では最大16Mbps(2 MB /秒)を転送できますが、512秒(8分32秒)かかります。
これらの値は偶然ではありません。そのようなダウンロードでは、最初は4Gインターネットのみを使用していました。
場合:
私が開発し、以下にレイアウトしたユーティリティは、次の場合にのみ機能します。
これらの制限は何ですか?
この減速はどのように実装されますか?
Nginx
location /static/ {
...
limit rate 50k; -> 50 kilobytes per second for a single connection
...
}
location /videos/ {
...
limit rate 500k; -> 500 kilobytes per second for a single connection
limit_rate_after 10m; -> after 10 megabytes download speed, will 500 kilobytes per second for a single connection
...
}
zipファイル付きの機能
zip拡張子のファイルをダウンロードするときに興味深い機能が発見されました。各部分では、アーカイブ内のファイルを部分的に表示できますが、ほとんどのアーカイバは、ファイルが壊れていて無効であると言いますが、コンテンツとファイル名の一部は次のようになります。表示されます。
コード解析:
このプログラムを作成するには、Python、asyncio、aiohttp、aiofilesが必要です。すべてのコードは非同期になり、パフォーマンスが向上し、メモリと速度の観点からオーバーヘッドが最小限に抑えられます。スレッドやプロセスで実行することも可能ですが、大きなファイルをロードするときに、スレッドやプロセスを作成できないときにエラーが発生する可能性があります。
async def get_content_length(url):
async with aiohttp.ClientSession() as session:
async with session.head(url) as request:
return request.content_length
この関数は、ファイルの長さを返します。また、リクエスト自体はGETではなくHEADを使用します。つまり、本文(指定されたURLのコンテンツ)を含まず、ヘッダーのみを取得します。
def parts_generator(size, start=0, part_size=10 * 1024 ** 2):
while size - start > part_size:
yield start, start + part_size
start += part_size
yield start, size
このジェネレーターは、ダウンロード用の範囲を返します。重要な点は、メガバイトあたりの比率を維持するために1024の倍数であるpart_sizeを選択することですが、どの数でもかまいません。part_size = 1では正しく機能しないため、デフォルトでパーツあたり10MBに設定しました。
async def download(url, headers, save_path):
async with aiohttp.ClientSession(headers=headers) as session:
async with session.get(url) as request:
file = await aiofiles.open(save_path, 'wb')
await file.write(await request.content.read())
主な機能の1つは、ファイルのダウンロードです。非同期で動作します。ここでは、入出力操作をブロックしないことでディスク書き込みを高速化するための非同期ファイルが必要です。
async def process(url):
filename = os.path.basename(urlparse(url).path)
tmp_dir = TemporaryDirectory(prefix=filename, dir=os.path.abspath('.'))
size = await get_content_length(url)
tasks = []
file_parts = []
for number, sizes in enumerate(parts_generator(size)):
part_file_name = os.path.join(tmp_dir.name, f'{filename}.part{number}')
file_parts.append(part_file_name)
tasks.append(download(URL, {'Range': f'bytes={sizes[0]}-{sizes[1]}'}, part_file_name))
await asyncio.gather(*tasks)
with open(filename, 'wb') as wfd:
for f in file_parts:
with open(f, 'rb') as fd:
shutil.copyfileobj(fd, wfd)
最も基本的な機能は、URLからファイル名を取得し、それを番号付きの.partファイルに変換し、元のファイルの下に一時ディレクトリを作成し、すべての部分をそのファイルにダウンロードします。await asyncio.gather(* tasks)を使用すると、収集されたすべてのコルーチンを同時に実行できるため、ダウンロードが大幅に高速化されます。その後、すでに同期されているshutil.copyfileobjメソッドは、すべてのファイルを1つのファイルに連結します。
async def main():
if len(sys.argv) <= 1:
print('Add URLS')
exit(1)
urls = sys.argv[1:]
await asyncio.gather(*[process(url) for url in urls])
main関数は、コマンドラインからURLのリストを受け取り、すでにおなじみのasyncio.gatherを使用して、同時に多くのファイルのダウンロードを開始します。
基準:
私が見つけたリソースの1つで、ある大学(低速サーバー)のサイトからGentooLinuxイメージをダウンロードする際のベンチマークが実行されました。
DietPiディストリビューション(高速サーバー)をダウンロードします。
ご覧のとおり、結果はほぼ3倍の加速度に達します。一部のファイルでは、結果が20〜30回に達しました。
考えられる改善:
結論として、非同期ロードは解決策であると言えますが、残念ながら、ファイルのダウンロードに関しては特効薬ではありません。
import asyncio
import os.path
import shutil
import aiofiles
import aiohttp
from tempfile import TemporaryDirectory
import sys
from urllib.parse import urlparse
async def get_content_length(url):
async with aiohttp.ClientSession() as session:
async with session.head(url) as request:
return request.content_length
def parts_generator(size, start=0, part_size=10 * 1024 ** 2):
while size - start > part_size:
yield start, start + part_size
start += part_size
yield start, size
async def download(url, headers, save_path):
async with aiohttp.ClientSession(headers=headers) as session:
async with session.get(url) as request:
file = await aiofiles.open(save_path, 'wb')
await file.write(await request.content.read())
async def process(url):
filename = os.path.basename(urlparse(url).path)
tmp_dir = TemporaryDirectory(prefix=filename, dir=os.path.abspath('.'))
size = await get_content_length(url)
tasks = []
file_parts = []
for number, sizes in enumerate(parts_generator(size)):
part_file_name = os.path.join(tmp_dir.name, f'{filename}.part{number}')
file_parts.append(part_file_name)
tasks.append(download(url, {'Range': f'bytes={sizes[0]}-{sizes[1]}'}, part_file_name))
await asyncio.gather(*tasks)
with open(filename, 'wb') as wfd:
for f in file_parts:
with open(f, 'rb') as fd:
shutil.copyfileobj(fd, wfd)
async def main():
if len(sys.argv) <= 1:
print('Add URLS')
exit(1)
urls = sys.argv[1:]
await asyncio.gather(*[process(url) for url in urls])
if __name__ == '__main__':
import time
start_code = time.monotonic()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
print(f'{time.monotonic() - start_code} seconds!')
ソース:https ://hackernoon.com/how-to-speed-up-file-downloads-with-python
1651943760
多くの場合、一部の管理者はファイルのダウンロード速度に制限を設定します。これによりネットワークの負荷が軽減されますが、同時に、特に大きなファイル(1 GBから)をダウンロードする必要がある場合は、ユーザーにとって非常に煩わしいものになります。速度は約1メガビット/秒(125キロバイト/秒)で変動します。これらのデータに基づいて、ダウンロード速度は少なくとも8192秒(2時間16分32秒)になると結論付けます。私たちの帯域幅では最大16Mbps(2 MB /秒)を転送できますが、512秒(8分32秒)かかります。
これらの値は偶然ではありません。そのようなダウンロードでは、最初は4Gインターネットのみを使用していました。
場合:
私が開発し、以下にレイアウトしたユーティリティは、次の場合にのみ機能します。
これらの制限は何ですか?
この減速はどのように実装されますか?
Nginx
location /static/ {
...
limit rate 50k; -> 50 kilobytes per second for a single connection
...
}
location /videos/ {
...
limit rate 500k; -> 500 kilobytes per second for a single connection
limit_rate_after 10m; -> after 10 megabytes download speed, will 500 kilobytes per second for a single connection
...
}
zipファイル付きの機能
zip拡張子のファイルをダウンロードするときに興味深い機能が発見されました。各部分では、アーカイブ内のファイルを部分的に表示できますが、ほとんどのアーカイバは、ファイルが壊れていて無効であると言いますが、コンテンツとファイル名の一部は次のようになります。表示されます。
コード解析:
このプログラムを作成するには、Python、asyncio、aiohttp、aiofilesが必要です。すべてのコードは非同期になり、パフォーマンスが向上し、メモリと速度の観点からオーバーヘッドが最小限に抑えられます。スレッドやプロセスで実行することも可能ですが、大きなファイルをロードするときに、スレッドやプロセスを作成できないときにエラーが発生する可能性があります。
async def get_content_length(url):
async with aiohttp.ClientSession() as session:
async with session.head(url) as request:
return request.content_length
この関数は、ファイルの長さを返します。また、リクエスト自体はGETではなくHEADを使用します。つまり、本文(指定されたURLのコンテンツ)を含まず、ヘッダーのみを取得します。
def parts_generator(size, start=0, part_size=10 * 1024 ** 2):
while size - start > part_size:
yield start, start + part_size
start += part_size
yield start, size
このジェネレーターは、ダウンロード用の範囲を返します。重要な点は、メガバイトあたりの比率を維持するために1024の倍数であるpart_sizeを選択することですが、どの数でもかまいません。part_size = 1では正しく機能しないため、デフォルトでパーツあたり10MBに設定しました。
async def download(url, headers, save_path):
async with aiohttp.ClientSession(headers=headers) as session:
async with session.get(url) as request:
file = await aiofiles.open(save_path, 'wb')
await file.write(await request.content.read())
主な機能の1つは、ファイルのダウンロードです。非同期で動作します。ここでは、入出力操作をブロックしないことでディスク書き込みを高速化するための非同期ファイルが必要です。
async def process(url):
filename = os.path.basename(urlparse(url).path)
tmp_dir = TemporaryDirectory(prefix=filename, dir=os.path.abspath('.'))
size = await get_content_length(url)
tasks = []
file_parts = []
for number, sizes in enumerate(parts_generator(size)):
part_file_name = os.path.join(tmp_dir.name, f'{filename}.part{number}')
file_parts.append(part_file_name)
tasks.append(download(URL, {'Range': f'bytes={sizes[0]}-{sizes[1]}'}, part_file_name))
await asyncio.gather(*tasks)
with open(filename, 'wb') as wfd:
for f in file_parts:
with open(f, 'rb') as fd:
shutil.copyfileobj(fd, wfd)
最も基本的な機能は、URLからファイル名を取得し、それを番号付きの.partファイルに変換し、元のファイルの下に一時ディレクトリを作成し、すべての部分をそのファイルにダウンロードします。await asyncio.gather(* tasks)を使用すると、収集されたすべてのコルーチンを同時に実行できるため、ダウンロードが大幅に高速化されます。その後、すでに同期されているshutil.copyfileobjメソッドは、すべてのファイルを1つのファイルに連結します。
async def main():
if len(sys.argv) <= 1:
print('Add URLS')
exit(1)
urls = sys.argv[1:]
await asyncio.gather(*[process(url) for url in urls])
main関数は、コマンドラインからURLのリストを受け取り、すでにおなじみのasyncio.gatherを使用して、同時に多くのファイルのダウンロードを開始します。
基準:
私が見つけたリソースの1つで、ある大学(低速サーバー)のサイトからGentooLinuxイメージをダウンロードする際のベンチマークが実行されました。
DietPiディストリビューション(高速サーバー)をダウンロードします。
ご覧のとおり、結果はほぼ3倍の加速度に達します。一部のファイルでは、結果が20〜30回に達しました。
考えられる改善:
結論として、非同期ロードは解決策であると言えますが、残念ながら、ファイルのダウンロードに関しては特効薬ではありません。
import asyncio
import os.path
import shutil
import aiofiles
import aiohttp
from tempfile import TemporaryDirectory
import sys
from urllib.parse import urlparse
async def get_content_length(url):
async with aiohttp.ClientSession() as session:
async with session.head(url) as request:
return request.content_length
def parts_generator(size, start=0, part_size=10 * 1024 ** 2):
while size - start > part_size:
yield start, start + part_size
start += part_size
yield start, size
async def download(url, headers, save_path):
async with aiohttp.ClientSession(headers=headers) as session:
async with session.get(url) as request:
file = await aiofiles.open(save_path, 'wb')
await file.write(await request.content.read())
async def process(url):
filename = os.path.basename(urlparse(url).path)
tmp_dir = TemporaryDirectory(prefix=filename, dir=os.path.abspath('.'))
size = await get_content_length(url)
tasks = []
file_parts = []
for number, sizes in enumerate(parts_generator(size)):
part_file_name = os.path.join(tmp_dir.name, f'{filename}.part{number}')
file_parts.append(part_file_name)
tasks.append(download(url, {'Range': f'bytes={sizes[0]}-{sizes[1]}'}, part_file_name))
await asyncio.gather(*tasks)
with open(filename, 'wb') as wfd:
for f in file_parts:
with open(f, 'rb') as fd:
shutil.copyfileobj(fd, wfd)
async def main():
if len(sys.argv) <= 1:
print('Add URLS')
exit(1)
urls = sys.argv[1:]
await asyncio.gather(*[process(url) for url in urls])
if __name__ == '__main__':
import time
start_code = time.monotonic()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
print(f'{time.monotonic() - start_code} seconds!')
ソース:https ://hackernoon.com/how-to-speed-up-file-downloads-with-python