web-dev-qa-db-ja.com

Python非同期/ URLのリストのダウンロードを待つ

FTPサーバーから30,000を超えるファイルをダウンロードしようとしていますが、非同期IO 。どんな助けにも本当に感謝します!ありがとう!

class pdb:
    def __init__(self):
        self.ids = []
        self.dl_id = []
        self.err_id = []


    async def download_file(self, session, url):
        try:
            with async_timeout.timeout(10):
                async with session.get(url) as remotefile:
                    if remotefile.status == 200:
                        data = await remotefile.read()
                        return {"error": "", "data": data}
                    else:
                        return {"error": remotefile.status, "data": ""}
        except Exception as e:
            return {"error": e, "data": ""}

    async def unzip(self, session, work_queue):
        while not work_queue.empty():
            queue_url = await work_queue.get()
            print(queue_url)
            data = await self.download_file(session, queue_url)
            id = queue_url[-11:-7]
            ID = id.upper()
            if not data["error"]:
                saved_pdb = os.path.join("./pdb", ID, f'{ID}.pdb')
                if ID not in self.dl_id:
                    self.dl_id.append(ID)
                with open(f"{id}.ent.gz", 'wb') as f:
                    f.write(data["data"].read())
                with gzip.open(f"{id}.ent.gz", "rb") as inFile, open(saved_pdb, "wb") as outFile:
                    shutil.copyfileobj(inFile, outFile)
                os.remove(f"{id}.ent.gz")
            else:
                self.err_id.append(ID)

    def download_queue(self, urls):
        loop = asyncio.get_event_loop()
        q = asyncio.Queue(loop=loop)
        [q.put_nowait(url) for url in urls]
        con = aiohttp.TCPConnector(limit=10)
        with aiohttp.ClientSession(loop=loop, connector=con) as session:
            tasks = [asyncio.ensure_future(self.unzip(session, q)) for _ in range(len(urls))]
            loop.run_until_complete(asyncio.gather(*tasks))
        loop.close()

tryの部分を削除した場合のエラーメッセージ:

トレースバック(最新の呼び出しが最後):
ファイル「test.py」、111行目、
x.download_queue(urls)
download_queueのファイル「test.py」、99行目
loop.run_until_complete(asyncio.gather(* tasks))
run_until_completeのファイル「/home/yz/miniconda3/lib/python3.6/asyncio/base_events.py」、467行目
return future.result()
ファイル「test.py」、73行目、解凍
data = await self.download_file(session、queue_url)
download_file内のファイル「test.py」、65行目
return {"error":remotefile.status、 "data": ""}
ファイル "/home/yz/miniconda3/lib/python3.6/site- packages/async_timeout/init。py"、46行目exit
rasync asyncio.TimeoutError from None
concurrent.futures._base.TimeoutError

10
Yi Zhou
tasks = [asyncio.ensure_future(self.unzip(session, q)) for _ in range(len(urls))]
loop.run_until_complete(asyncio.gather(*tasks))

ここで、すべてのURLを同時にダウンロードするプロセスを開始します。これは、それらすべてのタイムアウトのカウントも開始することを意味します。 30,000などの大きな数値になると、ネットワーク/ RAM/CPUの容量のため、10秒以内に物理的に実行することはできません。

この状況を回避するには、同時に開始されるコルーチンの制限を保証する必要があります。通常、 asyncio.Semaphore のような同期プリミティブを使用してこれを実現できます。

次のようになります。

sem = asyncio.Semaphore(10)

# ...

async def download_file(self, session, url):
    try:
        async with sem:  # Don't start next download until 10 other currently running
            with async_timeout.timeout(10):
11

@MikhailGerasimovのセマフォアプローチの代わりに、 aiostream.stream.map 演算子の使用を検討できます。

from aiostream import stream, pipe

async def main(urls):
    async with aiohttp.ClientSession() as session:
        ws = stream.repeat(session)
        xs = stream.Zip(ws, stream.iterate(urls))
        ys = stream.starmap(xs, fetch, ordered=False, task_limit=10)
        zs = stream.map(ys, process)
        await zs

パイプを使用した同等の実装は次のとおりです。

async def main3(urls):
    async with aiohttp.ClientSession() as session:
        await (stream.repeat(session)
               | pipe.Zip(stream.iterate(urls))
               | pipe.starmap(fetch, ordered=False, task_limit=10)
               | pipe.map(process))

次のコルーチンでテストできます。

async def fetch(session, url):
    await asyncio.sleep(random.random())
    return url

async def process(data):
    print(data)

この demonstration および documentation のaiostreamの例を参照してください。

免責事項:私はプロジェクトのメンテナーです。

2
Vincent