2010-08-12 2 views
10

Come suggerisce il titolo, sto lavorando su un sito scritto in python e fa diverse chiamate al modulo urllib2 per leggere i siti web. Li analizzo poi con BeautifulSoup.Python urllib2.urlopen() è lento, ha bisogno di un modo migliore per leggere diversi URL

Come devo leggere 5-10 siti, la pagina richiede un po 'di tempo per caricare.

Mi stavo chiedendo se c'è un modo per leggere i siti tutti in una volta? O anytricks per renderlo più veloce, come dovrei chiudere il urllib2.urlopen dopo ogni lettura, o tenerlo aperto?

Aggiunto: anche, se dovessi cambiare poco più di php, vorrei che essere più veloce per il recupero e il Parsi g file HTML e XML da altri siti? Voglio solo caricarlo più velocemente, al contrario dei ~ 20 secondi attualmente necessari

+1

Cosa ti fa pensare che 'open' sia lento? BeautifulSoup (tanto utile quanto è) fa molto più lavoro e presumo che sia il collo di bottiglia nel codice. Hai provato senza analizzare? Un esempio di codice qui sarebbe d'aiuto. – msw

+0

Non basta PHP, non servirà. Python ha un sacco di spazio per essere veloce, hai solo bisogno di ottimizzare il tuo codice. – bwawok

+0

Oh sparare, quindi la cosa da infilare non va bene? Grazie per il passaggio su –

risposta

13

Sto riscrivendo il codice di Dumb Guy sotto utilizzando moderni moduli Python come threading e Queue.

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 

Il periodo migliore per find_sequencial() è 2 secondi. Il miglior tempo per fetch_parallel() è 0.9s.

Inoltre, non è corretto dire che thread è inutile in Python a causa di GIL. Questo è uno di quei casi in cui il thread è utile in Python perché i thread sono bloccati su I/O. Come puoi vedere nel mio risultato, il caso parallelo è 2 volte più veloce.

+2

Sì, * se * questo fosse l'unico modo per recuperare gli URL, questo sarebbe più vicino al modo corretto di usare i thread. Tuttavia, l'IO asincrono * sarà * ancora più veloce, più gestibile, consentirà il debug deterministico e così via. Anche senza il GIL, sarebbe una soluzione superiore. – habnabit

+0

Oops, sembra che Dump Guy abbia ritirato la sua risposta. Ehi, dico che stavi andando nella giusta direzione! –

+2

Aaron, puoi fornire un esempio funzionante per mostrare che il codice IO asincrono è più gestibile? –

1

1) Stai aprendo lo stesso sito molte volte, o molti siti diversi? Se molti siti diversi, penso che urllib2 sia buono. Se faccio sempre lo stesso sito, ho avuto fortuna personale con urllib3 http://code.google.com/p/urllib3/

2) BeautifulSoup è facile da usare, ma è piuttosto lento. Se devi usarlo, assicurati di scomporre i tag per eliminare le perdite di memoria .. o probabilmente causerà problemi di memoria (fatto per me).

Che aspetto hanno la tua memoria e CPU? Se stai massimizzando la tua CPU, assicurati di utilizzare thread reali pesanti, in modo che tu possa girare su più di un core.

+0

Sto accedendo alle pagine XML per Amazon, eBay e Half. Quindi, mentre simili, i prodotti e i prezzi cambiano –

+0

Ok quindi urllib2 va bene. È necessario eseguire il thread out del programma per utilizzare thread pesanti e analizzare il più efficientemente possibile. – bwawok

+0

Ok, grazie mille! –

8

Modifica: Si prega di dare un'occhiata al post di Wai per una versione migliore di questo codice. Si noti che non c'è nulla di sbagliato in questo codice e che funzionerà correttamente, nonostante i commenti riportati di seguito.

La velocità di lettura delle pagine Web è probabilmente limitata dalla connessione Internet, non da Python.

È possibile utilizzare i thread per caricarli tutti in una volta.

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 
+0

Il collo di bottiglia probabilmente non è nemmeno la connessione Internet, ma il server remoto. Tuttavia, BeautifulSoup è lento in ogni caso. Quindi aggiungerà un ulteriore ritardo. – Wolph

+0

Oh ok, questo ha senso. E apprezzo il codice di esempio grazie! –

+1

-1 per i thread * e * che suggeriscono che il modulo 'thread' _e_ non sta facendo alcun blocco o * even * usando il modulo' Queue'. Stai solo aggiungendo più complessità e blocchi di overhead senza alcun guadagno se usi i thread. Anche se questo non era vero, il tuo codice dimostra che non sai davvero come usare i thread. – habnabit

