web-dev-qa-db-ja.com

Python urllib2.urlopen()は遅いので、いくつかのURLを読み取るためのより良い方法が必要です

タイトルが示すように、私はpythonで書かれたサイトで作業しており、ウェブサイトを読み取るためにurllib2モジュールを何度か呼び出します。次に、BeautifulSoupでそれらを解析します。

5〜10のサイトを読む必要があるため、ページの読み込みに時間がかかります。

一度にサイトを読む方法があるのだろうか?または、読み取るたびにurllib2.urlopenを閉じるか、開いたままにするかなど、高速化するためのトリックはありますか?

追加:また、phpに切り替えるだけの場合、他のサイトからHTMLファイルとXMLファイルをフェッチしてParsi gする方が高速ですか?現在の約20秒ではなく、より速くロードしたいだけです

14
Jack z

以下のDumbGuyのコードを、threadingQueueなどの最新のPythonモジュールを使用して書き直しています。

_import threading, urllib2
import Queue

urls_to_load = [
'http://stackoverflow.com/',
'http://slashdot.org/',
'http://www.archive.org/',
'http://www.yahoo.co.jp/',
]

def read_url(url, queue):
    data = urllib2.urlopen(url).read()
    print('Fetched %s from %s' % (len(data), url))
    queue.put(data)

def fetch_parallel():
    result = Queue.Queue()
    threads = [threading.Thread(target=read_url, args = (url,result)) for url in urls_to_load]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    return result

def fetch_sequencial():
    result = Queue.Queue()
    for url in urls_to_load:
        read_url(url,result)
    return result
_

find_sequencial()の最適な時間は2秒です。 fetch_parallel()の最適な時間は0.9秒です。

また、threadがPython GILのために役に立たないと言うのは誤りです。これは、スレッドがPythonで役立つ場合の1つです。スレッドがI/Oでブロックされているためです。私の結果からわかるように、並列の場合は2倍高速です。

16
Wai Yip Tung

編集:このコードのより良いバージョンについては、Waiの投稿をご覧ください。以下のコメントにもかかわらず、このコードには何も問題はなく、正しく機能しますであることに注意してください。

Webページの読み取り速度は、Pythonではなく、インターネット接続によって制限されている可能性があります。

スレッドを使用して、それらすべてを一度にロードできます。

import thread, time, urllib
websites = {}
def read_url(url):
  websites[url] = urllib.open(url).read()

for url in urls_to_load: thread.start_new_thread(read_url, (url,))
while websites.keys() != urls_to_load: time.sleep(0.1)

# Now websites will contain the contents of all the web pages in urls_to_load
9
Dumb Guy

完璧ではないかもしれません。しかし、サイトからのデータが必要な場合。私はこれをするだけです:

import socket
def geturldata(url):
    #NO HTTP URLS PLEASE!!!!! 
    server = url.split("/")[0]
    args = url.replace(server,"")
    returndata = str()
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((server, 80)) #lets connect :p

    s.send("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n" % (args, server)) #simple http request
    while 1:
        data = s.recv(1024) #buffer
        if not data: break
        returndata = returndata + data
    s.close()
    return returndata.split("\n\r")[1]
3
Thomas15v

Scrapy はあなたに役立つかもしれません。すべての機能が必要ない場合は、代わりに twistedtwisted.web.client.getPageを使用できます。 1つのスレッドでの非同期IOは、複数のスレッドを使用してIOをブロックするものよりもはるかにパフォーマンスが高く、デバッグが容易になります。

2
habnabit

原則として、どの言語の特定の構成も、測定されるまで遅くはありません。

Pythonでは、タイミングが直感に反することが多いだけでなく、 実行時間を測定するためのツール は非常に優れています。

2
msw

なぜ誰も言及しないのかわからない multiprocessing (これが悪い考えである理由を誰かが知っているなら、私に知らせてください):

_import multiprocessing
from urllib2 import urlopen

URLS = [....]

def get_content(url):
    return urlopen(url).read()


pool = multiprocessing.Pool(processes=8)  # play with ``processes`` for best results
results = pool.map(get_content, URLS) # This line blocks, look at map_async 
                                      # for non-blocking map() call
pool.close()  # the process pool no longer accepts new tasks
pool.join()   # join the processes: this blocks until all URLs are processed
for result in results:
   # do something
_

multiprocessingプールにはいくつかの注意点があります。まず、スレッドとは異なり、これらは完全に新しいPythonプロセス(インタープリター)です。グローバルインタープリターロックの対象ではありませんが、新しいプロセスに渡すことができるものに制限があることを意味します。

動的に定義されたラムダと関数を渡すことはできません。 map()呼び出しで使用される関数は、他のプロセスがインポートできるようにモジュールで定義する必要があります。

