Pythonで100,000のHTTPリクエストを送信する最も速い方法は何ですか?


286

100,000のURLを持つファイルを開いています。各URLにHTTPリクエストを送信し、ステータスコードを出力する必要があります。私はPython 2.6を使用していますが、これまでのところ、Pythonがスレッド化/並行処理を実装する多くの混乱する方法を見てきました。私はpythonの並行処理ライブラリも調べましたが、このプログラムを正しく作成する方法を理解できません。誰かが同様の問題に遭遇しましたか?私は一般に、Pythonで何千ものタスクを可能な限り高速に実行する方法を知る必要があると思います-それは「並行して」を意味すると思います。


47
HEADリクエストのみを実行するようにしてください(ドキュメント全体をダウンロードしないようにしてください)。参照:stackoverflow.com/questions/107405/...
Tarnayカールマン

5
優れた点、カルミ。Igorが必要とするのが要求のステータスだけである場合、これらの100Kの要求ははるかに速くなります。はるかに速い。
アダムクロスランド2010

1
これにはスレッドは必要ありません。最も効率的な方法は、Twistedのような非同期ライブラリを使用することです。
jemfinch 2010

3
ここにいるgevent、ねじれた、とasyncioベースのコード例(1000000回のリクエストでテスト)
JFS

4
@TarnayKálmánは、さまざまなステータスコードを返す可能性がrequests.getありますrequests.head(つまり、ページリクエストとヘッドリクエスト)。これは最善のアドバイスではありません
AlexG

回答:


201

ねじれのないソリューション:

from urlparse import urlparse
from threading import Thread
import httplib, sys
from Queue import Queue

concurrent = 200

def doWork():
    while True:
        url = q.get()
        status, url = getStatus(url)
        doSomethingWithResult(status, url)
        q.task_done()

def getStatus(ourl):
    try:
        url = urlparse(ourl)
        conn = httplib.HTTPConnection(url.netloc)   
        conn.request("HEAD", url.path)
        res = conn.getresponse()
        return res.status, ourl
    except:
        return "error", ourl

def doSomethingWithResult(status, url):
    print status, url

q = Queue(concurrent * 2)
for i in range(concurrent):
    t = Thread(target=doWork)
    t.daemon = True
    t.start()
try:
    for url in open('urllist.txt'):
        q.put(url.strip())
    q.join()
except KeyboardInterrupt:
    sys.exit(1)

これはツイストソリューションよりもわずかに高速で、CPU使用量も少なくなります。


10
@カルミ、なぜキューをに設定するのconcurrent*2ですか?
Marcel Wilson、

8
接続閉じることを忘れないでくださいconn.close()。開いているHTTP接続が多すぎると、スクリプトが途中で停止し、メモリを消費する場合があります。
アーミルアドナン2014年

4
@hyh、QueueモジュールはqueuePython 3で名前が変更されました。これはPython 2コードです。
TarnayKálmán2014

3
接続を持続させることにより、毎回同じサーバーと通信したい場合、どれだけ速く行くことができますか?これは、スレッド間で、またはスレッドごとに1つの永続的な接続で行うこともできますか?
mdurant、2015年

2
@ mptevsion、CPythonを使用している場合、(たとえば) "print status、url"を "my_global_list.append((status、url))"で置き換えることができます。(ほとんどの操作)リストは、GILによりCPython(および他の一部のpython実装)で暗黙的にスレッドセーフであるため、安全に実行できます。
TarnayKálmán2016

54

トルネード非同期ネットワークライブラリを使用したソリューション

from tornado import ioloop, httpclient

i = 0

def handle_request(response):
    print(response.code)
    global i
    i -= 1
    if i == 0:
        ioloop.IOLoop.instance().stop()

http_client = httpclient.AsyncHTTPClient()
for url in open('urls.txt'):
    i += 1
    http_client.fetch(url.strip(), handle_request, method='HEAD')
ioloop.IOLoop.instance().start()