0

Che ne dici di usare pycurl?

È possibile apt-get è da

$ sudo apt-get python-pycurl
+0

Pycurl non è più veloce di urllib2 nella mia esperienza – bwawok

2

Come regola generale, un dato costrutto in qualsiasi lingua, non è lento fino a quando non viene misurato.

In Python, non solo i tempi spesso vanno contro l'intuizione, ma lo tools for measuring execution time è eccezionalmente buono.

+0

Questo è un * GRANDE commento - Sfortunatamente, non penso davvero che sia una "* risposta *" ... – mgilson

2

Scrapy potrebbe essere utile per voi. Se non hai bisogno di tutte le sue funzionalità, potresti semplicemente usare lo twisted.web.client.getPage di twisted. L'I/O asincrono in un thread sarà molto più performante e facile da eseguire il debug rispetto a qualsiasi cosa che usi più thread e blocchi l'IO.

+0

Ok, ho sentito che è più veloce. Grazie! –

+0

@msw, la mia risposta è stata interrotta nel tuo browser? La frase completa è "L'IO asincrono in un thread sarà molto più performante e facile da eseguire il debug di qualsiasi cosa che usi più thread e blocchi l'IO." – habnabit

+0

avrei dovuto essere più chiaro; scusa. L'OP non ha nemmeno fatto il caso di aver bisogno di IO asincrono, e la tua filosofia di "aver capito bene, prima" di cui sopra è una buona posizione. Ma temo che non stia impressionando l'OP, vabbè;) – msw

3

Non è perfetto. Ma quando ho bisogno dei dati da un sito. Io faccio solo questo:

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] 
2

Non certo perché nessuno menziona multiprocessing (se qualcuno conosce il motivo per cui questo potrebbe essere una cattiva idea, fatemelo sapere):

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 

Ci sono alcune avvertenze con multiprocessing piscine . Innanzitutto, a differenza dei thread, questi sono processi Python completamente nuovi (interpreti). Anche se non è soggetto al blocco dell'interprete globale, significa che sei limitato a ciò che puoi trasmettere al nuovo processo.

Non è possibile passare lambda e funzioni definite dinamicamente. La funzione utilizzata nella chiamata map() deve essere definita nel modulo in modo tale che l'altro processo possa importarla.

Il Pool.map(), che è il modo più semplice per elaborare più attività contemporaneamente, non fornisce un modo per passare più argomenti, quindi potrebbe essere necessario scrivere funzioni wrapper o firme funzione di cambio e/o passare più argomenti parte del iterable che viene mappato.

Non è possibile creare processi figlio nuovi. Solo il genitore può generare processi figli. Ciò significa che devi pianificare attentamente e confrontare (e talvolta scrivere più versioni del tuo codice) al fine di determinare quale sarebbe l'uso più efficace dei processi.

Nonostante gli svantaggi, ritengo che il multiprocessing sia uno dei modi più semplici per effettuare chiamate di blocco contemporanee. Puoi anche combinare multiprocessing e thread (afaik, ma correggimi se ho torto), o combinare multiprocessing con thread verdi.

0

Per prima cosa, dovresti provare i pacchetti multithreading/multiprocessing. Attualmente, i tre più popolari sono multiprocessing; concurrent.futures e [threading] [3]. Questi pacchetti potrebbero aiutarti ad aprire più url contemporaneamente, il che potrebbe aumentare la velocità.

Ancora più importante, dopo aver utilizzato l'elaborazione multithread e se si tenta di aprire centinaia di URL contemporaneamente, si noterà che urllib.request.urlopen è molto lento e l'apertura e la lettura del contesto diventano la parte che richiede più tempo . Quindi, se vuoi renderlo ancora più veloce, dovresti provare i pacchetti request, request.get (url) .content() è più veloce di urllib.request.urlopen (url) .read().

Quindi, qui elencho due esempi per l'analisi veloce multi url e la velocità è più veloce rispetto alle altre risposte. Il primo esempio utilizza il classico pacchetto di threading e genera centinaia di thread allo stesso tempo. (Un difetto banale è che non può mantenere l'ordine originale del ticker.)

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)) 

Il secondo esempio utilizza pacchetto multiprocessore, ed è poco più semplice. Poiché devi solo indicare il numero di pool e mappare la funzione. L'ordine non cambierà dopo aver recuperato il contesto e la velocità è simile al primo esempio ma molto più veloce rispetto ad altri metodi.

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))