J'ai passé toute une journée à rechercher le fetcher d'URL multithread le plus simple possible en Python, mais la plupart des scripts que j'ai trouvés utilisaient des files d'attente, des bibliothèques de traitement multiple ou complexes.
Enfin, j'en ai écrit un moi-même, que je rapporte comme une réponse. S'il vous plaît n'hésitez pas à suggérer toute amélioration.
Je suppose que d'autres personnes ont peut-être cherché quelque chose de similaire.
Simplifier votre version originale autant que possible:
import threading
import urllib2
import time
start = time.time()
urls = ["http://www.google.com", "http://www.Apple.com", "http://www.Microsoft.com", "http://www.Amazon.com", "http://www.facebook.com"]
def fetch_url(url):
urlHandler = urllib2.urlopen(url)
html = urlHandler.read()
print "'%s\' fetched in %ss" % (url, (time.time() - start))
threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print "Elapsed Time: %s" % (time.time() - start)
Les seuls nouveaux trucs ici sont:
join
vous le dit déjà.Thread
, mais d'une fonction target
.multiprocessing
a un pool de threads qui ne démarre pas d'autres processus:
#!/usr/bin/env python
from multiprocessing.pool import ThreadPool
from time import time as timer
from urllib2 import urlopen
urls = ["http://www.google.com", "http://www.Apple.com", "http://www.Microsoft.com", "http://www.Amazon.com", "http://www.facebook.com"]
def fetch_url(url):
try:
response = urlopen(url)
return url, response.read(), None
except Exception as e:
return url, None, e
start = timer()
results = ThreadPool(20).imap_unordered(fetch_url, urls)
for url, html, error in results:
if error is None:
print("%r fetched in %ss" % (url, timer() - start))
else:
print("error fetching %r: %s" % (url, error))
print("Elapsed Time: %s" % (timer() - start,))
Les avantages par rapport à la solution Thread
-:
ThreadPool
permet de limiter le nombre maximum de connexions simultanées (20
dans l'exemple de code)from urllib.request import urlopen
sur Python 3).L'exemple principal dans le concurrent.futures
fait tout ce que vous voulez, beaucoup plus simplement. De plus, il peut gérer un grand nombre d'URL en en faisant seulement 5 à la fois, et il gère les erreurs beaucoup plus facilement.
Bien entendu, ce module est uniquement intégré à Python 3.2 ou version ultérieure… mais si vous utilisez la version 2.5-3.1, vous pouvez simplement installer le backport, futures
, en dehors de PyPI. Dans l'exemple de code, il vous suffit de rechercher et de remplacer concurrent.futures
par futures
et, pour 2.x, urllib.request
avec urllib2
.
Voici l'exemple de backported en 2.x, modifié pour utiliser votre liste d'URL et pour ajouter les heures:
import concurrent.futures
import urllib2
import time
start = time.time()
urls = ["http://www.google.com", "http://www.Apple.com", "http://www.Microsoft.com", "http://www.Amazon.com", "http://www.facebook.com"]
# Retrieve a single page and report the url and contents
def load_url(url, timeout):
conn = urllib2.urlopen(url, timeout=timeout)
return conn.readall()
# 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 '"%s" fetched in %ss' % (url,(time.time() - start))
print "Elapsed Time: %ss" % (time.time() - start)
Mais vous pouvez rendre cela encore plus simple. Vraiment, tout ce dont vous avez besoin est:
def load_url(url):
conn = urllib2.urlopen(url, timeout)
data = conn.readall()
print '"%s" fetched in %ss' % (url,(time.time() - start))
return data
with futures.ThreadPoolExecutor(max_workers=5) as executor:
pages = executor.map(load_url, urls)
print "Elapsed Time: %ss" % (time.time() - start)
Je publie maintenant une solution différente, par en ayant les threads de travail non-deamon et en les joignant au thread principal (ce qui signifie bloquer le thread principal jusqu'à ce que tous les threads de travail soient terminés) au lieu de notifier la fin de l'exécution de chaque thread de travail avec un rappel à une fonction globale (comme je l'ai fait dans la réponse précédente), comme dans certains commentaires, il a été noté qu'une telle manière n'est pas thread-safe.
import threading
import urllib2
import time
start = time.time()
urls = ["http://www.google.com", "http://www.Apple.com", "http://www.Microsoft.com", "http://www.Amazon.com", "http://www.facebook.com"]
class FetchUrl(threading.Thread):
def __init__(self, url):
threading.Thread.__init__(self)
self.url = url
def run(self):
urlHandler = urllib2.urlopen(self.url)
html = urlHandler.read()
print "'%s\' fetched in %ss" % (self.url,(time.time() - start))
for url in urls:
FetchUrl(url).start()
#Join all existing threads to main thread.
for thread in threading.enumerate():
if thread is not threading.currentThread():
thread.join()
print "Elapsed Time: %s" % (time.time() - start)