之前(Python爬蟲之構(gòu)建代理池)我們已經(jīng)說過將代理數(shù)據(jù)使用Redis進(jìn)行存儲管理甸怕,我們需要一個數(shù)據(jù)庫模塊來和Redis進(jìn)行交互操作,管理代理池中的代理梢杭。如果你還沒有做好準(zhǔn)備工作秸滴,請先移步Python爬蟲之構(gòu)建代理池。
設(shè)計(jì)思路
我們的代理池中存儲著大量的代理吝羞,這些代理質(zhì)量性參差不齊,有的代理可能一開始可以用钧排,后來卻不可用了均澳。我們從代理池中獲取代理時,應(yīng)該優(yōu)先返回那些可用性高的代理找前,其次,對于長期不可用的代理项戴,應(yīng)該將其從代理池中清除槽惫。
所以我們可以給代理進(jìn)行評分辩撑,可用性越高的代理仿耽,評分越高,對于評分低于某一標(biāo)準(zhǔn)的代理進(jìn)行刪除君躺,我們可以這樣設(shè)計(jì):
- 代理的最高評分我們設(shè)置為100开缎,100代表穩(wěn)定可用的代理。
- 當(dāng)代理初次被加入代理池時谍珊,我們給的初始評分60。
- 如果代理在一次檢測中可用,立馬將代理分?jǐn)?shù)置于100侮邀。
- 如果代理在一次檢測中不可用,將代理的分?jǐn)?shù)減1铝宵,當(dāng)代理分?jǐn)?shù)小于50時华畏,將代理刪除。
Tip:這個分?jǐn)?shù)標(biāo)準(zhǔn)可以自己進(jìn)行設(shè)定侣夷。
開發(fā)
在MyProxyPool
項(xiàng)目中新建redisdb.py
:
import redis
from random import choice
import re
# ============= 分?jǐn)?shù)配置 =================
# 最大分?jǐn)?shù)
MAX_SCORE = 100
# 最小分?jǐn)?shù)
MIN_SCORE = 50
# 初始分?jǐn)?shù)
INIT_SCORE = 60
# ============= redis配置 =================
# 主機(jī)
REDIS_HOST = 'localhost'
# 端口
REDIS_PORT = 6379
# 密碼
REDIS_PASSWORD = None
# 鍵
REDIS_KEY = 'proxies'
class RedisCli:
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=INIT_SCORE):
'''
添加代理仑乌,設(shè)置默認(rèn)分?jǐn)?shù)
:param proxy: 代理
:param score: 分?jǐn)?shù)
:return: 添加結(jié)果
'''
# 正則匹配代理格式是否正確
if not re.match(r'\d+.\d+.\d+.\d+:\d+', proxy):
print('代理 %s 無效 ==> 丟棄' % proxy)
return
# 判斷代理池中是否已經(jīng)存在
if not self.db.zscore(REDIS_KEY, proxy):
# 添加代理
print('新增代理 %s ' % proxy)
return self.db.zadd(REDIS_KEY, score, proxy)
def random(self):
'''
隨機(jī)獲取有效代理
:return: 隨機(jī)代理
'''
# 嘗試獲取最高分?jǐn)?shù)的代理集合
result = self.db.zrangebyscore(REDIS_KEY,MAX_SCORE,MAX_SCORE)
if len(result):
# 如果有最高分?jǐn)?shù)的代理晰甚,隨機(jī)選擇返回
return choice(result)
else:
# 獲取分?jǐn)?shù)排名前一百的代理
result = self.db.zrevrange(REDIS_KEY, 0, 100)
if len(result):
# 如果有,隨機(jī)返回
return choice(result)
else:
return None
def decrease(self, proxy):
'''
將代理值減一分蓖捶,分?jǐn)?shù)小于最小值時扁远,移除代理俊鱼。
:param proxy: 代理
:return: 修改后的代理分?jǐn)?shù)
'''
score = self.db.zscore(REDIS_KEY, proxy)
if score and score > MIN_SCORE:
# 如果分?jǐn)?shù)大于最低分刻像,將分?jǐn)?shù)減一
print('代理 %s 當(dāng)前分?jǐn)?shù) %d ==> 減1 ==> %d' % (proxy, score, score - 1))
return self.db.zincrby(REDIS_KEY, proxy, -1)
else:
# 否則移除代理
print('代理 %s 當(dāng)前分?jǐn)?shù) %d ==> 移除' % (proxy, score))
return self.db.zrem(REDIS_KEY, proxy)
def exists(self, proxy):
'''
代理是否存在
:param proxy: 代理
:return: 是否存在
'''
return self.db.zscore(REDIS_KEY, proxy) is not None
def set_max(self, proxy):
'''
將代理設(shè)置為最大分?jǐn)?shù)
:param proxy: 代理
:return: 設(shè)置結(jié)果
'''
print('代理 %s 可用 ==> 設(shè)置為 %d' % (proxy, MAX_SCORE))
return self.db.zadd(REDIS_KEY, MAX_SCORE, proxy)
def count(self):
'''
獲取代理數(shù)量
:return: 數(shù)量
'''
return self.db.zcard(REDIS_KEY)
def all(self):
'''
獲取全部代理
:return: 全部代理
'''
return self.db.zrangebyscore(REDIS_KEY, MIN_SCORE, MAX_SCORE)
if __name__ == '__main__':
# 進(jìn)行一些測試
redis_cli = RedisCli()
proxy = '118.25.220.214:3128'
redis_cli.add(proxy)
redis_cli.set_max(proxy)
redis_cli.decrease(proxy)
print(redis_cli.exists(proxy))
print(redis_cli.count())
print(redis_cli.all())
redis_cli.add('140.227.60.114:3128')
redis_cli.add('213.128.7.72:53281')
redis_cli.add('37.187.149.129:1080')
redis_cli.set_max('140.227.60.114:3128')
print(redis_cli.random())
這里我們使用了Redis 有序集合(sorted set):
- Redis 有序集合和集合一樣也是string類型元素的集合,且不允許重復(fù)的成員绎速。
- 不同的是每個元素都會關(guān)聯(lián)一個double類型的分?jǐn)?shù)纹冤。redis正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序购公。
- 有序集合的成員是唯一的,但分?jǐn)?shù)(score)卻可以重復(fù)。
- 集合是通過哈希表實(shí)現(xiàn)的知残,所以添加比庄,刪除,查找的復(fù)雜度都是O(1)佳窑。 集合中最大的成員數(shù)為 232 - 1 (4294967295, 每個集合可存儲40多億個成員)。