Qual è il modo più veloce per inviare 100.000 richieste HTTP in Python?


287

Sto aprendo un file con 100.000 URL. Devo inviare una richiesta HTTP a ciascun URL e stampare il codice di stato. Sto usando Python 2.6 e finora ho esaminato i molti modi confusi in cui Python implementa il threading / la concorrenza. Ho anche dato un'occhiata alla libreria di concurrence di Python , ma non riesco a capire come scrivere correttamente questo programma. Qualcuno ha riscontrato un problema simile? Immagino che in genere devo sapere come eseguire migliaia di attività in Python il più velocemente possibile - suppongo che significhi "contemporaneamente".


47
Assicurati di eseguire solo la richiesta HEAD (in modo da non scaricare l'intero documento). Vedi: stackoverflow.com/questions/107405/...
Tarnay Kálmán

5
Ottimo punto, Kalmi. Se tutto ciò che Igor vuole è lo stato della richiesta, queste 100K richieste andranno molto, molto, molto più velocemente. Molto più veloce
Adam Crossland,

1
Non hai bisogno di discussioni per questo; il modo più efficiente è probabilmente quello di utilizzare una libreria asincrona come Twisted.
jemfinch,

3
qui ci sono esempi di codice basati su gevent, twisted e asyncio (testati su 1000000 richieste)
jfs

4
@ TarnayKálmán è possibile per ( requests.gete requests.headcioè una richiesta di pagina contro una richiesta principale) di restituire codici di stato diversi, quindi questo non è il miglior consiglio
AlexG

Risposte:


200

Soluzione Twistedless:

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)

Questo è leggermente più veloce della soluzione twistata e utilizza meno CPU.


10
@ Kalmi, perché imposti la coda su concurrent*2?
Marcel Wilson,

8
Non dimenticare di chiudere la connessione conn.close() . L'apertura di troppe connessioni http potrebbe arrestare lo script ad un certo punto e consumare memoria.
Aamir Adnan,

4
@hyh, il Queuemodulo è stato rinominato queuein Python 3. Questo è il codice Python 2.
Tarnay Kálmán,

3
Quanto più veloce puoi andare se vuoi parlare ogni volta con il server SAME, persistendo nella connessione? Questo può essere fatto anche su più thread o con una connessione persistente per thread?
mdurant,

2
@mptevsion, se stai usando CPython, potresti (per esempio) semplicemente sostituire "print status, url" con "my_global_list.append ((status, url))". Gli elenchi (la maggior parte delle operazioni su) sono implicitamente thread-safe in CPython (e in alcune altre implementazioni di Python) a causa di GIL, quindi è sicuro farlo.
Tarnay Kálmán,

54

Una soluzione che utilizza la libreria di rete asincrona di tornado

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

7
Questo codice utilizza I / O di rete non bloccanti e non ha alcuna restrizione. Può scalare fino a decine di migliaia di connessioni aperte. Funzionerà in un singolo thread ma sarà molto più veloce di qualsiasi soluzione di threading. Acquista I / O senza blocco en.wikipedia.org/wiki/Asynchronous_I/O
mher

1
Puoi spiegare cosa sta succedendo qui con la variabile i globale? Una sorta di controllo degli errori?
LittleBobbyTables

