Python异步编程与aiohttp检测代理池

来源:互联网 发布:三星备份数据怎样恢复 编辑:程序博客网 时间:2024/05/18 00:37

之前为了检测代理的可用性学习了一下aiohttp,网上有关aiohttp的使用很少,所以写篇博客记录下来。
首先,为什么要使用异步编程,先看两张图。
这里写图片描述
这里写图片描述

很显然,我们不希望cpu在这些极慢的IO操作上阻塞,我们希望在IO操作期间,CPU能继续执行其他的任务,Python中的异步asyncio能很好实现这一点。
什么是协程?可以参考下面这篇文章
最新Python异步编程详解
aiohttp 简易使用教程
aiohttp基本使用可以参考aiohttp官网

在HTTP事物当中,当我们发送了一个GET请求后,我们的客户端与服务器建立TCP链接会有时延,链接建立后我们发送请求主体服务器接受后处理也会有时间延迟,将响应报文传送回来也会产生延迟,在同步模型中,程序在传输过程中会阻塞,等待传输完成,这将耗费大量的时间。
所以我们希望任何时候,当我们的客户端程序请求发送后,不因等待响应而阻塞,让程序能执行其他的事情,当响应到底后,以某种方法通知程序去处理,或者程序以查询的方式在空闲的时候回去查询这个响应是否到达。

Python的异步编程和异步网络框架可以帮我们做到这一点。
先上代码

import asynciofrom aiohttp import ClientSessionfrom ip_pool.MongoDB import mongodbimport requestsclass ProxyValidator(object):    def __init__(self):        self.url='https://www.zhihu.com/'        self.headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 Safari/537.36'}        self.timeout=3.05        self.coro_count=500        self.proxyqueue=None        self.useableProxy=set()    async def _validator(self, proxy_queue):     #测试代理的协程,受事件循环调度,传入公有待检测代理异步队列        if isinstance(proxy_queue,asyncio.Queue):            async with ClientSession() as session:                while not self.proxyqueue.empty():                    try:                        proxy = await proxy_queue.get()                        proxystr = 'http://' + proxy['http']                        async with session.get(self.url, headers=self.headers,                                               proxy=proxystr, timeout=self.timeout) as resp:                            if resp.status == 200:                                # text=await resp.text()                                # print(text)                                # if '知乎 ' in text:                                # print(resp.headers)                                self.useableProxy.add(proxy['http'])                                print(proxystr)                    except asyncio.TimeoutError:                        pass                    except Exception as e:                        pass    async def _get_proxyqueue(self):        mongo=mongodb()        proxy_iterator = mongo.find_from_mongodb({}, {'http': 1, '_id': 0}).skip(26000)        proxyqueue=asyncio.Queue()        for proxy in proxy_iterator:            await proxyqueue.put(proxy)        self.proxyqueue=proxyqueue        return proxyqueue    async def test_reportor(self):        total=self.proxyqueue.qsize()        time.clock()        while not self.proxyqueue.empty():            total_lastsec=self.proxyqueue.qsize()            await asyncio.sleep(1)            validated_num=total_lastsec-self.proxyqueue.qsize()            print('%d validated  %d item/sec; useable proxy: %d  ;%d item to  validate' % ((total-self.proxyqueue.qsize()),validated_num,len(self.useableProxy),self.proxyqueue.qsize()))        print('cost %f' % time.clock())    async def start(self):        proxy_queue= await self._get_proxyqueue()        to_validate=[self._validator(proxy_queue) for _ in range(self.coro_count)]        to_validate.append(self.test_reportor())        await asyncio.wait(to_validate)    def proxy_validator_run(self):        # loop = asyncio.new_event_loop()        # asyncio.set_event_loop(loop)        loop=asyncio.get_event_loop()        try:            loop.run_until_complete(self.start())        except Exception as e:            print(e)if __name__ == '__main__':    validator=ProxyValidator()    validator.proxy_validator_run()

这段异步的Http程序是如何运行的,我们一个方法一个方法来看。

    def proxy_validator_run(self):        # loop = asyncio.new_event_loop()        # asyncio.set_event_loop(loop)        loop=asyncio.get_event_loop()        try:            loop.run_until_complete(self.start())        except Exception as e:            print(e)

这是这个类里面唯一没用使用协程的方法,在这个方法中,我们初始化了一个事件循环loop,并用Loop的run_until_complete()方法启动了协程self.start(),run_until_complete()方法接受一个协程或者像asyncio.wait(to_validate)这样经过asyncio.wait包装产生的期物,这里真的很难说清’期物’这个概念,具体的可以翻阅流畅的python这本书相关章节。

    async def start(self):        proxy_queue= await self._get_proxyqueue()        to_validate=[self._validator(proxy_queue) for _ in range(self.coro_count)]        to_validate.append(self.test_reportor())        await asyncio.wait(to_validate)

start被async语句包装成了协程,代码中就必须出现await与之对应,任何时候,你在等待什么,记住用await。这段代码中,将待检测代理队列的一个引用传递给proxy_queue,然后根据self.coro_count初始化了一个tasks对象列表to_validate,由于self._validator是一个协程,所以这个to_validate实际上一堆协程对象,通过asyncio.wait(to_validate)方法,我们等待to_validate完成。
也就是所的self._validator方法完成。


async def _validator(self, proxy_queue): #测试代理的协程,受事件循环调度,传入公有待检测代理异步队列
if isinstance(proxy_queue,asyncio.Queue):
async with ClientSession() as session:
while not self.proxyqueue.empty():
try:
proxy = await proxy_queue.get()
proxystr = 'http://' + proxy['http']
async with session.get(self.url, headers=self.headers,
proxy=proxystr, timeout=self.timeout) as resp:
if resp.status == 200:
# text=await resp.text()
# print(text)
# if '知乎 ' in text:
# print(resp.headers)
self.useableProxy.add(proxy['http'])
print(proxystr)

self._validator在这个方法中,async with session.get(self.url, headers=self.headers, proxy=proxystr, timeout=self.timeout) as resp:
还有 text=await resp.text() 都是靠aiohttp在底层实现了的异步代码,程序执行到这里,不会像同步模式一样等待响应,而是交出程序的控制权,控制权返回到事件循环loop后继续运行其他协程。这样我们便能在很短的时间内处理大量的http事务。
值得一提的是,win系统下并发数受制于文件句柄数512的最大限制,这段代码我启动了500个检测代理的协程交给事件循环loop调度运行,每秒检测数百个代理,也没能跑满单个cpu,足见异步编程的高效性。
最后,免费的代理是真的不好用。。。

原创粉丝点击