自动爬取网上免费代理实战:存储模块篇
1.存储模块说明
- 当我们从网上爬取下来代理时,负责存储工作就主要由存储模块来完成。
- 存储代理的方式可能有很多,既然保证代理不重复,且要有一个标识来说明代理的可用情况,还要实时处理每个代理。所以这里选用Reids的有序集合(sorted set),Redis有序集合和集合一样不允许存在重复,不同的是每个元素都会关联一个double类型的分数,从而通过分数来为集合中的成员进行从从小到大的排序。
- 有序集合提供了迭代元素、获取指定分数、元素范围查询、计算成员排名等功能。
2.实现思路
- 实用Redis的有序集合,可保证元素不重复,这里的元素就是我们的代理,比如8.8.8.8:6666,由ip+端口组成的形式就是一个代理,也是集合中的一个元素。还有就是每个元素对应有一个分数,分数可重复的,它是一个double类型。集合中的每一个元素根据相对应的元素分数排序,数值小的靠前,数值大的靠后,这样就可以实现集合元素排序了。
- 如何保证代理的可用性呢?可参考崔神写的项目代理池,里面提供了一种比较实用的思路,利用分数去判断代理的可用情况,假设一个代理1.2.4.8:6666,起初它默认的分数是5分,当我们测试后发现它可用,则把它的分数设置为最高分10分,最高分也代表代理最可用,反之就是最低分0分,代表代理最不可用。然后通过定时去检测每个代理可用情况,每次去测试一个代理后,如果该代理可用,把它的分数设置为10分,当该代理不可用,把它的分数减1分,直至减至分数等于0,当代理分数为0时,就把代理移除。如果一个代理一开始就不可用,这里默认设定5分,它就只有5次机会去尝试。最后,如果一个代理可用设置成了最高分,这里说的10分作为最高分也可以再修改高一点,因为测试一个代理可用时,如果给予测试的机会过少,就可能很容易把一个曾经可用的代理丢弃掉,因为代理不可用的原因很可能是网络繁忙或者他人也在用请求太过频繁,短时间不可用并不无能说明它不是一个有效的代理。另外,代理分数默认设置为5分,可适当的减少不必要的开销。
- 如何保证每个代理都能被调用呢?使用随机化获取,从代理池中随机获取最高分的代理。
2.1 代码实现:
代码环境:Python 3.9.1, Redis:3.5.3
第三方依赖包:redis
主模块
#proxypool/storage/redisclient.py
from redis import StrictRedis, ConnectionPool
from typing import Dict, List
from random import choice
from proxypool.untils.parse import bytes_convert_string
from proxypool.untils.loggings import Logging
from proxypool.setting import (REDIS_HOST,
REDIS_PORT,
REDIS_DB,
DEFAULT_SCORE,
MAX_SCORE,
MIN_SCORE,
REDIS_PASSWORD)
class redisClient(object):
"""
Redis操作-增删改查
"""
def __init__(self):
self.redis = StrictRedis(host=REDIS_HOST,
password=REDIS_PASSWORD,
port=REDIS_PORT,
db=REDIS_DB,
decode_responses=True)
self.logger = Logging()
def add(self, redis_key, proxy, score=DEFAULT_SCORE) -> int:
"""
添加代理
args:
- redis_key: 传入集合name
- proxy: 传入以字典格式的IP代理,比如{'8.8.8.8:1080': 10}
:return:
"""
if isinstance(score, int) and proxy.strip():
return self.redis.zadd(redis_key, {proxy: score})
def decrease(self, redis_key, proxy):
"""
对代理的分数做增减操作
"""
self.redis.zincrby(redis_key, -1, proxy)
current_score = self.redis.zscore(redis_key, proxy)
self.logger.info(f"{proxy} current score {current_score}, decrease -1")
if current_score <= 0:
self.logger.info(f"{proxy} current score {current_score}, removing proxy")
self.redis.zrem(redis_key, proxy)
def max(self, redis_key, proxy, score=MAX_SCORE):
"""
对可用代理设置最高分
"""
self.logger.info(f'Modifying the ({proxy}) score to ({score})')
return self.redis.zadd(redis_key, {proxy: score})
def get_count(self, redis_key) -> int:
"""
获取集合name所有的个数
:param redis_key: 集合name
:return:
"""
return self.redis.zcard(redis_key)
def get_all(self, redis_key, min_score=0, max_score=100, start=None, num=None, withscores=False) -> List:
"""
根据指定分数范围获取集合name代理
:param redis_key: 集合name
:param min_score: 指定最小分数
:param max_score: 指定最大分数
:return:
"""
if start is None:
start = 0
elif num is None:
num = self.get_count(redis_key)
return self.redis.zrangebyscore(redis_key, min_score, max_score, start, num, withscores)
def exists(self, redis_key, proxy) -> bool:
"""
判断代理是否存在,不存在返回True
:param redis_key: 集合name
:param proxy: value
:return:
"""
return self.redis.zscore(redis_key, proxy) == None
def random_proxy(self, redis_key):
"""
随机获取代理,优先获取获取最高分数的,其次再随机获取最低~最高分数之间的所有元素
:param redis_key:
:return:
"""
high_score_proxies = self.redis.zrangebyscore(redis_key, MAX_SCORE, MAX_SCORE)
if len(high_score_proxies):
return bytes_convert_string(choice(high_score_proxies))
low_to_high_score_proxies = self.redis.zrangebyscore(redis_key, MIN_SCORE, MAX_SCORE)
if len(low_to_high_score_proxies):
return bytes_convert_string(choice(low_to_high_score_proxies))
self.logger.exception("no proxy")
return None
def batch(self, redis_key, cursor, count):
"""
迭代有序集合元素
"""
cursor, proxies = self.redis.zscan(redis_key, cursor=cursor, count=count)
return cursor, proxies
if __name__ == '__main__':
proxy = '1.2.4.8:1080'
client = redisClient()
client.add('proxy', proxy, score=100)
存储的格式如下:
完整代码:https://github.com/rosaany/proxypool
3.总结
通过使用Redis有序集合高效与方便地存储每一个代理,既可以判断代理不重复,还可以标识代理的可用情况。同时经过这一番学习,以前没接触过redis的我,这一回练习加深了对redis的具体理解与使用,除了有序集合,还有键操作、字符串操作、列表操作、集合操作、散列集合,后续如有需求再记录下来。
如有错误,望纠正。大家有更好的思路,欢迎下方留言。