Каков самый быстрый способ отправки 100 000 HTTP-запросов в Python?

Я открываю файл, содержащий 100 000 URL-адресов. Мне нужно отправить HTTP-запрос на каждый URL-адрес и распечатать код состояния. Я использую Python 2.6, и до сих пор смотрел на многие запутанные способы, которыми Python реализует потоки / параллелизм. Я даже посмотрел библиотеку согласования python, но не могу понять, как правильно писать эту программу. Кто-нибудь сталкивался с подобной проблемой? Я предполагаю, что обычно мне нужно знать, как выполнять тысячи задач на Python как можно быстрее – я полагаю, это означает «одновременно».

Спасибо, Игорь

13 Solutions collect form web for “Каков самый быстрый способ отправки 100 000 HTTP-запросов в Python?”

Раствор без сваривания:

 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) 

Это немного быстрее, чем скрученное решение, и использует меньше CPU.

Решение с использованием асинхронной сетевой библиотеки торнадо

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

Темы здесь абсолютно не являются ответом. Они будут обеспечивать узкие места процесса и ядра, а также ограничения пропускной способности, которые неприемлемы, если общая цель – «самый быстрый способ».

Немного twisted и его асинхронный HTTP клиент даст вам гораздо лучшие результаты.

Используйте grequests , это комбинация запросов + модуль Gevent.

GRequests позволяет использовать Запросы с Gevent для упрощения асинхронных запросов HTTP.

Использование прост:

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

Создайте набор неотправленных запросов:

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

Отправляйте их одновременно:

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

Хороший подход к решению этой проблемы – сначала написать код, необходимый для получения одного результата, а затем включить код потока для распараллеливания приложения.

В идеальном мире это означало бы одновременное начало 100 000 потоков, которые выводят их результаты в словарь или список для последующей обработки, но на практике вы ограничены в количестве параллельных запросов HTTP, которые вы можете опубликовать таким образом. Локально у вас есть ограничения в том, сколько сокетов вы можете открывать одновременно, сколько потоков выполнения вашего интерпретатора Python позволит. Удаленно, вы можете ограничить количество одновременных подключений, если все запросы связаны с одним сервером или многими. Эти ограничения, вероятно, потребуют, чтобы вы записывали сценарий таким образом, чтобы только опросить небольшую часть URL-адресов в любой момент времени (100, как упоминалось в другом плакате, вероятно, является приличным размером пула потоков, хотя вы можете обнаружить, что вы может успешно внедрить еще много).

Вы можете следовать этому шаблону проектирования, чтобы решить вышеуказанную проблему:

  1. Запустите поток, который запускает новые потоки запросов до тех пор, пока количество текущих выполняемых потоков (вы можете отслеживать их через threading.active_count () или путем нажатия объектов потока в структуру данных)> = максимальное количество одновременных запросов (скажем, 100) , затем спит в течение короткого таймаута. Этот поток должен завершиться, когда больше нет URL-адресов для обработки. Таким образом, поток будет продолжать просыпаться, запускать новые потоки и спать, пока вы не закончите.
  2. Попросите потоки запросов сохранить свои результаты в некоторой структуре данных для последующего поиска и вывода. Если структура, в которой вы сохраняете результаты, представляет собой list или dict в CPython, вы можете безопасно добавлять или вставлять уникальные элементы из ваших потоков без блокировок , но если вы пишете файл или требуете более сложных межпоточных данных, вы должны используйте блокировку взаимного исключения, чтобы защитить это состояние от коррупции .

Я бы предложил использовать модуль потоковой передачи. Вы можете использовать его для запуска и отслеживания текущих потоков. Поддержка потоков Python является голой, но описание вашей проблемы предполагает, что она полностью достаточна для ваших нужд.

Наконец, если вы хотите увидеть довольно простое приложение параллельного сетевого приложения, написанное на Python, проверьте ssh.py. Это небольшая библиотека, которая использует потоки Python для параллелизации многих SSH-соединений. Дизайн достаточно близок к вашим требованиям, и вы можете найти его хорошим ресурсом.