7
このコードはノンブロッキングネットワークI / Oを使用しており、制限はありません。数万のオープン接続に拡張できます。単一のスレッドで実行されますが、どのスレッドソリューションよりも高速です。チェックアウトノンブロッキングI / O en.wikipedia.org/wiki/Asynchronous_I/O
2014

1
ここでグローバルi変数で何が起こっているのか説明できますか?何らかのエラーチェック?
LittleBobbyTables

4
これは、「ioloop」を終了するタイミングを決定するためのカウンターです。
Michael Dorner、2015

1
@AndrewScottEvansは、Python 2.7およびプロキシを使用していることを前提としています
Dejell

5
@ガイアヴラハムあなたのddos計画の助けを得て幸運。
Walter

50

これが投稿された2010年以降、状況はかなり変化しており、他のすべての回答を試したことはありませんが、いくつか試しましたが、python3.6を使用すると、これが最もうまく機能することがわかりました。

AWSで実行されている毎秒約150の一意のドメインをフェッチできました。

import pandas as pd
import concurrent.futures
import requests
import time

out = []
CONNECTIONS = 100
TIMEOUT = 5

tlds = open('../data/sample_1k.txt').read().splitlines()
urls = ['http://{}'.format(x) for x in tlds[1:]]

def load_url(url, timeout):
    ans = requests.head(url, timeout=timeout)
    return ans.status_code

with concurrent.futures.ThreadPoolExecutor(max_workers=CONNECTIONS) as executor:
    future_to_url = (executor.submit(load_url, url, TIMEOUT) for url in urls)
    time1 = time.time()
    for future in concurrent.futures.as_completed(future_to_url):
        try:
            data = future.result()
        except Exception as exc:
            data = str(type(exc))
        finally:
            out.append(data)

            print(str(len(out)),end="\r")

    time2 = time.time()

print(f'Took {time2-time1:.2f} s')
print(pd.Series(out).value_counts())

1
私は知らないので質問しているだけですが、この先物は非同期/待機に置き換えることができますか?
TankorSmash 2017年

1
それは可能ですが、上記の方がうまくいくことがわかりました。あなたはaiohttpを使うことができますが、それは標準libの一部ではなく、かなり大きく変化しています。それは機能しますが、私はそれが同様に機能することを見つけられませんでした。使用するとエラー率が高くなります。理論上は問題なく動作するようですが、コンカレントフューチャーと同様に動作しません。stackoverflow.com / questions / 45800857 / それがうまく機能するようになったら、私がテストできるように回答を投稿してください。
グレントンプソン

1
これはちょっとしたテクニックですがtime1 = time.time()、forループの先頭とforループのtime2 = time.time()直後に配置する方がずっとクリーンだと思います。
マットM.

私はあなたのスニペットをテストしました、どういうわけかそれは2回実行されます。私は何か間違ったことをしていますか?それとも2回実行することを意図していますか?後者の場合、どのようにして2回トリガーされるかを理解するのにも役立ちますか?
ロニー

1
2回実行しないでください。なぜあなたはそれを見ているのかわからない。
グレントンプソン

40

スレッドはここでの答えではありません。それらは、プロセスとカーネルの両方のボトルネックを提供するだけでなく、全体的な目標が「最速の方法」である場合は許容できないスループットの制限を提供します。

少しtwistedとその非同期HTTPクライアントははるかに良い結果を与えるでしょう。


ironfroggy:私はあなたの感情に傾いています。スレッドとキュー(自動ミューテックス用)を使用してソリューションを実装しようとしましたが、キューに100,000個のデータを挿入するのにどれくらい時間がかかるか想像できますか?私はまだこのスレッドのみんなからさまざまなオプションや提案をいじっています。おそらくTwistedが良い解決策になるでしょう。
IgorGanapolsky 2010

2
キューに100k個のデータが入るのを回避できます。入力から一度に1つずつアイテムを処理し、スレッドを起動して各アイテムに対応するリクエストを処理します。(私は以下の記述通り、あなたのスレッド数がある閾値を下回った場合、HTTPリクエストのスレッドを起動するランチャースレッドを使用スレッドはリストへの応答、またはアペンドタプルにdictのマッピングURLに結果を書き出すください。。)
エリックをギャリソン

ironfroggy:また、Pythonスレッドを使用して見つけたボトルネックについて知りたいのですが。また、PythonスレッドはOSカーネルとどのように相互作用しますか?
エリックギャリソン

epoll reactorを必ずインストールしてください。そうしないと、select / pollを使用することになり、非常に遅くなります。また、実際に100,000の接続を同時に開こうとする場合(プログラムがそのように記述されていて、URLが異なるサーバー上にあると想定)、OSが不足しないように調整する必要があります。ファイル記述子、エフェメラルポートなど(一度に未処理の接続が10,000を超えないようにするだけの方が簡単です)。
マークノッティンガム

erikg:素晴らしいアイデアをお勧めしました。ただし、200スレッドで達成できた最良の結果は約でした。6分。短時間でこれを達成する方法があると確信しています...マークN:Twistedが私が決断する方法である場合、epoll reactorは確かに役に立ちます。ただし、スクリプトを複数のマシンから実行する場合、各マシンにTwistedをインストールする必要はありませんか?上司にそのルートに行くように説得できるかどうかわかりません...
IgorGanapolsky

20

これは古い質問ですが、Python 3.7ではasyncioand を使用してこれを行うことができますaiohttp

import asyncio
import aiohttp
from aiohttp import ClientSession, ClientConnectorError

async def fetch_html(url: str, session: ClientSession, **kwargs) -> tuple:
    try:
        resp = await session.request(method="GET", url=url, **kwargs)
    except ClientConnectorError:
        return (url, 404)
    return (url, resp.status)

async def make_requests(urls: set, **kwargs) -> None:
    async with ClientSession() as session:
        tasks = []
        for url in urls:
            tasks.append(
                fetch_html(url=url, session=session, **kwargs)
            )
        results = await asyncio.gather(*tasks)

    for result in results:
        print(f'{result[1]} - {str(result[0])}')

if __name__ == "__main__":
    import pathlib
    import sys

    assert sys.version_info >= (3, 7), "Script requires Python 3.7+."
    here = pathlib.Path(__file__).parent

    with open(here.joinpath("urls.txt")) as infile:
        urls = set(map(str.strip, infile))

    asyncio.run(make_requests(urls=urls))

詳細については、こちらの例をご覧ください


これはC# async / awaitやKotlinコルーチンに似ていますか?
IgorGanapolsky

@IgorGanapolsky、はい、C#async / awaitとよく似ています。私はコトリンコルーチンに詳しくありません。
MariusStănescu19年

@sandyp、それが機能するかどうかはわかりませんが、試したい場合はaiohttpにUnixConnectorを使用する必要があります。詳細については、docs.aiohttp.org / en / stable / client_reference.html#connectorsをご覧ください。
MariusStănescu19年

@MariusStănescuに感謝します。それはまさに私が使用したものです。
Sandyp

asyncio.gather(* tasks)を表示するための+1。ここではそのようなスニペットは、私が使用していることである: urls= [fetch(construct_fetch_url(u),idx) for idx, u in enumerate(some_URI_list)] results = await asyncio.gather(*urls)
アシュビニークマー

19

grequestsを使用します。これは、リクエストとGeventモジュールの組み合わせです。

GRequestsを使用すると、Geventでリクエストを使用して非同期のHTTPリクエストを簡単に作成できます。

使い方は簡単です:

import grequests

urls = [
   'http://www.heroku.com',
   'http://tablib.org',
   'http://httpbin.org',
   'http://python-requests.org',
   'http://kennethreitz.com'
]

未送信のリクエストのセットを作成します。

>>> rs = (grequests.get(u) for u in urls)

それらをすべて同時に送信します。

>>> grequests.map(rs)
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]

7
geventがpython 3をサポートするようになりました
Benjamin Toueg

14
grequestsは通常のリクエストの一部ではなく、ほとんどメンテナンスされていないようです
Thom

8

