Python

python爬虫010-IP代理池的维护——(1)存储模块

2019-04-05  本文已影响18人  DKider

清明时节雪纷纷,路上行人欲断魂。借问寝室和处在,室友遥指积雪痕。

为什么要用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
                   接口模块
"""
遇到的坑:

最后,我用下面的注释写了简单的单元测试,保证每一个方法都可以正常调用。

明天写获取模块。

上一篇 下一篇

猜你喜欢

热点阅读