Решение:

 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 

Если вы хотите получить максимальную производительность, вам может потребоваться использовать асинхронный ввод-вывод, а не потоки. Накладные расходы, связанные с тысячами потоков ОС, являются нетривиальными, а переключение контекста в интерпретаторе Python добавляет еще больше к нему. Threading, безусловно, выполнит эту работу, но я подозреваю, что асинхронный маршрут обеспечит лучшую общую производительность.

В частности, я предлагаю асинхронный веб-клиент в библиотеке Twisted ( http://www.twistedmatrix.com ). У этого есть, по общему признанию, крутая кривая обучения, но он довольно прост в использовании, как только вы получите хорошую ручку в стиле айнхронного программирования Twisted.

A HowTo на асинхронном веб-клиентском API Twisted доступен по адресу:

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

С 2010 года ситуация изменилась с самого начала, и я не пробовал все остальные ответы, но я попробовал несколько, и я нашел, что это работает для меня лучше, используя python3.6.

Я смог получить около 150 уникальных доменов в секунду, работающих на AWS.

 import pandas as pd import concurrent.futures import requests import time out = [] CONNECTIONS = 100 TIMEOUT = 5 time1 = None time2 = None 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): url for url in urls} 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") if time1 == None: time1 = time.time() if len(out)/len(urls)>=1 and time2==None: time2 = time.time() print('Took {:.2f} s'.format((time2-time1))) print(str(pd.Series(out).value_counts())) 

Использование пула потоков является хорошим вариантом и сделает это довольно легко. К сожалению, у python нет стандартной библиотеки, которая упрощает пулы потоков. Но вот приличная библиотека, которая должна вас запустить: http://www.chrisarndt.de/projects/threadpool/

Пример кода с сайта:

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

Надеюсь это поможет.

Для вашего случая потоки, вероятно, сделают трюк, поскольку вы, вероятно, будете тратить больше времени на ожидание ответа. В стандартной библиотеке могут быть полезны модули, такие как Queue .

Я сделал аналогичную вещь с параллельной загрузкой файлов раньше, и это было достаточно для меня, но это было не в том масштабе, о котором вы говорите.

Если ваша задача была больше привязана к процессору, вы можете посмотреть на модуль многопроцессорности , который позволит вам использовать больше процессоров / ядер / потоков (больше процессов, которые не будут блокировать друг друга с момента блокировки для каждого процесса)

Подумайте о том, как использовать Windmill , хотя Windmill, вероятно, не может сделать это много потоков.

Вы можете сделать это с помощью ручного скрипта Python на 5 машинах, каждый из которых подключается исходящим, используя порты 40000-60000, открывая 100 000 портов.

Кроме того, это может помочь выполнить выборочный тест с помощью хорошо знакомого приложения QA, такого как OpenSTA , чтобы получить представление о том, насколько каждый сервер может работать.

Кроме того, попробуйте просто использовать простой Perl с классом LWP :: ConnCache. Вероятно, вы получите больше производительности (больше подключений).

Этот скрученный веб-клиент async работает довольно быстро.

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

Самый простой способ – использовать встроенную библиотеку потоков Python. Они не являются «реальными» / ядерными потоками, но достаточно хороши. Вам нужен пул очереди и потока. Один из вариантов здесь , но тривиально писать свои собственные. Вы не можете распараллелить все 100 000 звонков, но вы можете одновременно запустить 100 (или около того) из них.

  • Добавить изображение в Facebook с помощью Python
  • Разбор заголовка HTTP-запроса с помощью Python
  • Как я могу войти в facebook с помощью python (request / urllib3)?
  • Запросы Python извлекают файл из локального URL-адреса
  • Как указать запросы на python http put body?
  • Создание веб-интерфейса для скрипта, выполняющего 30 минут для выполнения
  • Как передавать данные POST в запросы Python?
  • Flask - ошибка POST 405 не допускается
  • Python - лучший язык программирования в мире.