4
È un contatore per determinare quando uscire da `` ioloop` - quindi quando hai finito.
Michael Dorner,

1
@AndrewScottEvans ha ipotizzato che tu stia usando Python 2.7 e proxy
Dejell

5
@Guy Avraham Buona fortuna per ottenere aiuto sul tuo piano ddos.
Walter,

51

Le cose sono cambiate un po 'dal 2010, quando questo è stato pubblicato e non ho provato tutte le altre risposte, ma ne ho provate alcune e ho trovato che funziona meglio con me usando python3.6.

Sono stato in grado di recuperare circa 150 domini unici al secondo in esecuzione su 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())

1
Lo sto chiedendo solo perché non lo so, ma questa roba futures potrebbe essere sostituita da asincrona / attende?
TankorSmash,

1
Potrebbe, ma ho trovato quanto sopra per funzionare meglio. potresti usare aiohttp ma non fa parte della lib standard e sta cambiando molto. Funziona ma non ho ancora trovato che funzioni. Ottengo tassi di errore più alti quando lo uso e per la mia vita non riesco a farlo funzionare così come i futuri concorrenti anche se in teoria Sembra che dovrebbe funzionare meglio, vedi: stackoverflow.com/questions/45800857/… se riesci a farlo funzionare correttamente, pubblica la tua risposta in modo che io possa provarla.
Glen Thompson,

1
Questo è un gioco da ragazzi, ma penso che sia molto più pulito da mettere time1 = time.time()in cima al ciclo for e time2 = time.time()subito dopo il ciclo for.
Matt M.

Ho testato il tuo frammento, in qualche modo viene eseguito due volte. Sto facendo qualcosa di sbagliato? O è pensato per funzionare due volte? Se è l'ultimo caso, puoi anche aiutarmi a capire come si innesca due volte?
Ronnie

1
Non dovrebbe funzionare due volte. Non sono sicuro del perché lo stai vedendo.
Glen Thompson,

40

Le discussioni non sono assolutamente la risposta qui. Forniranno colli di bottiglia sia del processo che del kernel, nonché limiti di velocità che non sono accettabili se l'obiettivo generale è "il modo più veloce".

Un po 'di twistede il suo HTTPclient asincrono ti darebbe risultati molto migliori.


ironfroggy: mi sto sporgendo verso i tuoi sentimenti. Ho provato ad implementare la mia soluzione con thread e code (per i mutex automatici), ma puoi immaginare quanto tempo ci vuole per popolare una coda con 100.000 cose ?? Sto ancora giocando con diverse opzioni e suggerimenti da parte di tutti su questo thread, e forse Twisted sarà una buona soluzione.
IgorGanapolsky,

2
Puoi evitare di popolare una coda con 100.000 cose. Basta elaborare gli articoli uno alla volta dal tuo input, quindi avviare un thread per elaborare la richiesta corrispondente a ciascun elemento. (Come descriverò di seguito, utilizzare un thread di avvio per avviare i thread di richiesta HTTP quando il conteggio dei thread è al di sotto di una soglia. Fare in modo che i thread scrivano i risultati in un URL di mapping dict per rispondere o aggiungere tuple a un elenco.)
Erik Guarnigione

ironfroggy: Inoltre, sono curioso di sapere quali strozzature hai trovato usando i thread Python? E come interagiscono i thread Python con il kernel del sistema operativo?
Erik Garrison,

Assicurati di installare il reattore epoll; altrimenti userai select / poll e sarà molto lento. Inoltre, se in realtà proverai ad aprire contemporaneamente 100.000 connessioni (supponendo che il tuo programma sia scritto in questo modo e che gli URL siano su server diversi), dovrai ottimizzare il tuo sistema operativo in modo da non rimanere senza di descrittori di file, porte effimere, ecc. (probabilmente è più semplice assicurarsi di non avere più di, per esempio, 10.000 connessioni in sospeso contemporaneamente).
Mark Nottingham,

erikg: mi hai consigliato un'ottima idea. Tuttavia, il miglior risultato che sono riuscito a ottenere con 200 thread è stato di ca. 6 minuti Sono sicuro che ci sono modi per farlo in minor tempo ... Mark N: se Twisted è il modo in cui decido di andare, allora il reattore epoll è sicuramente utile. Tuttavia, se il mio script verrà eseguito da più macchine, ciò non richiederebbe l'installazione di Twisted su OGNI macchina? Non so se riesco a convincere il mio capo a seguire quella strada ...
IgorGanapolsky

21

So che questa è una vecchia domanda, ma in Python 3.7 puoi farlo usando asyncioe 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 __name__ == "__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))

Puoi leggere di più a riguardo e vedere un esempio qui .


È simile a C # async / await e Kotlin Coroutines?
IgorGanapolsky,

@IgorGanapolsky, sì, è molto simile a C # async / waitit. Non ho familiarità con le coroutine di Kotlin.
Marius Stănescu,

@sandyp, non sono sicuro che funzioni, ma se vuoi provare dovrai usare UnixConnector per aiohttp. Maggiori informazioni qui: docs.aiohttp.org/it/stable/client_reference.html#connectors .
Marius Stănescu,

Grazie @ MariusStănescu. Questo è esattamente quello che ho usato.
sandyp,

+1 per mostrare asyncio.gather (* attività). ecco uno di questi frammenti che ho usato: urls= [fetch(construct_fetch_url(u),idx) for idx, u in enumerate(some_URI_list)] results = await asyncio.gather(*urls)
Ashwini Kumar il

19

Usa grequests , è una combinazione di richieste + modulo Gevent.

GRequests ti consente di utilizzare le richieste con Gevent per rendere facilmente richieste HTTP asincrone.

L'uso è semplice:

import grequests

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

Crea una serie di richieste non inviate:

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

Inviali tutti contemporaneamente:

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

7
gevent ora supporta python 3
Benjamin Toueg il

14
le grequest non fanno parte delle normali richieste e sembrano essere in gran parte non mantenute
Thom,

8

Un buon approccio per risolvere questo problema è innanzitutto scrivere il codice richiesto per ottenere un risultato, quindi incorporare il threading code per parallelizzare l'applicazione.

In un mondo perfetto ciò significherebbe semplicemente avviare simultaneamente 100.000 thread che generano i loro risultati in un dizionario o in un elenco per l'elaborazione successiva, ma in pratica si è limitati a quante richieste HTTP parallele è possibile inviare in questo modo. A livello locale, hai dei limiti su quanti socket puoi aprire contemporaneamente, quanti thread di esecuzione consentirà il tuo interprete Python. In remoto, potresti avere un numero limitato di connessioni simultanee se tutte le richieste sono indirizzate a un server o a molte. Queste limitazioni probabilmente richiederanno che tu scriva lo script in modo da eseguire il polling solo una piccola parte degli URL in qualsiasi momento (100, come menzionato da un altro poster, è probabilmente una dimensione del pool di thread decente, anche se potresti scoprire che tu può implementare con successo molti altri).

È possibile seguire questo modello di progettazione per risolvere il problema sopra riportato:

  1. Avvia un thread che avvia nuovi thread di richieste fino a quando il numero di thread attualmente in esecuzione (puoi seguirli tramite threading.active_count () o spingendo gli oggetti thread in una struttura di dati) è> = il tuo numero massimo di richieste simultanee (diciamo 100) , quindi dorme per un breve timeout. Questo thread dovrebbe terminare quando non ci sono più URL da elaborare. Pertanto, il thread continuerà a svegliarsi, lanciando nuovi thread e dormendo fino al termine.
  2. Chiedi ai thread di richiesta di archiviare i loro risultati in una struttura di dati per il successivo recupero e output. Se la struttura in cui si stanno archiviando i risultati è a listo dictin CPython, è possibile aggiungere o inserire in modo sicuro elementi univoci dai thread senza blocchi , ma se si scrive su un file o si richiede un'interazione tra thread più complessa, è necessario utilizzare un blocco di esclusione reciproca per proteggere questo stato dalla corruzione .

Vorrei suggerire di utilizzare il modulo di threading . Puoi usarlo per avviare e tenere traccia dei thread in esecuzione. Il supporto per il threading di Python è semplice, ma la descrizione del tuo problema suggerisce che è completamente sufficiente per le tue esigenze.

Infine, se si desidera vedere una bella semplice applicazione di un'applicazione di rete in parallelo scritto in Python, controlla ssh.py . È una piccola libreria che utilizza il threading Python per parallelizzare molte connessioni SSH. Il design è abbastanza vicino alle tue esigenze che potresti trovare una buona risorsa.


1
erikg: gettare una coda nella tua equazione sarebbe ragionevole (per il blocco dell'esclusione reciproca)? Sospetto che GIL di Python non sia orientato a giocare con migliaia di thread.
IgorGanapolsky,

Perché è necessario il blocco dell'esclusione reciproca per impedire la generazione di troppi thread? Sospetto di aver frainteso il termine. È possibile tenere traccia dei thread in esecuzione in una coda di thread, rimuovendoli al completamento e aggiungendo altri fino a tale limite di thread. Ma in un caso semplice come quello in questione puoi anche solo guardare il numero di thread attivi nel processo Python corrente, attendere fino a quando non scende al di sotto di una soglia e avviare più thread fino alla soglia come descritto. Immagino che potresti considerarlo un blocco implicito, ma nessun blocco esplicito è richiesto in seguito.
Erik Garrison,

erikg: più thread non condividono lo stato? Nella pagina 305 del libro di O'Reilly "Python for Unix and Linux System Administration" si afferma: "... l'uso del threading senza code lo rende più complesso di quanto molte persone possano realisticamente gestire. È un'idea molto migliore usare sempre l'accodamento se trovi che devi usare i thread. Perché? Perché il modulo della coda allevia anche la necessità di proteggere esplicitamente i dati con i mutex perché la coda stessa è già protetta internamente da un mutex. " Ancora una volta, accolgo con favore il tuo punto di vista al riguardo.
IgorGanapolsky,

Igor: Hai perfettamente ragione che dovresti usare un lucchetto. Ho modificato il post per riflettere questo. Detto questo, l'esperienza pratica con Python suggerisce che non è necessario bloccare le strutture di dati che si modificano atomicamente dai thread, ad esempio list.append o aggiungendo una chiave hash. Il motivo, credo, è il GIL, che fornisce operazioni come list.append con un certo grado di atomicità. Attualmente sto eseguendo un test per verificarlo (utilizzare thread 10k per aggiungere i numeri 0-9999 a un elenco, verificare che tutti gli allegati abbiano funzionato). Dopo quasi 100 iterazioni il test non è fallito.
Erik Garrison,

Igor: Mi viene posta un'altra domanda su questo argomento: stackoverflow.com/questions/2740435/…
Erik Garrison,

7

Se stai cercando di ottenere le migliori prestazioni possibili, potresti prendere in considerazione l'uso dell'I / O asincrono anziché i thread. Il sovraccarico associato a migliaia di thread del sistema operativo non è banale e il cambio di contesto all'interno dell'interprete Python ne aggiunge ancora di più. Il threading farà sicuramente il suo lavoro, ma sospetto che un percorso asincrono fornirà migliori prestazioni complessive.

In particolare, suggerirei il client Web asincrono nella libreria Twisted ( http://www.twistedmatrix.com ). Ha una curva di apprendimento certamente ripida, ma è abbastanza facile da usare quando si ha una buona conoscenza dello stile di programmazione asincrona di Twisted.

Un HowTo sull'API del client Web asincrono di Twisted è disponibile all'indirizzo:

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


Rakis: Sto attualmente esaminando I / O asincroni e non bloccanti. Devo impararlo meglio prima di implementarlo. Un commento che vorrei fare sul tuo post è che è impossibile (almeno sotto la mia distribuzione Linux) generare "migliaia di thread del sistema operativo". Esiste un numero massimo di thread che Python ti permetterà di generare prima che il programma si interrompa. E nel mio caso (su CentOS 5) il numero massimo di thread è 303.
IgorGanapolsky

Buono a sapersi. Non ho mai provato a spawnare più di una manciata in Python contemporaneamente, ma mi sarei aspettato di essere in grado di creare più di quello prima che bombardasse.
Rakis,

6

Una soluzione:

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

6
L'uso di Twisted come threadpool ignora la maggior parte dei vantaggi che puoi ottenere da esso. Dovresti invece utilizzare il client HTTP asincrono.
Jean-Paul Calderone,

1

L'uso di un pool di thread è una buona opzione e lo renderà abbastanza semplice. Sfortunatamente, python non ha una libreria standard che rende i pool di thread ultra facili. Ma ecco una biblioteca decente che dovrebbe iniziare: http://www.chrisarndt.de/projects/threadpool/

Esempio di codice dal loro sito:

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

Spero che questo ti aiuti.


Suggerisco di specificare q_size per ThreadPool in questo modo: ThreadPool (poolsize, q_size = 1000) In modo da non avere 100000 oggetti WorkRequest in memoria. "Se q_size> 0 la dimensione della coda delle richieste di lavoro è limitata e il pool di thread si blocca quando la coda è piena e tenta di inserire più richieste di lavoro (vedere il putRequestmetodo), a meno che non si usi anche un timeoutvalore positivo per putRequest."
Tarnay Kálmán,

Finora sto cercando di implementare la soluzione threadpool - come suggerito. Tuttavia, non capisco l'elenco dei parametri nella funzione makeRequests. Cos'è some_callable, list_of_args, callback? Forse se vedessi un frammento di codice reale che potrebbe aiutare. Sono sorpreso che l'autore di quella biblioteca non abbia pubblicato NESSUN esempio.
IgorGanapolsky,

some_callable è la funzione in cui viene svolto tutto il lavoro (connessione al server http). list_of_args è argomenti che verranno passati in some_callabe. callback è una funzione che verrà chiamata al termine del thread di lavoro. Sono necessari due argomenti, l'oggetto worker (in realtà non è necessario preoccuparsi di te stesso) e i risultati recuperati dall'operatore.
Kevin Wiskia,

1

Crea epolloggetto,
apri molti socket TCP client,
regola i loro buffer di invio in modo che siano un po 'più di un'intestazione di richiesta,
invia un'intestazione di richiesta - dovrebbe essere immediato, semplicemente inserendo un buffer, registra un socket epollnell'oggetto,
fai .pollsu epollobect,
leggi prima 3 byte da ciascun socket da .poll,
scriverli a sys.stdoutseguiti da \n(non svuotare), chiudere il socket client.

Limitare il numero di socket aperti contemporaneamente: gestire gli errori durante la creazione dei socket. Crea un nuovo socket solo se un altro è chiuso.
Regola i limiti del sistema operativo.
Prova a fare il fork di alcuni (non molti) processi: questo potrebbe aiutare a utilizzare la CPU in modo un po 'più efficace.


@IgorGanapolsky Deve essere. Altrimenti sarei sorpreso. Ma ha certamente bisogno di sperimentazione.
George Sovetov,

0

Nel tuo caso, il threading probabilmente farà il trucco poiché probabilmente passerai la maggior parte del tempo in attesa di una risposta. Ci sono moduli utili come Queue nella libreria standard che potrebbero aiutare.

Ho fatto una cosa simile con il download parallelo di file prima ed è stato abbastanza buono per me, ma non era sulla scala di cui stai parlando.

Se la tua attività fosse più legata alla CPU, potresti voler esaminare il modulo multiprocessore , che ti permetterà di utilizzare più CPU / core / thread (più processi che non si bloccano a vicenda poiché il blocco è per processo)


L'unica cosa che vorrei menzionare è che la generazione di più processi può essere più costosa della generazione di più thread. Inoltre, non vi è alcun chiaro miglioramento delle prestazioni nell'invio di 100.000 richieste HTTP con più processi rispetto a più thread.
IgorGanapolsky,

0

Prendi in considerazione l'utilizzo di Windmill , anche se probabilmente Windmill non è in grado di fare così tanti thread.

Potresti farlo con uno script Python arrotolato a mano su 5 macchine, ognuna connessa in uscita usando le porte 40000-60000, aprendo 100.000 connessioni di porte.

Inoltre, potrebbe essere utile eseguire un test di esempio con un'app QA ben strutturata come OpenSTA per avere un'idea di quanto ogni server può gestire.

Inoltre, prova a guardare semplicemente usando Perl semplice con la classe LWP :: ConnCache. Probabilmente otterrai più prestazioni (più connessioni) in questo modo.


0

Questo contorto client Web asincrono va piuttosto veloce.

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

Ho scoperto che l'utilizzo del tornadopacchetto è il modo più rapido e semplice per raggiungere questo obiettivo:

from tornado import ioloop, httpclient, gen


def main(urls):
    """
    Asynchronously download the HTML contents of a list of URLs.
    :param urls: A list of URLs to download.
    :return: List of response objects, one for each URL.
    """

    @gen.coroutine
    def fetch_and_handle():
        httpclient.AsyncHTTPClient.configure(None, defaults=dict(user_agent='MyUserAgent'))
        http_client = httpclient.AsyncHTTPClient()
        waiter = gen.WaitIterator(*[http_client.fetch(url, raise_error=False, method='HEAD')
                                    for url in urls])
        results = []
        # Wait for the jobs to complete
        while not waiter.done():
            try:
                response = yield waiter.next()
            except httpclient.HTTPError as e:
                print(f'Non-200 HTTP response returned: {e}')
                continue
            except Exception as e:
                print(f'An unexpected error occurred querying: {e}')
                continue
            else:
                print(f'URL \'{response.request.url}\' has status code <{response.code}>')
                results.append(response)
        return results

    loop = ioloop.IOLoop.current()
    web_pages = loop.run_sync(fetch_and_handle)

    return web_pages

my_urls = ['url1.com', 'url2.com', 'url100000.com']
responses = main(my_urls)
print(responses[0])

-2

Il modo più semplice sarebbe utilizzare la libreria di threading integrata di Python. Non sono "reali" / thread del kernel Hanno problemi (come la serializzazione), ma sono abbastanza buoni. Vorresti un pool di code e thread. Un'opzione è qui , ma è banale scriverne una tua. Non è possibile parallelizzare tutte le 100.000 chiamate, ma è possibile attivarne 100 (o giù di lì) contemporaneamente.


7
I thread di Python sono abbastanza reali, al contrario di Ruby per esempio. Sotto il cofano sono implementati come thread del sistema operativo nativo, almeno su Unix / Linux e Windows. Forse ti riferisci al GIL, ma non rende i thread meno reali ...
Eli Bendersky

2
Eli ha ragione sui thread di Python, ma anche il punto di Pestilence che vorresti usare un pool di thread è corretto. L'ultima cosa che vorresti fare in questo caso è provare ad avviare un thread separato per ciascuna delle richieste da 100K contemporaneamente.
Adam Crossland,

1
Igor, non puoi pubblicare sensibilmente frammenti di codice nei commenti, ma puoi modificare la tua domanda e aggiungerli lì.
Adam Crossland,

Pestilenza: quante code e thread per coda consiglieresti per la mia soluzione?
IgorGanapolsky,

inoltre questo è un task I / O associato non associato alla CPU, il GIL influenza in gran parte i task associati alla CPU
PirateApp
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.