J'ouvre un fichier qui a 100 000 URL. J'ai besoin d'envoyer une requête HTTP à chaque URL et d'imprimer le code d'état. J'utilise Python 2.6 et, jusqu'à présent, j'ai examiné les nombreuses façons source de confusion que Python implémente le threading/la concurrence. J'ai même regardé la bibliothèque python concurrence , mais je n'arrive pas à comprendre comment écrire ce programme correctement. Quelqu'un at-il rencontré un problème similaire? De manière générale, je suppose que je dois savoir comment effectuer des milliers de tâches dans Python le plus rapidement possible. Je suppose que cela signifie "simultanément".
Twisted Less solution:
from urlparse import urlparse
from threading import Thread
import httplib, sys
from Queue import Queue
concurrent = 200
def doWork():
while True:
url = q.get()
status, url = getStatus(url)
doSomethingWithResult(status, url)
q.task_done()
def getStatus(ourl):
try:
url = urlparse(ourl)
conn = httplib.HTTPConnection(url.netloc)
conn.request("HEAD", url.path)
res = conn.getresponse()
return res.status, ourl
except:
return "error", ourl
def doSomethingWithResult(status, url):
print status, url
q = Queue(concurrent * 2)
for i in range(concurrent):
t = Thread(target=doWork)
t.daemon = True
t.start()
try:
for url in open('urllist.txt'):
q.put(url.strip())
q.join()
except KeyboardInterrupt:
sys.exit(1)
Celui-ci est légèrement plus rapide que la solution torsadée et utilise moins de processeur.
Une solution utilisant tornado bibliothèque réseau asynchrone
from tornado import ioloop, httpclient
i = 0
def handle_request(response):
print(response.code)
global i
i -= 1
if i == 0:
ioloop.IOLoop.instance().stop()
http_client = httpclient.AsyncHTTPClient()
for url in open('urls.txt'):
i += 1
http_client.fetch(url.strip(), handle_request, method='HEAD')
ioloop.IOLoop.instance().start()
Les fils ne sont absolument pas la réponse ici. Ils fourniront à la fois des goulots d'étranglement au niveau du processus et du noyau, ainsi que des limites de débit inacceptables si l'objectif général est "le moyen le plus rapide".
Un peu de twisted
et son client asynchrone HTTP
vous donneraient de bien meilleurs résultats.
Les choses ont bien changé depuis 2010, date à laquelle cela a été publié. Je n'ai pas essayé toutes les autres réponses, mais j'en ai essayé quelques-unes, et j'ai trouvé que cela fonctionnait le mieux pour moi avec python3.6.
J'ai pu extraire environ 150 domaines uniques par seconde exécutés sur AWS.
import pandas as pd
import concurrent.futures
import requests
import time
out = []
CONNECTIONS = 100
TIMEOUT = 5
tlds = open('../data/sample_1k.txt').read().splitlines()
urls = ['http://{}'.format(x) for x in tlds[1:]]
def load_url(url, timeout):
ans = requests.head(url, timeout=timeout)
return ans.status_code
with concurrent.futures.ThreadPoolExecutor(max_workers=CONNECTIONS) as executor:
future_to_url = (executor.submit(load_url, url, TIMEOUT) for url in urls)
time1 = time.time()
for future in concurrent.futures.as_completed(future_to_url):
try:
data = future.result()
except Exception as exc:
data = str(type(exc))
finally:
out.append(data)
print(str(len(out)),end="\r")
time2 = time.time()
print(f'Took {time2-time1:.2f} s')
print(pd.Series(out).value_counts())
Utilisez grequests , c'est une combinaison de requêtes + module Gevent.
GRequests vous permet d'utiliser Requests avec Gevent pour créer facilement des requêtes HTTP asynchrones.
L'utilisation est simple:
import grequests
urls = [
'http://www.heroku.com',
'http://tablib.org',
'http://httpbin.org',
'http://python-requests.org',
'http://kennethreitz.com'
]
Créez un ensemble de demandes non envoyées:
>>> rs = (grequests.get(u) for u in urls)
Envoyez-les tous en même temps:
>>> grequests.map(rs)
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
Une bonne approche pour résoudre ce problème consiste à écrire d’abord le code requis pour obtenir un résultat, puis à incorporer du code de threading pour paralléliser l’application.
Dans un monde parfait, cela signifie simplement que 100 000 threads doivent être démarrés simultanément et que leurs résultats sont exportés dans un dictionnaire ou une liste pour un traitement ultérieur, mais en pratique, le nombre de demandes HTTP parallèles que vous pouvez émettre de cette manière est limité. Localement, vous avez une limite quant au nombre de sockets que vous pouvez ouvrir simultanément, au nombre de threads d'exécution que votre interprète Python autorisera. À distance, le nombre de connexions simultanées peut être limité si toutes les demandes concernent un ou plusieurs serveurs. Ces limitations nécessiteront probablement que vous écriviez le script de manière à n’interroger qu’une petite fraction des URL à la fois (100, comme l’a mentionné une autre affiche, correspond probablement à une taille de pool de threads décente, bien que vous constatiez peut-être que peut déployer avec succès beaucoup plus).
Vous pouvez suivre ce modèle pour résoudre le problème ci-dessus:
list
ou dict
dans CPython, vous pouvez ajouter ou insérer en toute sécurité des éléments uniques de vos threads sans verro , mais si vous écrivez Pour un fichier ou une interaction plus complexe entre données , vous devez utiliser un verrou d'exclusion mutuelle pour protéger cet état de la corruption .Je vous suggère d'utiliser le module threading . Vous pouvez l'utiliser pour lancer et suivre les threads en cours d'exécution. Le support de threading de Python est simple, mais la description de votre problème suggère qu'il est complètement suffisant pour vos besoins.
Enfin, si vous souhaitez voir une application assez simple d’une application réseau parallèle écrite en Python, consultez ssh.py . C'est une petite bibliothèque qui utilise Python threading pour mettre en parallèle de nombreuses connexions SSH. La conception est suffisamment proche de vos besoins pour que vous puissiez la trouver comme une bonne ressource.
Si vous souhaitez obtenir les meilleures performances possibles, envisagez d'utiliser des E/S asynchrones plutôt que des threads. La surcharge associée à des milliers de threads de système d'exploitation n'est pas triviale et la commutation de contexte au sein de l'interpréteur Python en ajoute encore plus. Les threads vont certainement faire le travail, mais je pense qu’une route asynchrone offrira de meilleures performances.
Plus précisément, je suggérerais le client Web asynchrone dans la bibliothèque Twisted ( http://www.twistedmatrix.com ). La courbe d’apprentissage est certes raide, mais elle est assez facile à utiliser une fois que vous maîtrisez bien le style de programmation asynchrone de Twisted.
Un guide pratique sur l'API client Web asynchrone de Twisted est disponible à l'adresse suivante:
http://twistedmatrix.com/documents/current/web/howto/client.html
Une solution:
from twisted.internet import reactor, threads
from urlparse import urlparse
import httplib
import itertools
concurrent = 200
finished=itertools.count(1)
reactor.suggestThreadPoolSize(concurrent)
def getStatus(ourl):
url = urlparse(ourl)
conn = httplib.HTTPConnection(url.netloc)
conn.request("HEAD", url.path)
res = conn.getresponse()
return res.status
def processResponse(response,url):
print response, url
processedOne()
def processError(error,url):
print "error", url#, error
processedOne()
def processedOne():
if finished.next()==added:
reactor.stop()
def addTask(url):
req = threads.deferToThread(getStatus, url)
req.addCallback(processResponse, url)
req.addErrback(processError, url)
added=0
for url in open('urllist.txt'):
added+=1
addTask(url.strip())
try:
reactor.run()
except KeyboardInterrupt:
reactor.stop()
Temps de test:
[kalmi@ubi1:~] wc -l urllist.txt
10000 urllist.txt
[kalmi@ubi1:~] time python f.py > /dev/null
real 1m10.682s
user 0m16.020s
sys 0m10.330s
[kalmi@ubi1:~] head -n 6 urllist.txt
http://www.google.com
http://www.bix.hu
http://www.godaddy.com
http://www.google.com
http://www.bix.hu
http://www.godaddy.com
[kalmi@ubi1:~] python f.py | head -n 6
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
Pingtime:
bix.hu is ~10 ms away from me
godaddy.com: ~170 ms
google.com: ~30 ms
Je sais que c’est une vieille question, mais dans Python 3.7, vous pouvez le faire avec asyncio
et aiohttp
.
import asyncio
import aiohttp
from aiohttp import ClientSession, ClientConnectorError
async def fetch_html(url: str, session: ClientSession, **kwargs) -> Tuple:
try:
resp = await session.request(method="GET", url=url, **kwargs)
except ClientConnectorError:
return (url, 404)
return (url, resp.status)
async def make_requests(urls: set, **kwargs) -> None:
async with ClientSession() as session:
tasks = []
for url in urls:
tasks.append(
fetch_html(url=url, session=session, **kwargs)
)
results = await asyncio.gather(*tasks)
for result in results:
print(f'{result[1]} - {str(result[0])}')
if __== "__main__":
import pathlib
import sys
assert sys.version_info >= (3, 7), "Script requires Python 3.7+."
here = pathlib.Path(__file__).parent
with open(here.joinpath("urls.txt")) as infile:
urls = set(map(str.strip, infile))
asyncio.run(make_requests(urls=urls))
Vous pouvez en lire plus à ce sujet et voir un exemple ici .
Créer un objet epoll
,
ouvrir plusieurs sockets client TCP,
ajuster leurs tampons d'envoi pour être un peu plus que l'en-tête de demande,
envoie un en-tête de requête - il devrait être immédiat, il suffit de le placer dans un tampon pour enregistrer socket dans epoll
objet,
ne .poll
sur epoll
obect,
lit les 3 premiers octets de chaque socket de .poll
,
écrivez-les dans sys.stdout
suivi de \n
(ne pas vider), fermez le socket client.
Limiter le nombre de sockets ouverts simultanément - gérer les erreurs lors de la création de sockets. Créez un nouveau socket uniquement si un autre est fermé.
Ajustez les limites du système d'exploitation.
Essayez d’interfacer quelques processus (pas beaucoup): cela peut aider à utiliser le processeur un peu plus efficacement.
Utiliser un pool de threads est une bonne option, et cela facilitera la tâche. Malheureusement, python ne dispose pas d'une bibliothèque standard qui rend les pools de threads extrêmement faciles. Mais voici une bonne bibliothèque qui devrait vous aider à démarrer: http://www.chrisarndt.de/projects/threadpool/
Exemple de code de leur site:
pool = ThreadPool(poolsize)
requests = makeRequests(some_callable, list_of_args, callback)
[pool.putRequest(req) for req in requests]
pool.wait()
J'espère que cela t'aides.
Ce client Web asynchrone tordu va assez vite.
#!/usr/bin/python2.7
from twisted.internet import reactor
from twisted.internet.defer import Deferred, DeferredList, DeferredLock
from twisted.internet.defer import inlineCallbacks
from twisted.web.client import Agent, HTTPConnectionPool
from twisted.web.http_headers import Headers
from pprint import pprint
from collections import defaultdict
from urlparse import urlparse
from random import randrange
import fileinput
pool = HTTPConnectionPool(reactor)
pool.maxPersistentPerHost = 16
agent = Agent(reactor, pool)
locks = defaultdict(DeferredLock)
codes = {}
def getLock(url, simultaneous = 1):
return locks[urlparse(url).netloc, randrange(simultaneous)]
@inlineCallbacks
def getMapping(url):
# Limit ourselves to 4 simultaneous connections per Host
# Tweak this number, but it should be no larger than pool.maxPersistentPerHost
lock = getLock(url,4)
yield lock.acquire()
try:
resp = yield agent.request('HEAD', url)
codes[url] = resp.code
except Exception as e:
codes[url] = str(e)
finally:
lock.release()
dl = DeferredList(getMapping(url.strip()) for url in fileinput.input())
dl.addCallback(lambda _: reactor.stop())
reactor.run()
pprint(codes)
Dans votre cas, les threads feront probablement l'affaire, car vous passerez probablement le plus de temps à attendre une réponse. Des modules utiles tels que file d'attente dans la bibliothèque standard pourraient vous aider.
J'avais déjà fait la même chose avec le téléchargement parallèle de fichiers auparavant et c'était assez bon pour moi, mais ce n'était pas à l'échelle dont vous parlez.
Si votre tâche était davantage liée au processeur, vous voudrez peut-être consulter le module multitraitement , qui vous permettra d'utiliser plus de processeurs/cœurs/threads le verrouillage est par processus)
Pensez à utiliser Windmill , bien que Windmill ne puisse probablement pas faire autant de threads.
Vous pouvez le faire avec un script Python roulé à la main sur 5 machines, chacune se connectant en sortie à l'aide des ports 40000 à 60000, ouvrant ainsi 100 000 connexions de port.
En outre, il pourrait être utile de faire un exemple de test avec une application QA bien filetée telle que OpenSTA afin de savoir combien chaque serveur peut gérer.
Essayez également d’utiliser Perl avec la classe LWP :: ConnCache. Vous obtiendrez probablement plus de performances (plus de connexions) de cette façon.