当前位置:首页 > 通信资讯 > 正文

如何用最快的方式发送 10 万个 http 请求"}(如何用最快的方式发送 10 万个 http 请求"})

假如有一个文件,里面有 10 万个 url,需要对每个 url 发送 http 请求,并打印请求结果的状态码,如何编写代码尽可能快的完成这些任务呢?

Python 并发编程有很多方法,多线程的标准库 threading,concurrency,协程 asyncio,当然还有 grequests 这种异步库,每一个都可以实现上述需求,下面一一用代码实现一下,本文的代码可以直接运行,给你以后的并发编程作为参考:

队列+多线程

定义一个大小为 400 的队列,然后开启 200 个线程,每个线程都是不断的从队列中获取 url 并访问。

主线程读取文件中的 url 放入队列中,然后等待队列中所有的元素都被接收和处理完毕。代码如下:

  1. fromthreadingimportThread
  2. importsys
  3. fromqueueimportQueue
  4. importrequests
  5. concurrent=200
  6. defdoWork():
  7. whileTrue:
  8. url=q.get()
  9. status,url=getStatus(url)
  10. doSomethingWithResult(status,url)
  11. q.task_done()
  12. defgetStatus(ourl):
  13. try:
  14. res=requests.get(ourl)
  15. returnres.status_code,ourl
  16. except:
  17. return"error",ourl
  18. defdoSomethingWithResult(status,url):
  19. print(status,url)
  20. q=Queue(concurrent*2)
  21. foriinrange(concurrent):
  22. t=Thread(target=doWork)
  23. t.daemon=True
  24. t.start()
  25. try:
  26. forurlinopen("urllist.txt"):
  27. q.put(url.strip())
  28. q.join()
  29. exceptKeyboardInterrupt:
  30. sys.exit(1)

运行结果如下:

如何用最快的方式发送 10 万个 http 请求"}(如何用最快的方式发送 10 万个 http 请求"})

有没有 get 到新技能?

线程池

如果你使用线程池,推荐使用更高级的 concurrent.futures 库:

  1. importconcurrent.futures
  2. importrequests
  3. out=[]
  4. CONNECTIONS=100
  5. TIMEOUT=5
  6. urls=[]
  7. withopen("urllist.txt")asreader:
  8. forurlinreader:
  9. urls.append(url.strip())
  10. defload_url(url,timeout):
  11. ans=requests.get(url,timeout=timeout)
  12. returnans.status_code
  13. withconcurrent.futures.ThreadPoolExecutor(max_workers=CONNECTIONS)asexecutor:
  14. future_to_url=(executor.submit(load_url,url,TIMEOUT)forurlinurls)
  15. forfutureinconcurrent.futures.as_completed(future_to_url):
  16. try:
  17. data=future.result()
  18. exceptExceptionasexc:
  19. data=str(type(exc))
  20. finally:
  21. out.append(data)
  22. print(data)

协程 + aiohttp

协程也是并发非常常用的工具了:

  1. importasyncio
  2. fromaiohttpimportClientSession,ClientConnectorError
  3. asyncdeffetch_html(url:str,session:ClientSession,**kwargs)->tuple:
  4. try:
  5. resp=awaitsession.request(method="GET",url=url,**kwargs)
  6. exceptClientConnectorError:
  7. return(url,404)
  8. return(url,resp.status)
  9. asyncdefmake_requests(urls:set,**kwargs)->None:
  10. asyncwithClientSession()assession:
  11. tasks=[]
  12. forurlinurls:
  13. tasks.append(
  14. fetch_html(url=url,session=session,**kwargs)
  15. )
  16. results=awaitasyncio.gather(*tasks)
  17. forresultinresults:
  18. print(f'{result[1]}-{str(result[0])}')
  19. if__name__=="__main__":
  20. importsys
  21. assertsys.version_info>=(3,7),"ScriptrequiresPython3.7+."
  22. withopen("urllist.txt")asinfile:
  23. urls=set(map(str.strip,infile))
  24. asyncio.run(make_requests(urls=urls))

grequests[1]

这是个第三方库,目前有 3.8K 个星,就是 Requests + Gevent[2],让异步 http 请求变得更加简单。Gevent 的本质还是协程。

使用前:

  1. pipinstallgrequests

使用起来那是相当的简单:

  1. importgrequests
  2. urls=[]
  3. withopen("urllist.txt")asreader:
  4. forurlinreader:
  5. urls.append(url.strip())
  6. rs=(grequests.get(u)foruinurls)
  7. forresultingrequests.map(rs):
  8. print(result.status_code,result.url)

注意 grequests.map(rs) 是并发执行的。运行结果如下:

如何用最快的方式发送 10 万个 http 请求"}(如何用最快的方式发送 10 万个 http 请求"})

也可以加入异常处理:

  1. >>>defexception_handler(request,exception):
  2. ...print("Requestfailed")
  3. >>>reqs=[
  4. ...grequests.get('http://httpbin.org/delay/1',timeout=0.001),
  5. ...grequests.get('http://fakedomain/'),
  6. ...grequests.get('http://httpbin.org/status/500')]
  7. >>>grequests.map(reqs,exception_handler=exception_handler)
  8. Requestfailed
  9. Requestfailed
  10. [None,None,<Response[500]>]

最后的话

今天分享了并发 http 请求的几种实现方式,有人说异步(协程)性能比多线程好,其实要分场景看的,没有一种方法适用所有的场景,笔者就曾做过一个实验,也是请求 url,当并发数量超过 500 时,协程明显变慢。

原文链接:https://mp.weixin.qq.com/s/tacsqShnGNFHO3DpuxOMPA

如果您对该产品感兴趣,请填写办理(客服微信:xiaoxiongyidong)

为您推荐:

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。