URL이 100,000 개인 파일을 열었습니다. 각 URL에 HTTP 요청을 보내고 상태 코드를 인쇄해야합니다. 저는 Python 2.6을 사용하고 있으며 지금까지 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()
답변
2010 년 이후이 게시물이 게시되어 다른 답변을 모두 시도하지는 않았지만 몇 가지를 시도했지만 python3.6을 사용하여 가장 잘 작동하는 것으로 나타났습니다.
AWS에서 실행되는 초당 약 150 개의 고유 도메인을 가져올 수있었습니다.
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())
답변
스레드는 절대 대답이 아닙니다. 전체 목표가 “가장 빠른 방법”인 경우 허용되지 않는 처리량 제한뿐만 아니라 프로세스 및 커널 병목 현상을 모두 제공합니다.
약간 twisted
의 비동기 HTTP
클라이언트는 훨씬 나은 결과를 제공합니다.
답변
나는 이것이 오래된 질문이라는 것을 알고 있지만 Python 3.7에서는 asyncio
and를 사용 하여이 작업을 수행 할 수 있습니다 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))
이에 대한 자세한 내용은 여기 에서 예를 참조 하십시오 .
답변
grequests를 사용하십시오 . 그것은 request + 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]>]
답변
이 문제를 해결하는 좋은 방법은 먼저 하나의 결과를 얻는 데 필요한 코드를 작성한 다음 스레딩 코드를 통합하여 응용 프로그램을 병렬화하는 것입니다.
완벽한 세계에서 이것은 단순히 나중에 처리하기 위해 사전 또는 목록으로 결과를 출력하는 10 만 개의 스레드를 동시에 시작한다는 것을 의미하지만 실제로이 방식으로 발행 할 수있는 병렬 HTTP 요청 수는 제한되어 있습니다. 로컬에서는 동시에 열 수있는 소켓 수, 파이썬 인터프리터가 허용하는 실행 스레드 수에 제한이 있습니다. 원격으로 모든 요청이 한 서버 또는 여러 서버에 대한 경우 동시 연결 수가 제한 될 수 있습니다. 이러한 제한 사항으로 인해 한 번에 적은 양의 URL 만 폴링하는 방식으로 스크립트를 작성해야 할 수도 있습니다. 다른 포스터에서 언급했듯이 100은 적절한 스레드 풀 크기 일 수 있습니다. 더 많은 것을 성공적으로 배포 할 수 있습니다).
이 디자인 패턴에 따라 위의 문제를 해결할 수 있습니다.
- 현재 실행중인 스레드 수 (threading.active_count ()를 통해 또는 스레드 객체를 데이터 구조로 푸시하여 스레드를 추적 할 수 있음)가 최대 동시 요청 수 (예 : 100)가 될 때까지 새 요청 스레드를 시작하는 스레드를 시작합니다. 그런 다음 짧은 시간 종료 동안 휴면 상태가됩니다. 처리 할 URL이 더 이상 없으면이 스레드가 종료되어야합니다. 따라서 스레드는 계속 깨어나고 새 스레드를 시작하며 완료 될 때까지 대기합니다.
- 요청 스레드가 결과를 나중에 검색 및 출력 할 수 있도록 일부 데이터 구조에 저장하도록하십시오. 결과를 저장하는 구조 가 CPython
list
또는dict
CPython 인 경우 잠금없이 스레드에서 고유 항목을 안전하게 추가하거나 삽입 할 수 있지만 파일에 쓰거나보다 복잡한 크로스 스레드 데이터 상호 작용 이 필요한 경우에는 이 상태를 손상으로부터 보호하기위한 상호 배제 잠금 .
스레딩 모듈 을 사용하는 것이 좋습니다 . 이를 사용하여 실행중인 스레드를 시작하고 추적 할 수 있습니다. 파이썬의 스레딩 지원은 거의 없지만 문제에 대한 설명은 귀하의 요구에 충분하다고 제안합니다.
마지막으로, 파이썬으로 작성된 병렬 네트워크 애플리케이션의 간단한 애플리케이션을 보려면 ssh.py를 확인하십시오 . Python 스레딩을 사용하여 많은 SSH 연결을 병렬화하는 작은 라이브러리입니다. 디자인은 요구 사항에 충분히 근접하여 좋은 리소스가 될 수 있습니다.