Что самый быстрый путь состоит в том, чтобы отправить 100 000 Запросов HTTP в Python?

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

252
задан simhumileco 6 February 2019 в 10:29
поделиться

9 ответов

Решение без витой схемы:

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)

Это решение немного быстрее, чем решение с витой схемой, и использует меньше ЦП.

186
ответ дан 23 November 2019 в 02:53
поделиться

Использование пула потоков - хороший вариант, который упростит эту задачу. К сожалению, у 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()

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

1
ответ дан 23 November 2019 в 02:53
поделиться

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

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

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

0
ответ дан 23 November 2019 в 02:53
поделиться

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

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

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

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

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

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

8
ответ дан 23 November 2019 в 02:53
поделиться

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

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

Практическое руководство по API асинхронного веб-клиента Twisted доступно по адресу:

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

7
ответ дан 23 November 2019 в 02:53
поделиться

Решение:

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
ответ дан 23 November 2019 в 02:53
поделиться

Рассмотрите возможность использования Windmill , хотя Windmill, вероятно, не может выполнять такое количество потоков.

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

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

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

0
ответ дан 23 November 2019 в 02:53
поделиться

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

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

38
ответ дан 23 November 2019 в 02:53
поделиться

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

-2
ответ дан 23 November 2019 в 02:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: