Quel est le moyen le plus rapide d'envoyer 100 000 requêtes HTTP en Python?

j'ouvre un fichier qui contient 100 000 url. Je dois envoyer une demande http à chaque url et imprimer le code d'état. J'utilise Python 2.6, et jusqu'à présent j'ai regardé les nombreuses façons confuses Python implements threading/competiency. J'ai même regardé la bibliothèque python concurrence , mais je ne sais pas comment écrire correctement ce programme. Quelqu'un a rencontré un problème similaire? Je suppose que généralement je dois savoir comment effectuer des milliers de tâches en Python aussi vite que possible - je suppose que cela signifie 'simultanément'.

200
demandé sur gdoron 2010-04-13 23:19:50

13 réponses

Twisteless 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 tordue et utilise moins de CPU.

157
répondu Tarnay Kálmán 2014-08-28 12:38:46

Une solution à l'aide de tornade asynchrone mise en réseau de la bibliothèque

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()
42
répondu mher 2014-08-28 13:11:46

fils ne sont absolument pas la réponse ici. Ils fourniront à la fois des goulots d'étranglement du processus et du noyau, ainsi que des limites de débit qui ne sont pas acceptables si l'objectif général est "la voie la plus rapide".

un peu de twisted et son client asynchrone HTTP vous donnerait de bien meilleurs résultats.

30
répondu ironfroggy 2010-04-13 20:14:08

Use grequests , c'est une combinaison de requêtes + module Gevent .

GRequests vous permet d'utiliser des requêtes avec Gevent pour faire des requêtes HTTP asyncrones facilement.

L'Usage est simple:

import grequests

urls = [
   'http://www.heroku.com',
   'http://tablib.org',
   'http://httpbin.org',
   'http://python-requests.org',
   'http://kennethreitz.com'
]

créer un ensemble de requêtes non actives:

>>> 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]>]
13
répondu Akshay Pratap Singh 2014-07-14 07:41:25

les choses ont un peu changé depuis 2010 quand ceci a été posté et je n'ai pas essayé toutes les autres réponses mais j'ai essayé quelques-unes, et j'ai trouvé que cela fonctionne le mieux pour moi en utilisant python3.6.

j'ai été capable de récupérer environ ~150 domaines uniques par seconde tournant 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())
12
répondu Glen Thompson 2018-07-24 05:43:21

si vous cherchez à obtenir la meilleure performance possible, vous pourriez envisager d'utiliser des e/s asynchrones plutôt que des threads. Les frais généraux associés à des milliers de threads D'OS ne sont pas triviaux et la commutation de contexte au sein de L'interpréteur Python en ajoute encore plus. Le filetage permettra certainement de faire le travail, mais je soupçonne qu'une route asynchrone fournira une meilleure performance globale.

en particulier, je suggérerais le client Web async dans le Bibliothèque tordue ( ) http://www.twistedmatrix.com ). Il a certes courbe d'apprentissage très raide mais très facile à utiliser une fois que vous obtenez une bonne poignée Torsadée style de programmation asynchrone.

un HowTo sur L'API client Web asynchrone de Twisted est disponible à:

http://twistedmatrix.com/documents/current/web/howto/client.html

7
répondu Rakis 2010-04-13 20:12:47

une bonne approche pour résoudre ce problème est d'abord d'écrire le code requis pour obtenir un résultat, puis d'incorporer le code de threading pour paralléliser l'application.

