Який найшвидший спосіб надіслати 100 000 HTTP-запитів у Python?


287

Я відкриваю файл із 100 000 URL-адресами. Мені потрібно надіслати HTTP-запит до кожної URL-адреси та роздрукувати код статусу. Я використовую Python 2.6, і поки що розглянув безліч заплутаних способів, якими Python реалізує потоки / паралельність. Я навіть переглянув бібліотеку узгодження python , але не можу зрозуміти, як правильно написати цю програму. Хтось стикався з подібною проблемою? Я думаю, що, як правило, мені потрібно знати, як виконувати тисячі завдань на Python якомога швидше - я вважаю, що це означає "одночасно".


47
Переконайтеся, що ви робите лише запит HEAD (щоб ви не завантажували весь документ). Дивіться: stackoverflow.com/questions/107405/…
Тарнай Калман

5
Відмінна точка, Кальмі. Якщо все, що Ігор хоче, це статус запиту, ці 100K запити пройдуть набагато, набагато, набагато швидше. Набагато швидше.
Адам Кросленд

1
Для цього вам не потрібні нитки; найефективніший спосіб, ймовірно, використовувати асинхронну бібліотеку типу Twisted.
jemfinch


4
@ TarnayKálmán можна requests.getі requests.head(тобто запит на сторінку проти головного запиту) повертати різні коди статусу, тому це не найкраща порада
AlexG

Відповіді:


200

Рішення без скручування:

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)

Це трохи швидше, ніж скручене рішення та використовує менше процесора.


10
@Kalmi, чому ти встановив Чергу concurrent*2?
Марсель Вілсон

8
Не забудьте закрити з'єднання conn.close() . Відкриття занадто багато http-з'єднань може зупинити ваш скрипт у якийсь момент і з'їсть пам'ять.
Аамір Аднан

4
@hyh, Queueмодуль було перейменовано на queuePython 3. Це код Python 2.
Тарнай Калман

3
На скільки швидше ви можете піти, якщо хочете кожен раз спілкуватися з сервером SAME, зберігаючи з'єднання? Чи можна це зробити навіть через нитки або з одним стійким з'єднанням на потік?
mdurant

2
@mptevsion, якщо ви використовуєте CPython, ви можете (наприклад) просто замінити "статус друку, URL" на "my_global_list.append ((status, url))". (Більшість операцій над) списками неявно захищені потоками в CPython (та деяких інших реалізаціях python) завдяки GIL, тому це безпечно зробити.
Тарнай Калман

54

Рішення за допомогою асинхронної мережевої бібліотеки торнадо

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
Цей код використовує незахисну мережу вводу-виводу і не має обмежень. Він може масштабуватися до десятків тисяч відкритих з'єднань. Він працюватиме в одній нитці, але буде набагато швидшим, ніж будь-яке рішення для різьблення.
Замовлення, що

1
Чи можете ви пояснити, що тут відбувається з глобальною змінною i? Якась перевірка помилок?
LittleBobbyTables

4
Це лічильник для визначення, коли потрібно вийти з `` ioloop '' - значить, коли ти закінчиш.
Майкл Дорнер

1
@AndrewScottEvans передбачає, що ви використовуєте python 2.7 та проксі
Dejell

5
@Guy Avraham Удачі, щоб отримати допомогу у вашому плані ddos.
Вальтер

51

Речі змінилися досить сильно з 2010 року, коли це було опубліковано, і я не пробував всіх інших відповідей, але я спробував декілька, і я виявив, що це працює найкраще для мене за допомогою python3.6.

Мені вдалося отримати близько 150 унікальних доменів за секунду, що працює на 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
Я запитую лише тому, що я не знаю, але чи міг би цей ф'ючерс замінити на асинхронізацію / очікування?
TankorSmash

1
Це могло б, але я виявив, що вище може працювати краще. Ви можете використовувати aiohttp, але він не є частиною стандартної ліб і змінюється досить сильно. Це працює, але я просто не знайшов, що він також працює. Я отримую більш високі показники помилок, коли я його використовую, і за все життя я не можу змусити його працювати так само, як і одночасні ф'ючерси, хоча теоретично здається, що він повинен працювати краще, див.: Stackoverflow.com/questions/45800857/… якщо ви змусите його добре працювати, напишіть свою відповідь, щоб я міг перевірити її.
Глен Томпсон

1
Це нітпік, але я думаю, що набагато чистіше поставити time1 = time.time()вгорі петлю for і time2 = time.time()відразу після циклу for.
Метт М.

Я перевірив ваш фрагмент, він якось виконується двічі. Чи я щось роблю не так? Або це покликано бігати двічі? Якщо це останній випадок, чи можете ви також допомогти мені зрозуміти, як це спрацьовує двічі?
Ронні