この問題を解決する良い方法は、最初に1つの結果を得るのに必要なコードを記述し、次にスレッド化コードを組み込んでアプリケーションを並列化することです。

完璧な世界では、これは単に100,000スレッドを同時に開始して、結果を後の処理のためにディクショナリまたはリストに出力することを意味しますが、実際には、この方法で発行できる並列HTTP要求の数に制限があります。ローカルでは、同時に開くことができるソケットの数、Pythonインタープリターが許可する実行のスレッドの数に制限があります。リモートでは、すべてのリクエストが1つのサーバーに対するものである場合、または多数のサーバーに対するものである場合、同時接続数が制限される可能性があります。これらの制限により、一度にURLのごく一部のみをポーリングするような方法でスクリプトを作成することがおそらく必要になります(別の投稿者が述べたように、100はおそらくまともなスレッドプールサイズですが、より多くを正常に展開できます)。

この設計パターンに従って、上記の問題を解決できます。

  1. 現在実行中のスレッドの数(threading.active_count()またはスレッドオブジェクトをデータ構造にプッシュすることで追跡できます)が> =同時リクエストの最大数(たとえば100)になるまで、新しいリクエストスレッドを起動するスレッドを開始します。 、その後、短いタイムアウトの間スリープします。このスレッドは、処理するURLがなくなると終了します。したがって、スレッドは起動し続け、新しいスレッドを起動し、終了するまでスリープします。
  2. 要求スレッドに結果を何らかのデータ構造に保存して、後で取得して出力できるようにします。結果を保存する構造がlistまたはdictCPythonの場合、ロックなしでスレッドから一意のアイテムを安全に追加または挿入できますが、ファイルに書き込む場合、またはより複雑なスレッド間データの相互作用が必要場合は、この状態を破損から保護するための相互排他ロック

スレッディングモジュールを使用することをお勧めします。これを使用して、実行中のスレッドを起動および追跡できます。Pythonのスレッディングサポートは完全なものではありませんが、問題の説明から、ニーズに対して完全に十分であることがわかります。

最後に、Pythonで作成された並列ネットワークアプリケーションの非常に単純なアプリケーションを見たい場合は、ssh.pyをチェックしてください。これは、Pythonスレッドを使用して多くのSSH接続を並列化する小さなライブラリです。設計は、それが優れたリソースであることがわかるかもしれない要件に十分に近いです。


1
erikg:キューを式に投入することは合理的でしょうか(相互排他ロックの場合)?PythonのGILは何千ものスレッドで遊ぶことに向けられていないのではないかと思います。
IgorGanapolsky 2010

スレッドの生成が多すぎるのを防ぐために相互排他ロックが必要なのはなぜですか。私はその言葉を誤解していると思います。スレッドキューで実行中のスレッドを追跡し、完了時に削除して、上記のスレッド制限まで追加することができます。ただし、問題のような単純なケースでは、現在のPythonプロセスでアクティブなスレッドの数を監視し、それがしきい値を下回るまで待ってから、前述のしきい値までさらにスレッドを起動することもできます。これを暗黙のロックと見なすことができると思いますが、明示的なロックは必要ありません。
エリックギャリソン

erikg:複数のスレッドが状態を共有しませんか?O'Reillyの本「Python for Unix and Linux System Administration」の305ページに、「...キューなしでスレッドを使用すると、多くの人が実際に処理できるよりも複雑になります。常にキューイングを使用する方がはるかに良いアイデアです。スレッドを使用する必要がある場合は、モジュールを使用してください。理由は、キューモジュール自体がミューテックスによって内部的にすでに保護されているため、キューモジュールもミューテックスでデータを明示的に保護する必要性を軽減するためです。」繰り返しますが、これについてのあなたの見解を歓迎します。
IgorGanapolsky 2010

イゴール:あなたは鍵を使うべきだというのは絶対に正しいです。これを反映するように投稿を編集しました。そうは言っても、Pythonの実際の経験では、list.appendやハッシュキーの追加などによって、スレッドからアトミックに変更するデータ構造をロックする必要はないことが示唆されています。その理由は、list.appendなどの操作にある程度の原子性を提供するGILにあると思います。私は現在、これを確認するためのテストを実行しています(10kのスレッドを使用して0から9999の番号をリストに追加し、すべての追加が機能することを確認します)。ほぼ100回の繰り返しの後、テストは失敗していません。
エリックギャリソン

イゴール:私はこのトピックに関する別の質問をしています: stackoverflow.com/questions/2740435/...
エリック・ギャリソン

7

可能な限り最高のパフォーマンスを得たい場合は、スレッドではなく非同期I / Oの使用を検討してください。数千のOSスレッドに関連するオーバーヘッドは重要なものであり、Pythonインタープリター内でのコンテキストの切り替えにより、さらに多くのものが追加されます。スレッド化は確かに仕事を成し遂げますが、非同期ルートが全体的なパフォーマンスを向上させると私は思っています。

具体的には、Twistedライブラリ(http://www.twistedmatrix.com)の非同期Webクライアントをお勧めします。学習曲線は確かに急ですが、Twistedの非同期プログラミングスタイルをうまく理解すれば、とても簡単に使用できます。

Twistedの非同期WebクライアントAPIのハウツーは、次の場所にあります。

http://twistedmatrix.com/documents/current/web/howto/client.html


Rakis:現在、非同期および非ブロッキングI / Oを調査し​​ています。実装する前に、もっとよく学ぶ必要があります。私があなたの投稿に付け加えたいコメントの1つは、(少なくとも私のLinuxディストリビューションでは)「数千のOSスレッド」を生成することは不可能だということです。Pythonがプログラムを中断する前にスポーンできるスレッドの最大数があります。そして、私の場合には(CentOSの5上)スレッドの最大数は303です
IgorGanapolsky

知っておくと便利です。一度にPythonで一握り以上のスポーンを試みたことはありませんが、爆撃する前にそれ以上のものを作成できると期待していました。
Rakis

6

解決策:

from twisted.internet import reactor, threads
from urlparse import urlparse
import httplib
import itertools


concurrent = 200
finished=itertools.count(1)
reactor.suggestThreadPoolSize(concurrent)

def getStatus(ourl):
    url = urlparse(ourl)
    conn = httplib.HTTPConnection(url.netloc)   
    conn.request("HEAD", url.path)
    res = conn.getresponse()
    return res.status

def processResponse(response,url):
    print response, url
    processedOne()

def processError(error,url):
    print "error", url#, error
    processedOne()

def processedOne():
    if finished.next()==added:
        reactor.stop()

def addTask(url):
    req = threads.deferToThread(getStatus, url)
    req.addCallback(processResponse, url)
    req.addErrback(processError, url)   

added=0
for url in open('urllist.txt'):
    added+=1
    addTask(url.strip())

try:
    reactor.run()
except KeyboardInterrupt:
    reactor.stop()

テスト時間:

[kalmi@ubi1:~] wc -l urllist.txt
10000 urllist.txt
[kalmi@ubi1:~] time python f.py > /dev/null 

real    1m10.682s
user    0m16.020s
sys 0m10.330s
[kalmi@ubi1:~] head -n 6 urllist.txt
http://www.google.com
http://www.bix.hu
http://www.godaddy.com
http://www.google.com
http://www.bix.hu
http://www.godaddy.com
[kalmi@ubi1:~] python f.py | head -n 6
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu

Pingtime:

bix.hu is ~10 ms away from me
godaddy.com: ~170 ms
google.com: ~30 ms

6
Twistedをスレッドプールとして使用すると、Twistedから得られる利点のほとんどが無視されます。代わりに非同期HTTPクライアントを使用する必要があります。
Jean-Paul Calderone、

1

スレッドプールを使用することは良いオプションであり、これはかなり簡単になります。残念ながら、Pythonにはスレッドプールを非常に簡単にする標準ライブラリがありません。しかし、これはあなたが始めるべききちんとしたライブラリです:http : //www.chrisarndt.de/projects/threadpool/

彼らのサイトからのコード例:

pool = ThreadPool(poolsize)
requests = makeRequests(some_callable, list_of_args, callback)
[pool.putRequest(req) for req in requests]
pool.wait()

お役に立てれば。


ThreadPool(poolsize、q_size = 1000)のようにThreadPoolにq_sizeを指定することをお勧めします。これにより、メモリ内に100000のWorkRequestオブジェクトが保持されなくなります。「q_size> 0の場合、作業要求キューのサイズは制限されており、キューがいっぱいになるとスレッドプールがブロックし、スレッドにputRequest正のtimeout値を使用しない限り、スレッドにさらに多くの作業要求を入れようとします(メソッドを参照)putRequest。」
TarnayKálmán2010

これまでのところ、提案されているように、スレッドプールソリューションを実装しようとしています。ただし、makeRequests関数のパラメーターリストがわかりません。some_callable、list_of_args、callbackとは何ですか?おそらく、私が役立つであろう実際のコードスニペットを見た場合。そのライブラリの作者がどんな例も投稿しなかったことに私は驚いています。
IgorGanapolsky 2010

some_callableは、すべての作業が行われる関数です(httpサーバーへの接続)。list_of_argsは、some_callabeに渡される引数です。コールバックは、ワーカースレッドが完了したときに呼び出される関数です。これは、2つの引数、workerオブジェクト(実際に自分自身を気にする必要はありません)、およびworkerが取得した結果を取ります。
Kevin Wiskia

1

epollオブジェクトを作成し、
多くのクライアントTCPソケットを開き
、送信バッファーを要求ヘッダーよりも少し大きくなるように調整し、要求ヘッダーを
送信します—バッファーに配置するだけで、epollオブジェクトにソケットを登録し、オブジェクトに
実行.pollepoll
最初に読み取ります3バイトから各ソケットから.poll
にそれらを書くsys.stdoutに続いて\n(フラッシュません)、近いクライアントソケット。

同時に開くソケットの数を制限します—ソケットの作成時にエラーを処理します。別のソケットが閉じている場合にのみ、新しいソケットを作成します。
OSの制限を調整します。
少数の(多くはない)プロセスに分岐してみてください。これは、CPUを少し効果的に使用するのに役立ちます。


@IgorGanapolskyする必要があります。それ以外の場合は驚きます。しかし、確かに実験が必要です。
George Sovetov

0

あなたのケースでは、おそらく応答を待つのにほとんどの時間を費やしているので、スレッド化はおそらくうまくいくでしょう。標準ライブラリには、役立つようなQueueのような便利なモジュールがあります。

以前にファイルの並列ダウンロードで同様のことを行いましたが、それで十分でしたが、あなたが話している規模ではありませんでした。

タスクがより多くのCPUにバインドされている場合は、マルチプロセッシングモジュールを確認すると、より多くのCPU /コア/スレッドを使用できるようになります(ロックはプロセスごとに行われるため、お互いをブロックしないより多くのプロセス)。


私が言及したい唯一のことは、複数のプロセスを生成することは、複数のスレッドを生成することよりも費用がかかる可能性があるということです。また、複数のプロセスではなく複数のスレッドで100,000のHTTPリクエストを送信しても、明確なパフォーマンスの向上はありません。
IgorGanapolsky 2010

0

Windmillの使用を検討してください。ただし、Windmillは、それほど多くのスレッドを実行することはできません。

5台のマシンで手動でローリングしたPythonスクリプトを使用して、それぞれがポート40000から60000を使用してアウトバウンドに接続し、100,000のポート接続を開くことができます。

また、 各サーバーが処理できる量を把握するために、OpenSTAなどの適切にスレッド化されたQAアプリでサンプルテストを行うと役立つ場合があります。

また、LWP :: ConnCacheクラスで単純なPerlを使用することも検討してください。そうすれば、おそらくより多くのパフォーマンス(より多くの接続)が得られます。


0

このねじれた非同期Webクライアントは、かなり高速になります。

#!/usr/bin/python2.7

from twisted.internet import reactor
from twisted.internet.defer import Deferred, DeferredList, DeferredLock
from twisted.internet.defer import inlineCallbacks
from twisted.web.client import Agent, HTTPConnectionPool
from twisted.web.http_headers import Headers
from pprint import pprint
from collections import defaultdict
from urlparse import urlparse
from random import randrange
import fileinput

pool = HTTPConnectionPool(reactor)
pool.maxPersistentPerHost = 16
agent = Agent(reactor, pool)
locks = defaultdict(DeferredLock)
codes = {}

def getLock(url, simultaneous = 1):
    return locks[urlparse(url).netloc, randrange(simultaneous)]

@inlineCallbacks
def getMapping(url):
    # Limit ourselves to 4 simultaneous connections per host
    # Tweak this number, but it should be no larger than pool.maxPersistentPerHost 
    lock = getLock(url,4)
    yield lock.acquire()
    try:
        resp = yield agent.request('HEAD', url)
        codes[url] = resp.code
    except Exception as e:
        codes[url] = str(e)
    finally:
        lock.release()


dl = DeferredList(getMapping(url.strip()) for url in fileinput.input())
dl.addCallback(lambda _: reactor.stop())

reactor.run()
pprint(codes)

0

tornadoこれを実現するには、パッケージを使用するのが最も速くて簡単な方法であることがわかりました。

from tornado import ioloop, httpclient, gen


def main(urls):
    """
    Asynchronously download the HTML contents of a list of URLs.
    :param urls: A list of URLs to download.
    :return: List of response objects, one for each URL.
    """

    @gen.coroutine
    def fetch_and_handle():
        httpclient.AsyncHTTPClient.configure(None, defaults=dict(user_agent='MyUserAgent'))
        http_client = httpclient.AsyncHTTPClient()
        waiter = gen.WaitIterator(*[http_client.fetch(url, raise_error=False, method='HEAD')
                                    for url in urls])
        results = []
        # Wait for the jobs to complete
        while not waiter.done():
            try:
                response = yield waiter.next()
            except httpclient.HTTPError as e:
                print(f'Non-200 HTTP response returned: {e}')
                continue
            except Exception as e:
                print(f'An unexpected error occurred querying: {e}')
                continue
            else:
                print(f'URL \'{response.request.url}\' has status code <{response.code}>')
                results.append(response)
        return results

    loop = ioloop.IOLoop.current()
    web_pages = loop.run_sync(fetch_and_handle)

    return web_pages

my_urls = ['url1.com', 'url2.com', 'url100000.com']
responses = main(my_urls)
print(responses[0])

-2

最も簡単な方法は、Pythonの組み込みスレッドライブラリを使用することです。それらは「実際の」/カーネルスレッドではありません(シリアル化などの)問題がありますが、十分です。キューとスレッドプールが必要です。ここに1つのオプションがありますが、独自に作成するのは簡単です。100,000件の呼び出しをすべて並列化することはできませんが、同時に100件程度の呼び出しを実行できます。


7
例えばRubyのスレッドとは対照的に、Pythonのスレッドは非常にリアルです。内部的には、少なくともUnix / LinuxとWindowsではネイティブOSスレッドとして実装されています。たぶん、あなたはGILについて言及しているかもしれませんが、それによってスレッドが現実味を
失う

2
EliはPythonのスレッドについては正しいですが、スレッドプールを使用したいというPestilenceの主張も正しいです。この場合、最後に行うことは、100Kの要求ごとに別々のスレッドを同時に開始することです。
アダムクロスランド2010

1
イゴール、コメントにコードスニペットをうまく投稿することはできませんが、質問を編集してそこに追加することはできます。
アダムクロスランド2010

疫病:私のソリューションでは、キューとキューあたりのスレッド数をいくつ推奨しますか?
IgorGanapolsky 2010

さらに、これはCPUバウンドではなくI / Oバウンドタスクであり、GILはCPUバウンドタスクに大きく影響します
PirateApp
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.