28

いくつかの Web ページを取得して情報を解析するスクリプトがあります。

(例はhttp://bluedevilbooks.com/search/?DEPT=MATH&CLASS=103&SEC=01で見ることができます)

その上でcProfileを実行しましたが、想定どおり、urlopenには多くの時間がかかります。ページをより速く取得する方法はありますか? または、一度に複数のページを取得する方法はありますか? 私はPythonとWeb開発に慣れていないので、最も簡単なことは何でもします。

前もって感謝します!:)

更新: という関数がありますfetchURLs()。これを使用して、必要な URL の配列を作成します。URLurls = fetchURLS()はすべて Amazon および eBay API からの XML ファイルです (これは、ロードに時間がかかる理由について私を混乱させます。私のウェブホストは遅いですか?)

私がする必要があるのは、各 URL を読み込み、各ページを読み取り、そのデータをスクリプトの別の部分に送信して、データを解析して表示することです。

すべてのページがフェッチされるまで、後半を実行できないことに注意してください。それが私の問題です。

また、私のホストでは、一度に 25 プロセスに制限されていると思います。そのため、サーバー上で最も簡単なものは何でもいいでしょう :)


ここに時間があります:

Sun Aug 15 20:51:22 2010    prof

         211352 function calls (209292 primitive calls) in 22.254 CPU seconds

   Ordered by: internal time
   List reduced from 404 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       10   18.056    1.806   18.056    1.806 {_socket.getaddrinfo}
     4991    2.730    0.001    2.730    0.001 {method 'recv' of '_socket.socket' objects}
       10    0.490    0.049    0.490    0.049 {method 'connect' of '_socket.socket' objects}
     2415    0.079    0.000    0.079    0.000 {method 'translate' of 'unicode' objects}
       12    0.061    0.005    0.745    0.062 /usr/local/lib/python2.6/HTMLParser.py:132(goahead)
     3428    0.060    0.000    0.202    0.000 /usr/local/lib/python2.6/site-packages/BeautifulSoup.py:1306(endData)
     1698    0.055    0.000    0.068    0.000 /usr/local/lib/python2.6/site-packages/BeautifulSoup.py:1351(_smartPop)
     4125    0.053    0.000    0.056    0.000 /usr/local/lib/python2.6/site-packages/BeautifulSoup.py:118(setup)
     1698    0.042    0.000    0.358    0.000 /usr/local/lib/python2.6/HTMLParser.py:224(parse_starttag)
     1698    0.042    0.000    0.275    0.000 /usr/local/lib/python2.6/site-packages/BeautifulSoup.py:1397(unknown_starttag)
4

11 に答える 11

30

編集:より洗練された例を含めるように回答を拡張しています。この投稿では、スレッド化と非同期 I/O に関する多くの敵意と誤った情報を見つけました。したがって、特定の無効な主張に反論するための議論も追加します。これが、人々が適切な仕事に適切なツールを選択するのに役立つことを願っています.

これは 3 日前の質問の重複です。

Python urllib2.open が遅いため、複数の URL を読み取るためのより良い方法が必要です - コードログ

スレッドを使用して複数の Web ページを並行してフェッチする方法を示すために、コードを改良しています。

import time
import threading
import Queue

# utility - spawn a thread to execute target for each args
def run_parallel_in_threads(target, args_list):
    result = Queue.Queue()
    # wrapper to collect return value in a Queue
    def task_wrapper(*args):
        result.put(target(*args))
    threads = [threading.Thread(target=task_wrapper, args=args) for args in args_list]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    return result

def dummy_task(n):
    for i in xrange(n):
        time.sleep(0.1)
    return n

# below is the application code
urls = [
    ('http://www.google.com/',),
    ('http://www.lycos.com/',),
    ('http://www.bing.com/',),
    ('http://www.altavista.com/',),
    ('http://achewood.com/',),
]

def fetch(url):
    return urllib2.urlopen(url).read()

