Python:Flask + Redis 维护代理池
1.为什么需要维护代理池
我们知道很多网站都是由反爬虫的机制的,于是我们就需要对我们的 ip 进行伪装,也是因为这个原因,网上也有很多的免费代理 IP 可以使用,但是这些 ip 质量参差不齐,于是我们就需要对其进行进一步的过滤,所以我们需要自己维护一个自己的好用的代理池,这就是我们这一节的目的,我们使用的 Redis 就是用来存储我们的代理 ip 信息的,flask 主要为我们提供一个方便的调用接口
(1)多占抓取,异步检测
(2)定时筛选持续更新
(3)提供接口,易于获取
4.代码实现
(1)入口文件 run.py
(2)调度中心 scheduler.py
(3)代理ip获取
getter.py
crawler.py
定义一个元类来拦截类的创建,给类添加了一个__CrawlFunc__属性记录所有的爬虫方法名
__CrawlFuncCount__属性记录已经设置好的爬虫方法
关键技术解释:
虽然我在注释中大概把关键的点都说了一下,但是这个技术非常重要,于是我还想再写一下
(1)解决很多爬虫配合运行的问题
因为我们的获取代理 ip 的网站有很多,这样我们就需要些很多的爬虫,那么这些爬虫应该怎样被我们调度就成了一个比较重要的问题,我们最好的想法就是每次调用一个网站,每次从这个网站中返回一个代理 ip 存入数据库,那我们第一个想到的应该就是 用 yield 作为每个爬虫的返回值的形式,这样不仅能实现按照我们自定义的统一格式返回的目的,而且还能完美实现我们每次返回一个然后下一次还能接着继续返回的目的
除此之外,想要配合运行我们还需要一个统一的函数调用接口,这个的实现方法是使用的 callback 回调函数作为我们函数调用的参数,然后传入我们的函数名,并通过 eval() 去执行我们的函数
(2)解决动态获取方法名和方法个数问题
这个问题就比较神奇了,也是我们需要学习的重点,这里使用的是 元类 来劫持类的构建并且为其添加对应的属性的方法来解决这个问题,Python 中一切皆对象,元类简单的说就是创建类的对象,我们还是重点再看一下代码
解释
__new__是在__init__之前被调用的特殊方法,它用来创建对象并返回创建后的对象,各个参数说明如下:
# cls: 当前准备创建的类
# name: 类的名字
# bases: 类的父类集合
# attrs: 类的属性和方法,是一个字典。
attrs 可以获取到类的所有属性和方法,于是我们只要给我们想要的方法一个统一的命名规范就可以了,在这里的命名规范是方法名前都有 crawl_ 这个字符串,这样我们就能快速对其进行收集并且计数
解释:
这里用到的比较关键的技术是异步网络请求,因为我们的 requests 库是同步的,请求一个必须等到结果返回才能请求另一个,这不是我们想要的,于是异步网络请求模块 aiohttp 就出现了,这是在 python3.5 以后新添加的内置功能(本质使用的是 Python 的协程)
对于类似爬虫这种延时的IO操作,协程是个大利器,优点很多,他可以在一个阻塞发生时,挂起当前程序,跑去执行其他程序,把事件注册到循环中,实现多程序并发,据说超越了10k限制,不过我没有试验过极限。
现在讲一讲协程的简单的用法,当你爬一个网站,有100个网页,正常是请求一次,回来一次,这样效率很低,但协程可以一次发起100个请求(其实也是一个一个发),不同的是协程不会死等返回,而是发一个请求,挂起,再发一个再挂起,发起100个,挂起100个,然后同时等待100个返回,效率提升了100倍。可以理解为同时做100件事,相对于多线程,做到了由自己调度而不是交给CPU,程序流程可控,节约资源,效率极大提升。
具体的使用方法,我在上面代码中的注释部分已经写了,下面对关键步骤再简单梳理一下:
1.定义连接器并取消ssl安全验证
conn = aiohttp.TCPConnector(verify_ssl=False)
2.创建一个session对象
async with aiohttp.ClientSession(connector=conn) as session:
3.使用创建的 session 对象请求具体的网站
async with session.get(TEST_URL, proxy=real_proxy, timeout=15, allow_redirects=False) as response:
4.asyncio.get_event_loop方法创建一个事件循环
loop = asyncio.get_event_loop()
5.将多个任务封装到一起
tasks = [self.test_single_proxy(proxy) for proxy in test_proxies]
6.run_until_complete将协程注册到事件循环,并启动事件循环,多任务并发执行
loop.run_until_complete(asyncio.wait(tasks))
(5)对外接口 api.py
5.代理池使用