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

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

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

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

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

import asyncio

from aiohttp import ClientSession
from ip_pool.MongoDB import mongodb
import requests

class 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,足见异步编程的高效性。
最后,免费的代理是真的不好用。。。

你可能感兴趣的:(Python异步编程与aiohttp检测代理池)