run_parallel_in_threads(fetch, urls)

ご覧のとおり、アプリケーション固有のコードには 3 行しかありませんが、攻撃的な場合は 1 行に折りたたむことができます。これが複雑で保守不可能であるという主張を正当化できる人はいないと思います。

残念ながら、ここに掲載されている他のほとんどのスレッド化コードにはいくつかの欠陥があります。それらの多くは、アクティブ ポーリングを行ってコードが終了するのを待ちます。join()コードを同期するためのより良い方法です。このコードは、これまでのすべてのスレッド化の例よりも改善されていると思います。

キープアライブ接続

キープアライブ接続の使用に関する WoLpH の提案は、すべての URL が同じサーバーを指している場合に非常に役立ちます。

ねじれた

Aaron Gallagher はtwistedフレームワークのファンであり、スレッドを提案する人には敵意を持っています。残念ながら、彼の主張の多くは誤った情報です。たとえば、彼は「スレッドを提案する場合は-1。これは IO バウンドです。ここではスレッドは役に立ちません」と述べています。これは、Nick T と私がスレッドを使用することで速度が向上したことを示しているため、証拠に反しています。実際、I/O バウンドのアプリケーションは、Python のスレッドを使用することで最も多くのメリットを得ることができます (CPU バウンドのアプリケーションではメリットがありません)。スレッドに対する Aaron の見当違いの批判は、彼が一般的な並列プログラミングについてかなり混乱していることを示しています。

適切な仕事のための適切なツール

スレッド、Python、非同期 I/O などを使用した並列プログラミングに関連する問題については十分承知しています。各ツールには長所と短所があります。状況ごとに適切なツールがあります。私はツイストに反対ではありません (ただし、自分で展開したことはありません)。しかし、すべての状況において、糸がBADで、ねじれがGOODだと一概に言えるとは思いません。

たとえば、OP の要件が 10,000 の Web サイトを並行して取得することである場合、非同期 I/O が適しています。スレッド化は適切ではありません (おそらくスタックレス Python を使用しない限り)。

スレッドに対するアーロンの反対は、ほとんどが一般化されたものです。彼は、これが些細な並列化作業であることを認識していません。各タスクは独立しており、リソースを共有しません。したがって、彼の攻撃のほとんどは適用されません。

私のコードには外部依存関係がないことを考えると、それを適切な仕事のための適切なツールと呼びます。

パフォーマンス

ほとんどの人は、このタスクのパフォーマンスがネットワーク コードと外部サーバーに大きく依存することに同意すると思います。プラットフォーム コードのパフォーマンスは無視できるはずです。ただし、Aaron のベンチマークでは、スレッド化されたコードよりも 50% 速度が向上しています。この見た目のスピードアップへの対応が必要だと思います。

Nick のコードには、非効率の原因となった明らかな欠陥があります。しかし、私のコードよりも 233 ミリ秒速度が向上したことをどのように説明できますか? ねじれたファンでさえ、これをねじれたの効率に帰するために結論を急ぐことは控えると思います。結局のところ、リモート サーバーのパフォーマンス、ネットワーク、キャッシング、urllib2 とツイスト Web クライアントとの違いの実装など、システム コードの外側には膨大な量の変数があります。

Python のスレッド化が大量の非効率性を招かないようにするためだけに、5 つのスレッドを生成してから 500 のスレッドを生成する簡単なベンチマークを行います。5 スレッドを生成するオーバーヘッドはごくわずかであり、233 ミリ秒の速度差を説明できないと言っても過言ではありません。

In [274]: %time run_parallel_in_threads(dummy_task, [(0,)]*5)
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
Wall time: 0.00 s
Out[275]: <Queue.Queue instance at 0x038B2878>

In [276]: %time run_parallel_in_threads(dummy_task, [(0,)]*500)
CPU times: user 0.16 s, sys: 0.00 s, total: 0.16 s
Wall time: 0.16 s

