清明时节雪纷纷,路上行人欲断魂。借问寝室和处在,室友遥指积雪痕。
为什么要用IP代理,我就不多说了。直接进入正题。
我们在使用爬虫时需要换代理时,总是希望能找到稳定快速的代理,但是网上大多数免费代理的很多都是不可用的,每次换都要先判断这个代理是否可用。为了省去这一麻烦的步骤,IP代理池就出现了。
我的目的是自己维护一个的代理池,他定时获取新的代理,定时的去检测代理的可用性,并赋予权值、分数,提供api接口,我们需要的时候直接请求api就行了。
分为四大模块:
- 存储模块
- 获取模块
- 检测模块
- 接口模块
其中存储模块是中枢,连接着其他三个模块。
获取模块----------->存储模块<--------------->检测模块
|
|
|
|
V
接口模块
大概是这么个结构。
今天先说存储模块 :
这里我用的Redis数据库的有序集合来存储代理,因为集合具有唯一性等特点,可以去重,而且集合有序,可以方便查找。
直接上代码:中间遇到了不少坑,下面说:
import redis
from random import choice
MAX_SCORE = 100
MIN_SCORE = 0
INITIAL_SCORE = 10
REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_PASSWORD = None
REDIS_KEY = 'proxies'
class PoolEmptyError(Exception):
def __init__(self, error_info='IP代理池为空,无法提供有效代理'):
# super().__init__(self)
self.error_info = error_info
def __str__(self):
return self.error_info
class RedisClient:
"""定义一个Redis服务器
连接本地Redis数据库,并提供相关方法"""
def __init__(self, host=REDIS_HOST, port=REDIS_PORT,
password=REDIS_PASSWORD):
"""连接接数据库
:param host: Redis地址
:param port: Redis地址
:param password: Redis密码"""
self.db = redis.StrictRedis(host=host, port=port, password=password,
decode_responses=True)
def add(self, proxy, score=INITIAL_SCORE):
"""添加新的IP代理
:param proxy: 代理
:param score: 分数
:return : 添加结果"""
if not self.db.zscore(REDIS_KEY, proxy):
return self.db.zadd(REDIS_KEY, {proxy: score})
def random(self):
"""随机返回一个代理
如果有100分的代理,随机返回一个;
如果没有100分的,则按照分数排名获取分数最高的
如果都没有则返回异常
:return: 随机代理"""
result = self.db.zrangebyscore(REDIS_KEY, MAX_SCORE, MAX_SCORE)
if len(result):
return choice(result)
else:
result = self.db.zrevrange(REDIS_KEY, 0, 100)
if len(result):
return choice(result)
else:
raise PoolEmptyError
def decrease(self, proxy):
"""将检测出不可用的代理的分数减一分,如果分数小于最小值,则从代理池中删除
:param proxy: 代理地址及端口
:return: 修改后的代理分数"""
score = self.db.zscore(REDIS_KEY, proxy)
if score and score > MIN_SCORE:
print('代理', proxy, '当前分数', score, '减1')
return self.db.zincrby(REDIS_KEY, -1, proxy)
else:
print('代理', proxy, '当前分数', score, '删除')
return self.db.zrem(REDIS_KEY, proxy)
def exists(self, proxy):
"""判断IP代理是否存在
:param proxy: 代理ip
:return: 是否存在->bool"""
return self.db.zscore(REDIS_KEY, proxy) is not None
def max(self, proxy):
"""将代理的分数设置为MAX_SCORE
:param proxy: 代理ip
:return: 设置结果"""
print('代理', proxy, '可用,设置为',MAX_SCORE)
return self.db.zadd(REDIS_KEY, {proxy: MAX_SCORE})
def count(self):
"""返回数据库中的代理数量
:return: 数量->int"""
return self.db.zcard(REDIS_KEY)
def all(self):
"""返回数据库中的所有代理
:return: 全部代理"""
return self.db.zrangebyscore(REDIS_KEY, MIN_SCORE, MAX_SCORE)
# p=RedisClient()
# proxy = '11.1.1.1:8080'
# p.add(proxy, 10)
# p.decrease(proxy)
# print(p.exists(proxy))
# p.max(proxy)
# print(p.count())
# print(p.all())
"""
获取模块----------->存储模块<--------------->检测模块
|
|
|
|
V
接口模块
"""
遇到的坑:
- 对自定义异常类不熟悉,定义了一个PoolEmptyError,而且也不清楚什么时候应该报异常,而不是过滤错误。
- redis库在操作数据库时有序结合的一个方法:zadd(),这个函数,书上和网上给的例子都是:zadd(redis_key, 'bob', 10)或者zadd(redis_key, 10, 'bob'),等类似这样的调用方法,但是这样会报错。自己试一下就知道了,我忘记怎么说的了。正确的用法:zadd(redis_key, {'bob': 100})
- 判断是否为空时要用:is | is not ,而不是==
- 筛选函数zincrby() 的调用,网上书上是:zincrby(redis_key, 'bob',-1) 类似这样的调用,本意是让bob的score减一,但是他参数写反了,应该是:zincrby(redis_key, -1,'bob')
最后,我用下面的注释写了简单的单元测试,保证每一个方法都可以正常调用。
明天写获取模块。
网友评论