dans un monde parfait cela signifierait simplement démarrer simultanément 100 000 threads qui produisent leurs résultats dans un dictionnaire ou une liste pour un traitement ultérieur, mais en pratique vous êtes limité dans le nombre de requêtes HTTP parallèles que vous pouvez émettre de cette façon. Localement, vous avez des limites dans le nombre sockets vous pouvez ouvrir simultanément, le nombre de threads d'exécution de votre interpréteur Python. À distance, vous pouvez être limité dans le nombre de connexions simultanées si toutes les demandes sont contre un serveur ou à plusieurs. Ces limitations nécessiteront probablement que vous écriviez le script de manière à ne sonder qu'une petite fraction des URLs à tout moment (100, comme un autre poster l'a mentionné, est probablement une taille de pool de thread décente, bien que vous puissiez trouver que vous pouvez réussir déployer beaucoup plus).

vous pouvez suivre ce modèle pour résoudre le problème ci-dessus:

  1. démarrez un thread qui lance de nouveaux threads de requête jusqu'au nombre de threads en cours d'exécution (vous pouvez les suivre via threading.active_count() ou en poussant les objets thread dans une structure de données) est >= votre nombre maximum de requêtes simultanées (disons 100), puis dort pendant un court délai. Ce fil doit se terminer quand il n'y a pas de plusieurs Url à traiter. Ainsi, le fil continuera de se réveiller, de lancer de nouveaux fils, et de dormir jusqu'à ce que votre est terminé.
  2. demandent aux threads de requête de stocker leurs résultats dans une structure de données pour une extraction et une sortie ultérieures. Si la structure dans laquelle vous stockez les résultats est un list ou dict dans CPython, vous pouvez ajouter ou insérer en toute sécurité des éléments uniques à partir de vos threads sans serrures , mais si vous écrivez à un fichier ou exiger en plus complexe de la croix-fil des données d'interaction vous devez utiliser un verrou d'exclusion mutuelle pour protéger cet état de la corruption .

je vous suggère d'utiliser le module de filetage . Vous pouvez l'utiliser pour lancer et suivre les threads en cours d'exécution. Le support de threading de Python est nu, mais la description de votre problème suggère qu'il est tout à fait suffisant pour vos besoins.

enfin, si vous voulez voir une application assez simple d'une application réseau parallèle écrite en Python, vérifier ssh.py . C'est une petite bibliothèque qui utilise Python threading pour paralléliser de nombreuses connexions SSH. La conception est assez proche de vos exigences que vous pouvez trouver qu'il s'agit d'une bonne ressource.

7
répondu Erik Garrison 2017-05-23 12:02:49

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

Testtime:

[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
5
répondu Tarnay Kálmán 2010-04-14 05:23:05

utiliser un thread pool est une bonne option, et rendra cela assez facile. Malheureusement, python n'a pas de bibliothèque standard qui rend les piscines de thread ultra faciles. Mais voici une bibliothèque décente 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()

Espérons que cette aide.

1
répondu Kevin Wiskia 2010-04-13 19:42:41

pour votre cas, fileter fera probablement l'affaire car vous passerez probablement le plus de temps à attendre une réponse. Il y a des modules utiles comme file dans la bibliothèque standard qui pourraient aider.

j'ai fait une chose similaire avec le téléchargement parallèle de fichiers avant et il était assez bon pour moi, mais il n'était pas sur l'échelle dont vous parlez.

si votre tâche était plus liée au CPU, vous pourriez vouloir regarder multiprocessing module, qui vous permettra d'utiliser plus de processeurs/noyaux/threads (plus de processus qui ne se bloquent pas les uns les autres puisque le verrouillage est par processus)

0
répondu Mattias Nilsson 2010-04-13 19:43:02

envisager d'utiliser Moulin à vent , bien que Moulin à vent ne peut probablement pas faire que de nombreux fils.

vous pouvez le faire avec un script Python roulé à la main sur 5 machines, chacune connectant à l'extérieur en utilisant les ports 40000-60000, ouvrant 100 000 connexions de port.

aussi, il pourrait être utile de faire un test d'échantillon avec une application QA bien filetée comme OpenSTA afin d'obtenir une idée de combien chaque serveur peut gérer.

aussi, essayez de chercher simplement à utiliser Perl simple avec la classe LWP::ConnCache. Vous obtiendrez probablement plus de performance (plus de connexions) de cette façon.

0
répondu djangofan 2010-04-13 20:20:02

ce client async web 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)
0
répondu Robᵩ 2014-08-12 03:00:20

la façon la plus simple serait d'utiliser la bibliothèque de threading intégrée à Python. ils ne sont pas" réels "/ threads du noyau ils ont des problèmes (comme la sérialisation), mais sont assez bons. Vous voulez une file d'attente & pool de threads. Une option est ici , mais il est trivial d'écrire la vôtre. Vous ne pouvez pas mettre en parallèle les 100 000 appels, mais vous pouvez en virer une centaine (ou plus) en même temps.

-2
répondu pestilence669 2017-12-25 20:42:15