1
Він не повинен працювати два рази. Не впевнений, чому ви це бачите.
Глен Томпсон

40

Нитки тут абсолютно не відповідь. Вони забезпечуватимуть як вузькі місця технологічних процесів, так і ядра, а також обмеження пропускної здатності, неприйнятні, якщо загальна мета - "найшвидший шлях".

Трохи twistedта його асинхронний HTTPклієнт дасть вам набагато кращі результати.


ironfroggy: Я схиляюся до ваших настроїв. Я спробував реалізувати своє рішення за допомогою ниток та черг (для автоматичних файлів), але чи можете ви уявити, скільки часу потрібно, щоб заповнити чергу 100 000 речей ?? Я все ще граю з різними варіантами та пропозиціями всіх в цій темі, і, можливо, Twisted буде хорошим рішенням.
ІгорГанапольський

2
Ви можете уникнути заповнення черги на 100 тис. Речей. Просто обробіть елементи один за одним із свого введення, а потім запустіть нитку для обробки запиту, відповідного кожному елементу. (Як я описую нижче, використовуйте ланцюжок запуску, щоб запустити потоки запитів HTTP, коли кількість ваших потоків нижче деякого порогового значення. Зробіть те, щоб результати записували в URL-адресу зіставлення дік для відповіді або додавали кортежі до списку.)
Ерік Гарнізон

ironfroggy: Також мені цікаво, які вузькі місця ви знайшли за допомогою потоків Python? І як потоки Python взаємодіють з ядром ОС?
Ерік Гарнізон

Обов’язково встановіть реактор epoll; інакше ви будете використовувати select / опитування, і це буде дуже повільно. Крім того, якщо ви дійсно намагаєтесь відкрити 100 000 з'єднань одночасно (якщо припустимо, що програма написана саме так, а URL-адреси є на різних серверах), вам потрібно буде налаштувати вашу ОС, щоб у вас не вичерпалося дескрипторів файлів, ефемерних портів тощо (можливо, простіше просто переконатися, що у вас немає більше, скажімо, 10000 видатних з'єднань відразу).
Марк Ноттінгем

erikg: ти рекомендував чудову ідею. Однак найкращого результату, який мені вдалося досягти за допомогою 200 ниток, було приблизно. 6 хвилин. Я впевнений, що є способи досягти цього за менший час ... Марк N: якщо Twisted - це шлях, який я вирішу йти, тоді реактор еполіси, безумовно, корисний. Однак, якщо мій сценарій буде запускатися з декількох машин, чи не це вимагатиме встановлення Twisted на кожній машині? Я не знаю, чи зможу я переконати свого шефа піти цим маршрутом ...
ІгорГанапольський

21

Я знаю, що це старе питання, але в Python 3.7 ви можете це зробити за допомогою asyncioі 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))

Ви можете прочитати більше про це та побачити приклад тут .


Це схоже на C # async / await та Котлінські розробки?
ІгорГанапольський

@IgorGanapolsky, так, це дуже схоже на C # async / wait. Я не знайомий з Котлін Короутінами.
Marius

@sandyp, я не впевнений, що це працює, але якщо ви хочете спробувати, вам доведеться використовувати UnixConnector для aiohttp. Детальніше читайте тут: docs.aiohttp.org/en/stable/client_reference.html#connectors .
Marius

Спасибі @ MariusStănescu. Саме цим я і користувався.
пісок

+1 для показу asyncio.gather (* завдання). ось один такий фрагмент, який я використав: urls= [fetch(construct_fetch_url(u),idx) for idx, u in enumerate(some_URI_list)] results = await asyncio.gather(*urls)
Ашвіні Кумар

19

Скористайтеся вітальними повідомленнями , це комбінація запитів + ​​модуль 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]>]

7
gevent тепер підтримує python 3
Benjamin Toueg

14
grequests не є частиною звичайних запитів і, здається, є значною мірою незареєстрованою
Том

8

Хороший підхід до вирішення цієї проблеми полягає в тому, щоб спочатку написати код, необхідний для отримання одного результату, а потім включити нитку коду для паралелізації програми.

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