In [278]: %time run_parallel_in_threads(dummy_task, [(10,)]*500)
CPU times: user 1.13 s, sys: 0.00 s, total: 1.13 s
Wall time: 1.13 s       <<<<<<<< This means 0.13s of overhead

並列フェッチをさらにテストすると、17 回の実行で応答時間に大きなばらつきがあることがわかりました。(残念ながら、私は Aaron のコードを検証するためにひねりを加えていません)。

0.75 s
0.38 s
0.59 s
0.38 s
0.62 s
1.50 s
0.49 s
0.36 s
0.95 s
0.43 s
0.61 s
0.81 s
0.46 s
1.21 s
2.87 s
1.04 s
1.72 s

私のテストでは、スレッド化が非同期 I/O より一貫して測定可能なマージンだけ遅いという Aaron の結論を支持していません。関連する変数の数を考えると、これは非同期 I/O とスレッド化の間の体系的なパフォーマンスの違いを測定するための有効なテストではないと言わざるを得ません。

于 2010-08-16T06:05:20.963 に答える
19

ねじって使う!たとえば、スレッドを使用する場合と比較して、この種のことはとてつもなく簡単になります。

from twisted.internet import defer, reactor
from twisted.web.client import getPage
import time

def processPage(page, url):
    # do somewthing here.
    return url, len(page)

def printResults(result):
    for success, value in result:
        if success:
            print 'Success:', value
        else:
            print 'Failure:', value.getErrorMessage()

def printDelta(_, start):
    delta = time.time() - start
    print 'ran in %0.3fs' % (delta,)
    return delta

urls = [
    'http://www.google.com/',
    'http://www.lycos.com/',
    'http://www.bing.com/',
    'http://www.altavista.com/',
    'http://achewood.com/',
]

def fetchURLs():
    callbacks = []
    for url in urls:
        d = getPage(url)
        d.addCallback(processPage, url)
        callbacks.append(d)

    callbacks = defer.DeferredList(callbacks)
    callbacks.addCallback(printResults)
    return callbacks

@defer.inlineCallbacks
def main():
    times = []
    for x in xrange(5):
        d = fetchURLs()
        d.addCallback(printDelta, time.time())
        times.append((yield d))
    print 'avg time: %0.3fs' % (sum(times) / len(times),)

reactor.callWhenRunning(main)
reactor.run()

このコードは、投稿された他のどのソリューションよりも優れたパフォーマンスを発揮します (多くの帯域幅を使用していたいくつかのものを閉じた後に編集しました)。

Success: ('http://www.google.com/', 8135)
Success: ('http://www.lycos.com/', 29996)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.518s
Success: ('http://www.google.com/', 8135)
Success: ('http://www.lycos.com/', 30349)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.461s
Success: ('http://www.google.com/', 8135)
Success: ('http://www.lycos.com/', 30033)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.435s
Success: ('http://www.google.com/', 8117)
Success: ('http://www.lycos.com/', 30349)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.449s
Success: ('http://www.google.com/', 8135)
Success: ('http://www.lycos.com/', 30349)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.547s
avg time: 0.482s

また、Nick T のコードを使用して、平均 5 を提供し、出力をより適切に表示するように調整しました。

Starting threaded reads:
...took 1.921520 seconds ([8117, 30070, 15043, 8386, 28611])
Starting threaded reads:
...took 1.779461 seconds ([8135, 15043, 8386, 30349, 28611])
Starting threaded reads:
...took 1.756968 seconds ([8135, 8386, 15043, 30349, 28611])
Starting threaded reads:
...took 1.762956 seconds ([8386, 8135, 15043, 29996, 28611])
Starting threaded reads:
...took 1.654377 seconds ([8117, 30349, 15043, 8386, 28611])
avg time: 1.775s

Starting sequential reads:
...took 1.389803 seconds ([8135, 30147, 28611, 8386, 15043])
Starting sequential reads:
...took 1.457451 seconds ([8135, 30051, 28611, 8386, 15043])
Starting sequential reads:
...took 1.432214 seconds ([8135, 29996, 28611, 8386, 15043])
Starting sequential reads:
...took 1.447866 seconds ([8117, 30028, 28611, 8386, 15043])
Starting sequential reads:
...took 1.468946 seconds ([8153, 30051, 28611, 8386, 15043])
avg time: 1.439s

Wai Yip Tung のコードを使用すると、次のようになります。

Fetched 8117 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30051 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.704s
Fetched 8117 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30114 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.845s
Fetched 8153 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30070 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.689s
Fetched 8117 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30114 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.647s
Fetched 8135 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30349 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.693s
avg time: 0.715s

私は言わなければなりません、私はシーケンシャルフェッチが私にとってより良いパフォーマンスをしたことを気に入っています。

于 2010-08-16T03:20:01.090 に答える
5

これは python を使用した例Threadsです。ここにある他のスレッド化された例は、URL ごとにスレッドを起動します。これは、サーバーが処理するにはあまりにも多くのヒットが発生する場合、あまりフレンドリーな動作ではありません (たとえば、スパイダーが同じホストに多くの URL を持つことは一般的です)。

from threading import Thread
from urllib2 import urlopen
from time import time, sleep

WORKERS=1
urls = ['http://docs.python.org/library/threading.html',
        'http://docs.python.org/library/thread.html',
        'http://docs.python.org/library/multiprocessing.html',
        'http://docs.python.org/howto/urllib2.html']*10
results = []

class Worker(Thread):
    def run(self):
        while urls:
            url = urls.pop()
            results.append((url, urlopen(url).read()))

start = time()
threads = [Worker() for i in range(WORKERS)]
any(t.start() for t in threads)

while len(results)<40:
    sleep(0.1)
print time()-start

注: ここに示す時間は 40 個の URL の場合であり、インターネット接続の速度とサーバーへの遅延に大きく依存します。オーストラリアにいるので、私の ping は 300 ミリ秒を超えています

実行にWORKERS=186秒かかりました 実行
WORKERS=423秒かかりました 実行
WORKERS=1010秒かかりました

したがって、10 スレッドのダウンロードは、単一スレッドの 8.6 倍の速さです。

これは、キューを使用するアップグレードされたバージョンです。少なくともいくつかの利点があります。
1. URL はリストに表示される順序で要求されます
2.q.join()要求がすべて完了したことを検出するために使用できます
3. 結果は URL リストと同じ順序で保持されます

from threading import Thread
from urllib2 import urlopen
from time import time, sleep
from Queue import Queue

WORKERS=10
urls = ['http://docs.python.org/library/threading.html',
        'http://docs.python.org/library/thread.html',
        'http://docs.python.org/library/multiprocessing.html',
        'http://docs.python.org/howto/urllib2.html']*10
results = [None]*len(urls)

def worker():
    while True:
        i, url = q.get()
        # print "requesting ", i, url       # if you want to see what's going on
        results[i]=urlopen(url).read()
        q.task_done()

start = time()
q = Queue()
for i in range(WORKERS):
    t=Thread(target=worker)
    t.daemon = True
    t.start()

for i,url in enumerate(urls):
    q.put((i,url))
q.join()
print time()-start
于 2010-08-16T07:57:46.243 に答える
2

回答のほとんどは、異なるサーバーから同時に複数のページをフェッチすること (スレッド化) に焦点を当てていましたが、既に開いている HTTP 接続を再利用することには焦点を当てていませんでした。OPが同じサーバー/サイトに複数のリクエストを行っている場合。

urlib2 では、リクエストごとに個別の接続が作成され、パフォーマンスに影響を与え、その結果、ページのフェッチ速度が遅くなります。urllib3 は、接続プールを使用してこの問題を解決します。ここで詳細を読むことができますurllib3 [スレッドセーフでもあります]

