1. 程式人生 > 其它 >自動爬取網上免費代理實戰:儲存模組篇

自動爬取網上免費代理實戰:儲存模組篇

目錄

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):
        """
        redis連線物件
        """
        conn = ConnectionPool.from_url(url=self.redis_tcp_url())
        self.redis = StrictRedis(connection_pool=conn, decode_responses=True)
        self.logger = Logging()

    def redis_tcp_url(self):
        if REDIS_PASSWORD:
            return f'redis://:{str(REDIS_PASSWORD)}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}'
        else:
            return f'redis://@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}'

    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, MIN_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)

儲存的格式如下:

3.總結

通過使用Redis有序集合高效與方便地儲存每一個代理,既可以判斷代理不重複,還可以標識代理的可用情況。同時經過這一番學習,以前沒接觸過redis的我,這一回練習加深了對redis的具體理解與使用,除了有序集合,還有鍵操作、字串操作、列表操作、集合操作、雜湊集合,後續如有需求再記錄下來。

如有錯誤,望糾正。大家有更好的思路,歡迎下方留言。