複数のタスクを同時に処理する最も簡単な方法であるPool.map()は、複数の引数を渡す方法を提供しないため、ラッパー関数を記述したり、関数のシグネチャを変更したり、複数を渡す必要がある場合があります。マップされているiterableの一部としての引数。

子プロセスに新しいプロセスを生成させることはできません。親のみが子プロセスを生成できます。つまり、プロセスの最も効果的な使用法を決定するには、慎重に計画してベンチマークを行う必要があります(場合によっては、コードの複数のバージョンを作成する必要があります)。

欠点にもかかわらず、マルチプロセッシングは同時ブロッキング呼び出しを行うための最も簡単な方法の1つであることがわかりました。マルチプロセッシングとスレッドを組み合わせたり(afaikですが、間違っている場合は訂正してください)、マルチプロセッシングとグリーンスレッドを組み合わせることもできます。

2
user234932

1)同じサイトを何度も開いていますか、それとも多くの異なるサイトを開いていますか?多くの異なるサイトがある場合、urllib2は良いと思います。同じサイトを何度も繰り返している場合、私はurllib3で個人的な運がありました http://code.google.com/p/urllib3/

2)BeautifulSoupは使いやすいですが、かなり遅いです。それを使用する必要がある場合は、メモリリークを取り除くためにタグを分解してください。そうしないと、メモリの問題が発生する可能性があります(私にとってはそうです)。

あなたの記憶とCPUはどのように見えますか? CPUを最大化する場合は、複数のコアで実行できるように、実際の重量のあるスレッドを使用していることを確認してください。

1
bwawok

まず、マルチスレッド/マルチプロセッシングパッケージを試す必要があります。現在、人気のある3つは マルチプロセッシング ; concurrent.futures と[スレッド] [3]です。これらのパッケージは、同時に複数のURLを開くのに役立ち、速度を上げることができます。

さらに重要なことに、マルチスレッド処理を使用した後、同時に数百のURLを開こうとすると、urllib.request.urlopenが非常に遅くなり、コンテキストを開いて読み取ることが最も時間のかかる部分になります。したがって、さらに高速にしたい場合は、requestsパッケージを試してください。requests.get(url).content()はurllib.request.urlopen(url).read()よりも高速です。

したがって、ここでは、高速マルチURL解析を実行する2つの例をリストします。速度は、他の回答よりも高速です。最初の例では、従来のスレッドパッケージを使用し、同時に数百のスレッドを生成します。 (些細な欠点の1つは、ティッカーの元の順序を維持できないことです。)

import time
import threading
import pandas as pd
import requests
from bs4 import BeautifulSoup


ticker = pd.ExcelFile('short_tickerlist.xlsx')
ticker_df = ticker.parse(str(ticker.sheet_names[0]))
ticker_list = list(ticker_df['Ticker'])

start = time.time()

result = []
def fetch(ticker):
    url = ('http://finance.yahoo.com/quote/' + ticker)
    print('Visit ' + url)
    text = requests.get(url).content
    soup = BeautifulSoup(text,'lxml')
    result.append([ticker,soup])
    print(url +' fetching...... ' + str(time.time()-start))



if __name__ == '__main__':
    process = [None] * len(ticker_list)
    for i in range(len(ticker_list)):
        process[i] = threading.Thread(target=fetch, args=[ticker_list[i]])

    for i in range(len(ticker_list)):    
        print('Start_' + str(i))
        process[i].start()



    # for i in range(len(ticker_list)):
    #     print('Join_' + str(i))    
    #     process[i].join()

    print("Elapsed Time: %ss" % (time.time() - start))

2番目の例はマルチプロセッシングパッケージを使用しており、もう少し簡単です。プールの数を記述して関数をマップする必要があるだけなので。コンテキストをフェッチした後、順序は変更されません。速度は最初の例と同様ですが、他の方法よりもはるかに高速です。

from multiprocessing import Pool
import requests
from bs4 import BeautifulSoup
import pandas as pd
import os
import time

os.chdir('file_path')

start = time.time()

def fetch_url(x):
    print('Getting Data')
    myurl = ("http://finance.yahoo.com/q/cp?s=%s" % x)
    html = requests.get(myurl).content
    soup = BeautifulSoup(html,'lxml')
    out = str(soup)
    listOut = [x, out]
    return listOut

tickDF = pd.read_Excel('short_tickerlist.xlsx')
li = tickDF['Ticker'].tolist()    

if __name__ == '__main__':
    p = Pool(5)
    output = p.map(fetch_url, ji, chunksize=30)
    print("Time is %ss" %(time.time()-start))
0
fzn0728

Pycurlを使ってみませんか?

あなたはapt-によってそれを得ることができます

$ Sudo apt-get python-pycurl
0
OTZ