urllib3 を使用する HTTP ライブラリを要求するものもあります

これをスレッド化と組み合わせると、ページのフェッチ速度が向上するはずです

于 2013-12-20T13:19:58.783 に答える
2

この質問が投稿されたので、利用可能なより高いレベルの抽象化があるようですThreadPoolExecutor:

https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor-example

便宜上、そこからの例をここに貼り付けます。

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the url and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

mapコードを簡単にするものもあります: https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Executor.map

于 2015-12-11T17:32:57.880 に答える
2

実際の待機はおそらくurllib2サーバー内ではなく、サーバーおよび/またはサーバーへのネットワーク接続内にあります。

これを高速化するには 2 つの方法があります。

  1. 接続を維持します (その方法については、この質問を参照してください: Python urllib2 with keep alive )
  2. Aaron Gallagher が提案したように、複数の接続を使用します。スレッドまたは非同期アプローチを使用できます。そのためには、スレッド化の例を使用するだけで問題ありません:) また、multiprocessinglib を使用して物事をかなり簡単にすることもできます。
于 2010-08-16T02:08:49.047 に答える
1

現在、requestsと呼ばれる、これを行う優れた Python lib があります。

スレッドに基づくソリューションが必要な場合はリクエストの標準 API を使用し、ノンブロッキング IO に基づくソリューションが必要な場合は非同期 API (ボンネットの下で gevent を使用) を使用します。

于 2012-01-27T13:31:00.277 に答える
0

これが標準ライブラリソリューションです。それほど高速ではありませんが、スレッド化されたソリューションよりもメモリの使用量が少なくなります。

try:
    from http.client import HTTPConnection, HTTPSConnection
except ImportError:
    from httplib import HTTPConnection, HTTPSConnection
connections = []
results = []

for url in urls:
    scheme, _, host, path = url.split('/', 3)
    h = (HTTPConnection if scheme == 'http:' else HTTPSConnection)(host)
    h.request('GET', '/' + path)
    connections.append(h)
for h in connections:
    results.append(h.getresponse().read())

また、ほとんどのリクエストが同じホストに対するものである場合は、同じ http 接続を再利用することで、並行して処理を行うよりも多くのことを行うことができます。

于 2014-11-23T16:02:36.880 に答える
0

ローカルにアクセスしていないため、Web ページの取得には明らかに時間がかかります。アクセスするものが複数ある場合は、threadingモジュールを使用して一度にいくつかを実行できます。

これは非常に大雑把な例です

import threading
import urllib2
import time

urls = ['http://docs.python.org/library/threading.html',
        'http://docs.python.org/library/thread.html',
        'http://docs.python.org/library/multiprocessing.html',
        'http://docs.python.org/howto/urllib2.html']
data1 = []
data2 = []

class PageFetch(threading.Thread):
    def __init__(self, url, datadump):
        self.url = url
        self.datadump = datadump
        threading.Thread.__init__(self)
    def run(self):
        page = urllib2.urlopen(self.url)
        self.datadump.append(page.read()) # don't do it like this.

print "Starting threaded reads:"
start = time.clock()
for url in urls:
    PageFetch(url, data2).start()
while len(data2) < len(urls): pass # don't do this either.
print "...took %f seconds" % (time.clock() - start)

print "Starting sequential reads:"
start = time.clock()
for url in urls:
    page = urllib2.urlopen(url)
    data1.append(page.read())
print "...took %f seconds" % (time.clock() - start)

for i,x in enumerate(data1):
    print len(data1[i]), len(data2[i])

これを実行したときの出力は次のとおりです。

Starting threaded reads:
...took 2.035579 seconds
Starting sequential reads:
...took 4.307102 seconds
73127 19923
19923 59366
361483 73127
59366 361483

リストに追加してスレッドからデータを取得することはおそらく賢明ではありませんが (Queue の方がよいでしょう)、違いがあることを示しています。

于 2010-08-16T02:08:53.253 に答える