Python urllib2.urlopen() est lent, besoin d'une meilleure façon de lire plusieurs urls
comme le titre l'indique, je travaille sur un site écrit en python et il fait plusieurs appels au module urllib2 pour lire des sites web. Je les partage ensuite avec de belles mouches.
comme je dois lire 5-10 sites, la page prend un certain temps à charger.
je me demande juste s'il y a un moyen de lire les sites d'un seul coup? Ou anytricks pour le rendre plus rapide, comme si je fermais l'urllib2.urlopen après chaque lecture, ou la laisser ouverte?
ajouté : aussi, si je devais simplement passer à php, serait-ce plus rapide pour récupérer et partager des fichiers HTML et XML à partir d'autres sites? Je veux juste qu'il se charge plus rapidement, par opposition aux ~20 secondes qu'il prend actuellement
9 réponses
je réécris le code de Dumb Guy ci-dessous en utilisant des modules Python modernes comme threading
et 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
meilleur temps pour find_sequencial()
est 2s. Le meilleur temps pour fetch_parallel()
est 0.9 S.
aussi il est incorrect de dire thread
est inutile en Python à cause de GIL. C'est l'un de ces cas où le thread est utile en Python parce que les threads sont bloqués sur I/O. comme vous pouvez le voir dans mon résultat le cas parallèle est 2 fois plus rapide.
Edit: s'il vous Plaît prendre un coup d'oeil à Wai post pour une meilleure version de ce code. Notez qu'il n'y a aucun problème avec ce code et qu'il fonctionnera correctement , malgré les commentaires ci-dessous.
la vitesse de lecture des pages web est probablement limitée par votre connexion Internet, PAS Python.
vous pouvez utiliser des fils pour les charger tous à la fois.
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
c'est maby pas parfait. Mais quand j'ai besoin des données d'un site. Je fais juste ceci:
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]
en règle générale, une construction dans aucune langue n'est pas lent jusqu'à ce qu'elle est mesurée.
en Python, non seulement les timings vont souvent à l'encontre de l'intuition, mais les outils pour mesurer le temps d'exécution sont exceptionnellement bons.
Scrapy pourrait être utile pour vous. Si vous n'avez pas besoin de toutes ses fonctionnalités, vous pouvez simplement utiliser tordu", 151930920" 's twisted.web.client.getPage
à la place. Asynchrone IO dans un thread va être beaucoup plus performant et facile à déboguer que tout ce qui utilise plusieurs threads et le blocage IO.
Je ne sais pas pourquoi personne ne mentionne multiprocessing
(si quelqu'un sait pourquoi cela pourrait être une mauvaise idée, faites-le moi savoir):
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
il y a quelques mises en garde avec multiprocessing
piscines. Tout d'abord, contrairement aux threads, ce sont des processus Python complètement nouveaux (interpréteur). Bien qu'il ne soit pas soumis au verrouillage global de l'interpréteur, il signifie que vous êtes limité dans ce que vous pouvez transmettre au nouveau processus.
vous ne pouvez pas passer lambdas et des fonctions qui sont définies dynamiquement. La fonction utilisée dans l'appel map()
doit être définie dans votre module d'une manière qui permette à l'autre processus de l'importer.
le Pool.map()
, qui est le moyen le plus simple de traiter plusieurs tâches simultanément, ne fournit pas un moyen de passer plusieurs arguments, de sorte que vous pouvez avoir besoin d'écrire des fonctions de wrapper ou de modifier les signatures de fonction, et/ou passer plusieurs arguments dans le cadre de l'itérable qui est en cours mapper.
vous ne pouvez pas laisser les processus enfants en engendrer de nouveaux. Seuls les parents peuvent engendrer des processus enfants. Cela signifie que vous devez soigneusement planifier et comparer (et parfois écrire plusieurs versions de votre code) afin de déterminer quelle serait l'utilisation la plus efficace des processus.
Malgré les inconvénients du, je trouve que le multiprocessing est l'un des moyens les plus simples de bloquer les appels simultanés. Vous pouvez également combiner le multitraitement et threads (afaik, mais s'il vous plaît me corriger si je me trompe), ou combiner multiprocessing avec des threads verts.
1) Ouvrez-vous le même site plusieurs fois, ou plusieurs sites différents? Si beaucoup de sites différents, je pense urllib2 est bon. Si faire le même site encore et encore, j'ai eu un peu de chance personnelle avec urllib3 http://code.google.com/p/urllib3/
2) BeautifulSoup est facile à utiliser, mais est assez lent. Si vous devez l'utiliser, assurez-vous de décomposer vos étiquettes pour vous débarrasser des fuites de mémoire.. ou il va probablement conduire à des problèmes de mémoire (a fait pour je.)
A quoi ressemblent votre mémoire et votre cpu? Si vous maxez votre CPU, assurez-vous que vous utilisez de vrais threads poids lourds, de sorte que vous pouvez exécuter sur plus de 1 core.
Que Diriez-vous d'utiliser pycurl?
Vous pouvez apt-get par
$ sudo apt-get python-pycurl
tout d'abord, vous devriez essayer les paquets multithreading/multiprocessing. Actuellement, les trois populaires sont multiprocessing ; concurrent.futures et[threading] [3]. Ces paquets pourraient vous aider à ouvrir plusieurs url en même temps, ce qui pourrait augmenter la vitesse.
plus important encore, après avoir utilisé le traitement multithread, et si vous essayez d'ouvrir des centaines d'urls en même temps, vous trouverez urllib.demande.urlopen est très lent, et l'ouverture et la lecture du contexte deviennent la partie la plus longue. Donc, si vous voulez le rendre encore plus rapide, vous devriez essayer de demandes de paquets, les demandes.get (url).contenu() est plus rapide que urllib.demande.urlopen (url).lire.)(
donc, voici deux exemples pour faire le parsing multi url rapide, et la vitesse est plus rapide que les autres réponses. Le premier exemple utilise le paquet filetage classique et génère des centaines de threads en même temps. (Une lacune insignifiante est qu'il ne peut pas conserver l'ordre d'origine du téléscripteur.)
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))
le second exemple utilise le paquet multiprocessing, et il est un peu plus simple. Puisque vous avez juste besoin d'indiquer le nombre de piscine et la carte de la fonction. L'ordre ne changera pas après avoir récupéré le contexte et la vitesse est similaire au premier exemple mais beaucoup plus rapide que les autres méthodes.
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))