Ви можете дотримуватися цієї схеми дизайну, щоб вирішити вищевказану проблему:

  1. Запустіть потік, який запускає нові потоки запитів, доки кількість поточних потоків, що виконуються в даний момент (ви можете відстежувати їх за допомогою threading.active_count () або шляхом натискання об'єктів потоку в структуру даних)>> ваша максимальна кількість одночасних запитів (скажімо, 100) , потім спить ненадовго. Цей потік повинен закінчуватися, коли більше URL-адрес обробляти не буде. Таким чином, нитка буде постійно прокидатися, запускати нові нитки і спати до тих пір, поки ваш не закінчиться.
  2. Попросіть потоки запитів зберігати свої результати в деякій структурі даних для подальшого пошуку та виведення. Якщо структура, в якій ви зберігаєте результати, - це listабо dictв CPython, ви можете сміливо додавати або вставляти унікальні елементи зі своїх потоків без замків , але якщо ви записуєте у файл або потребуєте більш складної взаємодії даних міжпотокових даних, ви повинні використовувати блокування взаємного виключення для захисту цієї держави від корупції .

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

І, нарешті, якщо ви хочете , щоб побачити досить просто додаток паралельного мережевого додатки , написаного в Python, перевірити ssh.py . Це невелика бібліотека, яка використовує потоки Python для паралелізації багатьох з'єднань SSH. Дизайн досить близький до ваших вимог, щоб ви могли вважати його хорошим ресурсом.


1
erikg: чи було б викиданням черги у ваше рівняння розумним (для блокування взаємного виключення)? Я підозрюю, що GIL Python не спрямований на гру з тисячами ниток.
ІгорГанапольський

Чому вам потрібно заблокувати взаємне виключення, щоб запобігти генеруванню занадто багато ниток? Я підозрюю, що я неправильно розумію цей термін. Ви можете відстежувати запущені нитки в черзі потоків, видаляючи їх, коли вони завершуються, і додаючи більше до обмеження потоку. Але в такому простому випадку, як розглянутий, ви також можете просто спостерігати кількість активних потоків у поточному процесі Python, чекати, поки воно не впаде нижче порогового значення, і запускати більше потоків до порогу, як описано. Я думаю, ви могли б вважати це неявним блокуванням, але явних блоків не потрібно afaik.
Ерік Гарнісон

erikg: чи не поділяють декілька потоків стан? На сторінці 305 у книзі О'Рейлі "Python для Unix та Linux системного адміністрування" вказується: "... використання потоків без черг робить її складнішою, ніж багато людей реально реалізувати. Це набагато краща ідея завжди використовувати чергу. модуль, якщо вам здається, що вам потрібно використовувати нитки. Чому? Тому що модуль черги також зменшує необхідність явного захисту даних з мютексами, оскільки сама черга вже захищена внутрішньо мютексним файлом. " Знову вітаю вашу точку зору з цього приводу.
ІгорГанапольський

Ігор: Ви абсолютно праві, що вам слід користуватися замком. Я відредагував публікацію, щоб відобразити це. Враховуючи це, практичний досвід роботи з python говорить про те, що вам не потрібно блокувати структури даних, які ви атомно модифікуєте зі своїх потоків, як-от list.append або додавання хеш-ключа. Причиною, я вважаю, є GIL, який забезпечує такі операції, як list.append із ступенем атомності. Зараз я провожу тест, щоб перевірити це (використовуйте 10k потоків для додавання номерів 0-9999 до списку, перевірте, чи працювали всі додатки). Після майже 100 повторень тест не провалився.
Ерік Гарнізон

Ігор: Мені задають ще одне запитання на цю тему: stackoverflow.com/questions/2740435/…
Ерік Гарнісон

7

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

Зокрема, я б запропонував веб-клієнт async у бібліотеці Twisted ( http://www.twistedmatrix.com ). Він має, безумовно, круту криву навчання, але він досить простий у використанні, як тільки ви отримаєте хорошу ручку в стилі асинхронного програмування Twisted.

API асинхронного веб-клієнта Twisted доступний за адресою:

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


Ракіс: Я зараз переглядаю асинхронний і не блокуючий введення / вивід. Мені потрібно це навчитися краще, перш ніж це здійснити. Один коментар, який я хотів би зробити у своєму дописі, полягає в тому, що неможливо (принаймні, під моїм дистрибутивом Linux) породити "тисячі потоків ОС". Існує максимальна кількість потоків, за допомогою яких Python дозволить нереститися до розриву програми. І в моєму випадку (на CentOS 5) максимальна кількість ниток - 303.
ІгорГанапольський

Це добре знати. Я ніколи не пробував нересту одразу більше ніж жменю в Python, але я би очікував, що зможу створити більше, ніж це, перш ніж його бомбили.
Ракіс

6

Рішення:

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

Час випробування:

[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
Використання Twisted як нитки ігнорування більшості переваг, які ви можете отримати від цього. Натомість ви повинні використовувати клієнт async HTTP.
Жан-Поль Кальдероне

1

Використання пулу ниток - хороший варіант, і це зробить це досить просто. На жаль, у 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()

Сподіваюсь, це допомагає.


Я пропоную вказати q_size для ThreadPool так: ThreadPool (poolsize, q_size = 1000), щоб у вас не було 100000 об'єктів WorkRequest в пам'яті. "Якщо q_size> 0, розмір черги запитів на роботу обмежений, і пул потоків блокується, коли черга заповнена, і вона намагається помістити в неї більше запитів на роботу (див. putRequestМетод), якщо ви також не використовуєте додатне timeoutзначення для putRequest."
Тарнай Калман

Поки я намагаюся реалізувати рішення з нитковим пулом - як пропонується. Однак я не розумію список параметрів у функції makeRequests. Що таке деякий_звоновий, список_файлів, зворотний дзвінок? Можливо, якби я побачив справжній фрагмент коду, який би допоміг. Я здивований, що автор цієї бібліотеки не розмістив жодного прикладу.
ІгорГанапольський

some_callable - це ваша функція, в якій виконується вся ваша робота (підключення до http-сервера). list_of_args - це аргументи, які будуть передані в some_callabe. зворотний виклик - це функція, яка буде викликана, коли робоча нитка виконана. Це займає два аргументи, об'єкт працівника (не потрібно по-справжньому стосуватися себе) та результати, які отримав працівник.
Кевін Вісконія

1

Створіть epollоб'єкт,
відкрийте багато клієнтських сокетів TCP,
налаштуйте їх буфери відправлення, щоб бути трохи більше, ніж заголовок запиту,
надішліть заголовок запиту - він повинен бути негайним, просто помістити в буфер, зареєструвати сокет в epollоб’єкті,
зробити .pollна предмет epoll,
прочитати спочатку 3 байтів з кожного сокета з .poll,
запишіть їх, щоб sys.stdoutдалі\n (не змивайте), закрийте клієнтський сокет.

Обмежте кількість розеток одночасно відкритих - обробляйте помилки, коли створюються сокети. Створіть нову розетку, лише якщо інша закрита.
Налаштування лімітів ОС.
Спробуйте роздрібнити декілька (не багато) процесів: це може допомогти використовувати процесор трохи ефективніше.


@IgorGanapolsky Повинен бути. Я був би здивований інакше. Але це, безумовно, потребує експериментів.
Георгій Совєтов

0

У вашому випадку, нарізання різьби, ймовірно, зробить трюк, оскільки ви, ймовірно, будете витрачати більшість часу на очікування відповіді. У стандартній бібліотеці є корисні модулі на зразок черги, які можуть допомогти.

Я робив подібну річ з паралельним завантаженням файлів і раніше, і це було досить добре для мене, але це було не в масштабі, про який ви говорите.

Якщо ваше завдання було більше пов'язане з процесором, ви можете подивитися модуль багатопроцесорної обробки , який дозволить вам використовувати більше процесорів / ядер / потоків (більше процесів, які не блокують один одного, оскільки блокування відбувається за один процес)


Єдине, що я хотів би зазначити, це те, що нерестування декількох процесів може бути дорожчим, ніж нерестування декількох потоків. Крім того, немає чіткого підвищення продуктивності при надсиланні 100 000 HTTP-запитів з декількома процесами проти кількох потоків.
ІгорГанапольський

0

Подумайте про використання вітряної млини , хоча вітряк, мабуть, не може зробити це багатьма нитками.

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

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

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


0

Цей закручений веб-клієнт 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)

0

Я виявив, що використання tornadoпакету - це найшвидший і найпростіший спосіб досягти цього:

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

Найпростішим способом було б користуватися вбудованою бібліотекою різьблення Python. Вони не є "справжніми" / потоками ядра. У них є проблеми (як серіалізація), але вони досить хороші. Ви хочете пула черг та ниток. Один варіант є тут , але тривіально написати власний. Ви не можете паралельно виконати всі 100 000 дзвінків, але ви можете одночасно відмовитись від 100 (або близько) з них.


7
Нитки Python цілком реальні, на відміну, наприклад, від Ruby. Під кришкою вони реалізовані як рідні потоки ОС, принаймні, на Unix / Linux та Windows. Можливо, ви посилаєтесь на GIL, але це не робить теми менш реальними ...
Елі Бендерський,

2
Елі має рацію щодо ниток Python, але точка Pestilence, що ви хочете використовувати пул потоків, також є правильною. Останнє, що ви хочете зробити в цьому випадку, це спробувати запустити окремий потік для кожного з 100K запитів одночасно.
Адам Кросленд

1
Ігоре, ти не можеш розумно розмістити фрагменти коду в коментарях, але ти можеш відредагувати своє запитання та додати їх туди.
Адам Кросленд

Морність: скільки черг та ниток за чергою ви б рекомендували для мого рішення?
ІгорГанапольський

плюс це завдання, пов'язане з
входом / виводом,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.