100,000個のURLを持つファイルを開いています。各URLにHTTPリクエストを送信し、ステータスコードを印刷する必要があります。私はPython 2.6を使用していますが、これまでPythonがスレッド化/並行性を実装する多くの紛らわしい方法を見てきました。 python concurrence ライブラリも見てきましたが、このプログラムを正しく書く方法がわかりません。誰かが同様の問題に遭遇しましたか?一般的に、Pythonで可能な限り高速に何千ものタスクを実行する方法を知る必要があると思います。これは「同時に」を意味すると思います。
ツイストレスソリューション:
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が少なくなります。
tornado 非同期ネットワークライブラリを使用したソリューション
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()
ここではスレッドは絶対に答えではありません。プロセスとカーネルの両方のボトルネック、および全体的な目標が「最速の方法」である場合に許容できないスループット制限を提供します。
少しのtwisted
とその非同期HTTP
クライアントを使用すると、より良い結果が得られます。
これが投稿された2010年以降、状況はかなり変化しており、他のすべての答えを試していませんが、いくつか試しましたが、python3.6を使用して私にとってこれが最適に機能することがわかりました。
AWSで実行されている1秒あたり約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())
grequests を使用します。これはリクエストとGeventモジュールの組み合わせです。
GRequestsを使用すると、GeventでRequestsを使用して、非同期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]>]
この問題を解決する良い方法は、最初に1つの結果を得るために必要なコードを記述し、次にスレッド化コードを組み込んでアプリケーションを並列化することです。
完璧な世界では、これは結果を辞書またはリストに出力して後で処理する100,000スレッドを同時に開始することを意味しますが、実際にはこの方法で発行できる並行HTTPリクエストの数に制限があります。ローカルでは、同時に開くことができるソケットの数、Pythonインタープリターが許可する実行スレッドの数に制限があります。リモートでは、すべての要求が1つまたは多くのサーバーに対するものである場合、同時接続の数が制限される場合があります。これらの制限により、URLのごく一部のみを一度にポーリングするような方法でスクリプトを記述することがおそらく必要になります(別の投稿者が述べたように、100はおそらくまともなスレッドプールサイズです。ただし、より多くを正常に展開できます)。
この設計パターンに従って、上記の問題を解決できます。
list
またはdict
である場合、 ロックなしでスレッドから一意のアイテムを安全に追加または挿入 できますが、ファイルに書き込むか、より複雑な場合はクロススレッドデータの相互作用この状態を破損から保護するには、相互排他ロックを使用する必要があります。threading モジュールを使用することをお勧めします。これを使用して、実行中のスレッドを起動および追跡できます。 Pythonのスレッドサポートはむき出しになっていますが、問題の説明は、ニーズに完全に対応していることを示唆しています。
最後に、Pythonで記述された並列ネットワークアプリケーションの非常に単純なアプリケーションを確認したい場合は、 ssh.py を確認してください。 Pythonスレッディングを使用して多くのSSH接続を並列化する小さなライブラリです。設計は要件に十分に近いため、適切なリソースであることがわかります。
可能な限り最高のパフォーマンスを得たい場合は、スレッドではなく非同期I/Oの使用を検討することをお勧めします。数千のOSスレッドに関連するオーバーヘッドは些細なものではなく、Pythonインタープリター内のコンテキスト切り替えにより、さらに多くのスレッドが追加されます。スレッド化は確かに仕事を成し遂げますが、非同期ルートは全体的なパフォーマンスを向上させると思います。
具体的には、Twistedライブラリ( http://www.twistedmatrix.com )で非同期Webクライアントを提案します。明らかに急な学習曲線を持っていますが、Twistedのスタイルの非同期プログラミングをうまく理解すれば、非常に使いやすくなります。
Twistedの非同期WebクライアントAPIのHowToは次の場所から入手できます
http://twistedmatrix.com/documents/current/web/howto/client.html
解決策:
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
これは古い質問ですが、Python 3.7では、asyncio
と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 __== "__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))
詳細については、例を参照してください here 。
epoll
オブジェクトを作成し、
多くのクライアントTCPソケットを開き、
送信バッファを調整して、リクエストヘッダーより少し大きくします。
リクエストヘッダーを送信します。すぐにバッファに配置し、epoll
オブジェクトにソケットを登録し、
do .poll
on epoll
obect、.poll
の各ソケットから最初の3バイトを読み取り、sys.stdout
に続いて\n
(フラッシュしない)に書き込み、クライアントソケットを閉じます。
同時に開くソケットの数を制限します—ソケットの作成時にエラーを処理します。別のソケットが閉じている場合にのみ、新しいソケットを作成します。
OSの制限を調整します。
少数(多くはない)のプロセスに分岐してみてください。これにより、CPUをもう少し効率的に使用できる場合があります。
スレッドプール を使用することは適切なオプションであり、これはかなり簡単になります。残念ながら、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()
お役に立てれば。
このねじれた非同期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)
あなたの場合、おそらく応答を待つことにほとんどの時間を費やしているので、スレッド化はおそらくトリックを行うでしょう。標準ライブラリには Queue などの役立つモジュールがあります。
以前にファイルの並列ダウンロードで同様のことをしましたが、それで十分でしたが、あなたが話している規模ではありませんでした。
タスクがよりCPUにバインドされている場合は、 multiprocessing モジュールを確認することをお勧めします。これにより、より多くのCPU /コア/スレッド(ロックはプロセスごとです)
Windmill を使用することを検討してください。ただし、おそらくWindmillはそれほど多くのスレッドを実行できません。
5台のマシンで手動でPythonスクリプトを実行して、それぞれがポート40000〜60000を使用してアウトバウンドに接続し、100,000のポート接続を開くことができます。
また、各サーバーがどれだけ処理できるかを知るために、 OpenSTA などの適切にスレッド化されたQAアプリでサンプルテストを行うと役立つ場合があります。
また、LWP :: ConnCacheクラスで単純なPerlを使用するだけで調べてみてください。そうすれば、おそらくより多くのパフォーマンス(より多くの接続)が得られます。