Was ist der schnellste Weg, um 100.000 HTTP-Anfragen in Python zu senden?

Ich öffne eine Datei mit 100.000 URLs. Ich muss eine HTTP-Anfrage an jede URL senden und den Statuscode ausdrucken. Ich bin mit Python 2.6, und so weit sah die vielen verwirrenden Möglichkeiten Python implementiert Threading / Parallelität. Ich habe sogar die Python- Concurrence- Bibliothek angesehen, kann aber nicht herausfinden, wie man dieses Programm richtig schreibt. Hat jemand ein ähnliches Problem? Ich denke, generell muss ich wissen, wie man Tausende von Aufgaben in Python so schnell wie möglich ausführt – ich vermute, das bedeutet "gleichzeitig".

Danke, Igor

  • Python Requests-Bibliothek verwenden
  • Python: HTTP PUT mit uncodierten Binärdaten
  • Http-Anfrage mit Timeout, maximale Größe und Verbindungs-Pooling
  • Erstellen und Partern von Multipart-HTTP-Anforderungen in Python
  • Verwenden von urllib2 über Proxy
  • Unterhaltende eventlet.wsgi.server
  • Stream eine Datei auf die HTTP-Antwort in Pylons
  • Wie man pycurl benutzt, wenn angeforderte Daten manchmal gezippt werden, manchmal nicht?
  • 12 Solutions collect form web for “Was ist der schnellste Weg, um 100.000 HTTP-Anfragen in Python zu senden?”

    Verdrehte Lösung:

     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) 

    Dieser ist etwas schneller als die verdrehte Lösung und nutzt weniger CPU.

    Eine Lösung mit Tornado asynchroner Netzwerkbibliothek

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

    Threads sind hier absolut nicht die Antwort. Sie werden sowohl Prozess- als auch Kernel-Engpässe bereitstellen, sowie Durchsatzgrenzen, die nicht akzeptabel sind, wenn das Gesamtziel "der schnellste Weg" ist.

    Ein bisschen twisted und sein asynchroner HTTP Client würde Ihnen viel bessere Ergebnisse geben.

    Verwenden Sie Grequests , es ist eine Kombination von Anfragen + Gevent-Modul.

    GRequests erlaubt Ihnen, Anfragen mit Gevent zu verwenden, um asynchrone HTTP-Anfragen leicht zu machen.

    Verwendung ist einfach:

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

    Erstellen Sie einen Satz von unsent Anfragen:

     >>> rs = (grequests.get(u) for u in urls) 

    Schicke sie alle zur gleichen Zeit:

     >>> grequests.map(rs) [<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>] 

    Ein guter Ansatz zur Lösung dieses Problems besteht darin, zuerst den Code zu schreiben, der benötigt wird, um ein Ergebnis zu erhalten, und dann den Thread-Code einzufügen, um die Anwendung zu parametrieren.

    In einer perfekten Welt würde dies einfach bedeuten, gleichzeitig 100.000 Threads zu starten, die ihre Ergebnisse in ein Wörterbuch oder eine Liste für die spätere Verarbeitung ausgeben, aber in der Praxis sind Sie begrenzt, wie viele parallele HTTP-Anfragen können Sie auf diese Weise ausgeben. Vor Ort haben Sie Grenzen, wie viele Steckdosen Sie gleichzeitig öffnen können, wie viele Gewinde der Ausführung Ihr Python-Interpreter erlaubt. Remote, können Sie in der Anzahl der gleichzeitigen Verbindungen begrenzt sein, wenn alle Anfragen gegen einen Server oder viele sind. Diese Einschränkungen werden wahrscheinlich dazu führen, dass Sie das Skript so schreiben, dass nur ein kleiner Bruchteil der URLs zu einem beliebigen Zeitpunkt abfragt wird (100, wie ein anderes Poster erwähnt wird, ist wahrscheinlich eine anständige Fadenpoolgröße, obwohl Sie das vielleicht finden können Kann erfolgreich viel mehr implementieren).

    Sie können diesem Entwurfsmuster folgen, um das oben genannte Problem zu lösen:

    1. Starten Sie einen Thread, der neue Anforderungs-Threads startet, bis die Anzahl der aktuell laufenden Threads (Sie können sie über threading.active_count () oder durch Drücken der Thread-Objekte in eine Datenstruktur verfolgen) ist> = Ihre maximale Anzahl gleichzeitiger Anfragen (zB 100) , Dann schläft für ein kurzes Timeout. Dieser Thread sollte beenden, wenn es keine weiteren URLs gibt. So wird der Faden aufwachen, neue Fäden starten und schlafen, bis du fertig bist.
    2. Haben die Anforderungs-Threads speichern ihre Ergebnisse in einigen Datenstruktur für spätere Abfrage und Ausgabe. Wenn die Struktur, in der Sie die Ergebnisse speichern, eine list oder ein dict in CPython ist, können Sie sicher einfügen oder einfügen einzelner Elemente aus Ihren Threads ohne Sperren , aber wenn Sie in eine Datei schreiben oder eine komplexere Cross-Thread-Daten-Interaktion benötigen , sollten Sie Verwenden Sie eine gegenseitige Ausschlussverriegelung, um diesen Zustand vor Korruption zu schützen .

    Ich würde vorschlagen, dass Sie das Threading- Modul verwenden. Sie können es verwenden, um laufende Threads zu starten und zu verfolgen. Python's Threading-Unterstützung ist nackt, aber die Beschreibung Ihres Problems deutet darauf hin, dass es völlig ausreichend für Ihre Bedürfnisse ist.

    Schließlich, wenn Sie eine ziemlich einfache Anwendung einer parallelen Netzwerkanwendung in Python geschrieben sehen möchten , schauen Sie sich ssh.py an . Es ist eine kleine Bibliothek, die Python-Threading verwendet, um viele SSH-Verbindungen zu parallelisieren. Das Design ist nah genug an Ihre Anforderungen, dass Sie es finden können, um eine gute Ressource zu sein.

    Eine Lösung:

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

    Testzeit:

     [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 

    Wenn du schaffst, die beste Leistung zu erzielen, möchtest du vielleicht die Verwendung von asynchronen I / O anstatt Threads in Erwägung ziehen. Der Overhead, der mit Tausenden von OS-Threads verknüpft ist, ist nicht trivial und der Kontext, der innerhalb des Python-Interpreters wechselt, fügt noch mehr hinzu. Threading wird sicherlich die Arbeit erledigt, aber ich vermute, dass eine asynchrone Route eine bessere Gesamtleistung bieten wird.

    Speziell würde ich den asynchronen Webclient in der Twisted Library ( http://www.twistedmatrix.com ) vorschlagen. Es hat eine zugegebenermaßen steile Lernkurve, aber es ist ganz einfach zu bedienen, sobald man einen guten Griff auf Twisted's Stil der asynchronen Programmierung.

    Eine asTo on Twisted asynchrone Web Client API ist abrufbar unter:

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

    Mit einem Faden Pool ist eine gute Option, und wird dies ziemlich einfach machen. Leider hat python keine Standardbibliothek, die Thread-Pools extrem einfach macht. Aber hier ist eine anständige Bibliothek, die Sie anfangen sollte: http://www.chrisarndt.de/projects/threadpool/

    Codebeispiel von ihrer Seite:

     pool = ThreadPool(poolsize) requests = makeRequests(some_callable, list_of_args, callback) [pool.putRequest(req) for req in requests] pool.wait() 

    Hoffe das hilft.

    Für deinen Fall wird das Threading wahrscheinlich den Trick machen, da du wahrscheinlich die meiste Zeit damit verbracht hast, auf eine Antwort zu warten. Es gibt hilfreiche Module wie Queue in der Standardbibliothek, die helfen könnten.

    Ich habe eine ähnliche Sache mit dem parallelen Download von Dateien vor und es war gut genug für mich, aber es war nicht auf der Skala, die Sie reden.

    Wenn Ihre Aufgabe mehr CPU-gebunden wäre, möchten Sie vielleicht das Multiprocessing- Modul anschauen, mit dem Sie mehr CPUs / Cores / Threads nutzen können (mehr Prozesse, die sich nicht blockieren, da die Sperre pro Prozess erfolgt)

    Betrachten Sie die Verwendung von Windmühle , obwohl Windmühle wahrscheinlich nicht so viele Fäden machen kann.

    Sie könnten es mit einer Hand rollen Python-Skript auf 5 Maschinen, die jeweils ausgehenden mit Ports 40000-60000, öffnen 100.000 Port-Verbindungen.

    Auch könnte es helfen, einen Beispieltest mit einer gut gewickelten QA App wie OpenSTA zu machen, um eine Vorstellung davon zu bekommen, wie viel jeder Server verarbeiten kann.

    Versuchen Sie auch nur die Verwendung von einfachem Perl mit der LWP :: ConnCache-Klasse. Sie werden wahrscheinlich mehr Leistung (mehr Verbindungen) auf diese Weise bekommen.

    Dieser verdrehte async Web-Client geht ziemlich schnell.

     #!/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) 

    Der einfachste Weg wäre, Pythons eingebaute Threading-Bibliothek zu verwenden. Sie sind nicht "echte" / Kernel-Threads, aber sind gut genug. Du möchtest einen Warteschlangen- und Thread-Pool. Eine Option ist hier , aber es ist trivial, deine eigenen zu schreiben. Sie können nicht alle 100.000 Anrufe parallelisieren, aber Sie können 100 (oder so) von ihnen zur gleichen Zeit abfeuern.

    Python ist die beste Programmiersprache der Welt.