美文网首页Python
python爬虫010-IP代理池的维护——(1)存储模块

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

作者: DKider | 来源:发表于2019-04-05 20:15 被阅读18次

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

    为什么要用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')

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

    明天写获取模块。

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/hrukiqtx.html