前言
我们从代理商买了代理后,代理商提供的接口返回的代理其实可用率还是值得深思的。如果你有钱,买的是代理商自建的代理,那可用率很高,也就不需要使用代理池筛选了。如果像我这样的学生党,只能买得起测试级别的代理,这种代理一般是代理商扫描出来的,可用时间和可用率可想而知,这样就需要一个代理池筛选出有用的代理并提供接口给爬虫。
既然只是为了测试代理的可用性,那么asyncio+aiohttp再合适不过了(效率高,代码简单)。
思路
创建两个Redis集合,一个存储提取的代理,一个存储有效的代理,然后每隔一段时间请求一次接口得到若干个代理,这个一段时间要看你的代理商的限制和你爬虫的需求,将得到的代理放入到总代理池,并交给程序验证,将有效的代理存入有效的池中,当下一次提取数据的时候,我先验证得到的代理在不在总代理池中,如果在则不验证,不在的话再验证其有效性,另外,每隔一段时间,验证一下总代理的代理是否可用(针对网上扫描的不稳定代理),不验证有效的代理池的代理是否可用,这个交给爬虫程序去验证。当然,以上的思路只是针对不稳定代理的,如果是代理商自建的代理,需要做一些改动提高代理池的可用率。
小改进
本来想开发一个接口给爬虫,但想了想,接口也是要从数据库中取出来,我为什么不让爬虫直接从数据库中取呢,这样不是更节省时间,提高效率。
小思考
这里出现了一个小的问题,验证的网站是要选择爬虫爬取的网站(后面称目标网站),还是选择其他的网站呢,如果选择目标网站,那么可不可能爬虫和代理程序同时在使用一个代理,多次之后,造成代理的被封呢。如果选择其他网站,目标网站已经封了这个代理也说不定。最后,我还是选择了目标网站,因为买的是万人骑的代理,那么验证目标网站的可用性就显得很重要了。自建的优质代理则可以选择一些其他网站。
问题
代理的使用间隔。假如我们有效代理池中有100个代理,而爬虫程序应该开多少个协程比较合适呢?对于我买的这个代理,我试过过度消费代理,就是即使只有100个代理,但我依旧开1000个协程去爬取,这样会导致一个代理被使用多次后失效,然后在某个时间点,我的代理池是空的,爬虫就停止了。后面我才学会将协程的数量调整到代理数量*(1.5-2),因为我爬取的网站限制不是很严格,一秒请求两次是被允许的。于是爬虫和代理池才都能稳定运行。但成功率依旧不高,80%左右,有时甚至只有60%,但这样得到的数据居然没有过度消费代理来的多,我能怎么办,我也很无奈啊。
代码:
# -*- coding: utf-8 -*-
import asyncio
import aiohttp
import time
import json
import redis
import requests
class GetProxies:
def __init__(self):
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.61 Safari/537.36',
}
self.url = '' # 用于验证代理的URL
self.r = redis.Redis(decode_responses=True)
self.rset = 'sproxies' # redis集合, 存储已提取的代理
def start(self):
self.vset = 'vproxies' # redis集合, 存储有效的代理
n = 0 # 用于间隔标识,每十次检验一次sproxies中代理是否有效
while True:
n += 1
if n == 10:
print('--开始验证已获取代理--')
self.test_proxy()
n = 0
print('---开始获取代理---')
self.get_proxy()
print('---获取完成,代理池数量:(%d)---' % self.r.scard(self.vset))
time.sleep(5)
def test_proxy(self):
'''
取出sproxies中代理,检验其有效性
'''
proxy_list = self.r.smembers(self.rset)
self.run(proxy_list)
def get_proxy(self):
'''
这个函数有点多余,不过我喜欢这样写
'''
proxy_list = self.get_api()
if proxy_list:
self.r.sadd(self.rset, *proxy_list)
self.run(proxy_list)
def get_api(self):
'''
获取代理(http://127.0.0.1:12306),返回列表
'''
pass
async def get(self, proxy, session):
'''
协程函数,每个协程运行的函数
'''
try:
async with session.get(self.url, timeout=10, proxy=proxy) as resp:
if resp.status == 200:
self.r.sadd(self.vset, proxy)
# 这三个异常都是代理失效造成的
except (aiohttp.ClientError, aiohttp.client_exceptions.ClientConnectorError, asyncio.TimeoutError):
pass
async def main(self, proxy_list):
'''
所有协程
'''
tasks = []
async with asyncio.Semaphore(500):
session = aiohttp.ClientSession(headers=self.headers)
for proxy in proxy_list:
task = asyncio.ensure_future(self.get(proxy, session))
tasks.append(task)
await asyncio.wait(tasks)
await session.close()
def run(self, proxy_list):
'''
运行协程
'''
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(self.main(proxy_list))
loop.run_until_complete(asyncio.sleep(0))
loop.close()
if __name__ == '__main__':
g = GetProxies()
g.start()