搭建ip代理池

目录

  • 一、背景
    • 免费代理
    • 付费代理
  • 二、架构
  • 三、搭建
    • 1、redis 组件
      • db.py 完整代码
    • 2、下载器
      • getter.py 完整代码
    • 3、调度器
      • 3.1 校验器
      • 3.2 添加器
      • 3.3 调度器
        • scheduler.py 完整代码
    • 4、settings.py
    • 5、搭建flask可视化
      • api.py
      • run.py
  • 四、整体效果
  • 五、待完成

github项目地址:

https://github.com/wenwenc9/proxypool.git

一、背景

免费代理

网上有很多的代理池教学与文章,但是基本上都是通过爬取众多免费代理网站进行处理,或者讲的并不清楚

  • 免费代理绝大部分不可以用,也就玩玩而已
  • 对于真正公司项目而言,肯定不会采用这种方法的
  • 无法对ip进行真正管控

付费代理

那么付费代理了?如果使用过付费代理的人,应该知道付费代理,都有个ip存活时间
本文以芝麻代理作为搭建 http://www.zhimahttp.com/index/
在这里插入图片描述

  • ip存活时间,一个5~25分钟存活率为0.04芝麻币,即4分钱
  • 复用性,每次爬虫采集都会用到ip ,不可能使用一次就用一次四分钱吧
    (公司有钱当我没有说…)
  • 即时性,ip一但被封禁对ip进行标记,不再采用此ip
  • 多可用,多个爬虫项目分别A网站,B网站,C网站,C网站可以用同时使用这一个ip
  • 针对性,A网站使用此IP的使用被网站封禁,那么这个IP应当进行标记 A 不再使用,但是B,C任然可以使用
  • 使用次数,针对此条IP,被A,B,C三个采集调用共计多少次,这能助于我们对于程序参数调整,对代理IP的消耗情况进行统计、提供方法让爬虫程序通过简单的配合后能统计出代理IP的数据产出率、代理可用率进行统计,即可视化开发

但是付费代理也有缺点:

  • 如果大量的请求此接口,需要进行等待。

项目结构

  • server
    – config.py 配置文件。
    – ip 客户端请求后获取的客户端IP,文本保存。
    – main.py
    – Flask主程序,提供两个接口,一个是接收客户端请求,然后将IP保存,另外一个是获取当前保存的IP。

  • client
    – crontab定时任务命令示例。
    – pppoe.sh 拨号脚本,主要是实现重新拨号的几个命令。
    – request.sh
    – 请求服务器的脚本,主要是实现拨号后请求服务器的操作。
    – reqeust.conf 配置文件。

二、架构

本文以付费代理搭建
搭建ip代理池_第1张图片

核心模块组成:调度器、添加器、校验器、下载器,redis组件,flask API接口

  • 调度器
    全局管控,相当于CPU,管控添加器、校验器

  • 下载器
    从IP代理服务商,提取代理IP,http/https类型

  • 篮子
    存放校验的ip

  • 添加器
    调用下载器,添加IP到篮子
    篮子里面假如达到100个IP,启动校验器

  • 校验器
    校验篮子里面IP是否过期,如果没有过期,调用redis组件,进行存储
    循环校验校验redis数据库中IP是否过期,如果有,调用redis组件,进行删除

  • redis组件
    封装了增删改查功能

  • flask API接口
    提供网页说明文档,让人快速了解接口与使用方法

三、搭建

1、redis 组件

负责功能 :

  • 定期检查
  • 保持顺序(保证先校验的IP与后校验的IP区别)

redis特性即为什么选择redis:

  • list数据类型提供双向链路,栈,队列功能

  • list可以实现队列功能,先入后出,因此ip经过校验,存放到list,那么后面校验的为最新的校验成功的ip
    因此调用pop()方法弹出尾部最新的可用代理ip,就可以提高代理可用性

  • 同样的配合get()方法头部获取,经过校验存入的旧的代理ip从头部取出来

  • put(),将新校验的代理ip,存入list尾部

创建 Redis_Client(object)

将来这个类,是能够封装调用的,连接redis,host等参数应该不能固定
因此需要配置到配置文件,新建 settings.py

集合分别存储http,https类型的ip

# ---------------------- redis 配置 -----------------------
HOST = 'localhost'
PORT = 6380
PASSWORD = ''

PROXY_NAME = 'proxies' # 数据库名称
HTTP_PROXY_NAME = 'http_proxies'  # HTPP IP存储
HTTPS_PROXY_NAME = 'https_proxies'  # HTTPS IP存储

写完毕后,db.pysettings.py调用参数就可以了,新建 db.py

  • 设置私有,避免别人想拿就拿,万一清空数据空
  • if else,判断有无密码。两种方式
import redis
from settings import HOST, PASSWORD, PORT, PROXY_NAME, HTTP_PROXY_NAME, HTTPS_PROXY_NAME
class Reids_Client(object):
    def __init__(self, host=HOST, port=PORT, password=PASSWORD):
        if password:
            self.__conn = redis.Redis(host=host, port=port, password=password)
        else:
            self.__conn = redis.Redis(host=host, port=port)

Reids_Client类中实现 put()方法,向尾部添加一个代理

    def put(self, proxy, types):
        """
        向代理池尾部添加一个代理
        :param proxy: 代理ip
        :param types: 代理类型
        :return: 
        """
 
        # self.__conn.rpush(PROXY_NAME, proxy)
        if types == 'http':
            self.__conn.rpush(HTTP_PROXY_NAME, proxy)
        if types == 'https':
            self.__conn.rpush(HTTPS_PROXY_NAME, proxy)

Reids_Client类实现pop()方法,获取一个代理

  • redis出来的数据都是二进制数据,所以需要将其变成字符串
  • redisd的存储所有数据都是bytes类型
    def pop(self, types):
        """
        获取一个代理
        redisd的存储所有数据都是bytes类型-
        :param types: 代理类型
        :return: 可用的最新代理
        """

        # return self.__conn.rpop(PROXY_NAME).decode('utf-8')
        if types == 'http':
            return self.__conn.rpop(HTTP_PROXY_NAME).decode('utf-8')
        if types == 'https':
            return self.__conn.rpop(HTTPS_PROXY_NAME).decode('utf-8')

Reids_Client类实现get()方法,get()应该获取多个代理ip校验

    def get(self, count=1, types=None):
        """
        获取count个代理,同时将这些数据删除
        :param count: 默认数量为1
        :param types: 代理类型
        :return: 
        """

        if types == 'http':
            proxies = self.__conn.lrange(HTTP_PROXY_NAME, 0, count - 1)
            self.__conn.ltrim(HTTP_PROXY_NAME, count, -1)
            return proxies
        if types == 'https':
            proxies = self.__conn.lrange(HTTPS_PROXY_NAME, 0, count - 1)
            self.__conn.ltrim(HTTPS_PROXY_NAME, count, -1)
            return proxies

Reids_Client类实现queue_len()统计代理池的长度

  • 针对添加器,什么是开始添加,什么时候结束添加,应该查看代理池有无数据,那么查看代理池的长度
    def queue_len(self, types=None):
        """
        针对添加器,什么是开始添加,什么时候结束添加,应该查看代理池有无数据,那么查看代理池的长度
        :param types: 代理类型
        :return: 
        """
        # return self.__conn.llen(PROXY_NAME)
        if types == None:
            http_parameters = (self.__conn.llen(HTTP_PROXY_NAME), HTTP_PROXY_NAME,'http')
            https_parameters = (self.__conn.llen(HTTPS_PROXY_NAME), HTTPS_PROXY_NAME,'https')
            return http_parameters, https_parameters
        elif types == 'http':
            return self.__conn.llen(HTTP_PROXY_NAME)
        elif types == 'https':
            return self.__conn.llen(HTTPS_PROXY_NAME)

Reids_Client类 实现flush()方法

  • flushdb() 清空数据库
    def flush(self):
        self.__conn.flushdb()  # 清空代理池

db.py 完整代码

# -*- coding: utf-8 -*-
# @Time    : 2020-12-28 10:17
# @Author  : XuGuangJun
# @FileName: db.py
# @Software: PyCharm
import redis
from settings import HOST, PASSWORD, PORT, PROXY_NAME, HTTP_PROXY_NAME, HTTPS_PROXY_NAME

class Reids_Client(object):
    def __init__(self, host=HOST, port=PORT, password=PASSWORD):
        if password:
            self.__conn = redis.Redis(host=host, port=port, password=password)
        else:
            self.__conn = redis.Redis(host=host, port=port)

    def put(self, proxy, types):
        """
        向代理池尾部添加一个代理
        :param proxy: 代理ip
        :param types: 代理类型
        :return:
        """

        # self.__conn.rpush(PROXY_NAME, proxy)
        if types == 'http':
            self.__conn.rpush(HTTP_PROXY_NAME, proxy)
        if types == 'https':
            self.__conn.rpush(HTTPS_PROXY_NAME, proxy)

    def lput(self, proxy, types):
        '''
        向代理池头部部添加一个使用过的代理
        :param proxy:
        :return:
        '''
        # self.__conn.lpush(PROXY_NAME, proxy)
        if types == 'http':
            self.__conn.lpush(HTTP_PROXY_NAME, proxy)
        if types == 'https':
            self.__conn.lpush(HTTPS_PROXY_NAME, proxy)

    def pop(self, types):
        """
        获取一个代理
        redisd的存储所有数据都是bytes类型-
        :param types: 代理类型
        :return: 可用的最新代理
        """

        # return self.__conn.rpop(PROXY_NAME).decode('utf-8')
        if types == 'http':
            return self.__conn.rpop(HTTP_PROXY_NAME).decode('utf-8')
        if types == 'https':
            return self.__conn.rpop(HTTPS_PROXY_NAME).decode('utf-8')

    def get(self, count=1, types=None):
        """
        获取count个代理,同时将这些数据删除
        :param count: 默认数量为1
        :param types: 代理类型
        :return:
        """

        if types == 'http':
            proxies = self.__conn.lrange(HTTP_PROXY_NAME, 0, count - 1)
            self.__conn.ltrim(HTTP_PROXY_NAME, count, -1)
            return proxies
        if types == 'https':
            proxies = self.__conn.lrange(HTTPS_PROXY_NAME, 0, count - 1)
            self.__conn.ltrim(HTTPS_PROXY_NAME, count, -1)
            return proxies

    def queue_len(self, types=None):
        """
        针对添加器,什么是开始添加,什么时候结束添加,应该查看代理池有无数据,那么查看代理池的长度
        :param types: 代理类型
        :return:
        """
        # return self.__conn.llen(PROXY_NAME)
        if types == None:
            http_parameters = (self.__conn.llen(HTTP_PROXY_NAME), HTTP_PROXY_NAME,'http')
            https_parameters = (self.__conn.llen(HTTPS_PROXY_NAME), HTTPS_PROXY_NAME,'https')
            return http_parameters, https_parameters
        elif types == 'http':
            return self.__conn.llen(HTTP_PROXY_NAME)
        elif types == 'https':
            return self.__conn.llen(HTTPS_PROXY_NAME)

    def flush(self):
        self.__conn.flushdb()  # 清空代理池

2、下载器

新建 getter.py
新建 IpProxyMeta(type) 继承 type,即python的所有类的祖宗这样可以达到自定义元类方法,如果谁继承了这个自定义类,那么就调用__Protocol_Func__,这个属性,就能打印出此函数里面的所有函数名称
新建 IpProxyGetter(metaclass=IpProxyMeta) 类,并在其内定义三个函数,分别从付费代理接口获取http,和https接口获取代理IP,以及调用http还是https函数的函数

  • protocol_http 负责从付费代理商提供的API接口获得http类型IP
  • protocol_https 负责从付费代理商提供的API接口获得https类型IP,返回json,包含了 IP、过期时间、等字段
  • get_raw_proxies 负责代用protocol_http 还是 protocol_https ,并且返回代理批量代理IP(放入篮子的材料)

经过protocol_http处理后返回的ip为json(原本API接口返回的字段很多,可以自己查看,现在只保留了想要的,和添加自己想要的)

ip,过期时间,类型,使用次数
{"ip": '', "expire_time":'', "types": "https","use_num": '0'}
{"ip": '', "expire_time":'', "types": "http","use_num": '0'}

来到 settings.py 设置HTTP_URLHTTPS_URL 地址

HTTP_URL = 'http://webapi.http.zhimacangku.com/getip?num=20&type=2&pro=0&city=0&yys=0&port=1&time=1&ts=1&ys=0&cs=0&lb=1&sb=0&pb=45&mr=1®ions='
HTTPS_URL = 'http://webapi.http.zhimacangku.com/getip?num=20&type=2&pro=0&city=0&yys=0&port=11&time=1&ts=1&ys=0&cs=0&lb=1&sb=0&pb=45&mr=1®ions='

这个芝麻代理API接口来源,http和https分别执行下面参数两次
一定要勾选过期时间,因为要校验IP的存活时间,达到复用性

搭建ip代理池_第2张图片
点击后鼠标向下滑动下,这就是API接口
搭建ip代理池_第3张图片
复制完毕,打开,如果需要加入白名单,来到主页加入白名单即可
搭建ip代理池_第4张图片

getter.py 需要导入HTTP_URLHTTPS_URL两个参数

关于这个元类执行效果:
可能会说,python当中不是自带有__dir__能够打印出这个类中所有方法不是吗,当然没错
但是过于复杂,包含了所有的执行方法,并不能过滤掉我们不想要的
搭建ip代理池_第5张图片
那么使用继承元类效果如下
搭建ip代理池_第6张图片

getter.py 完整代码

# -*- coding: utf-8 -*-
# @Time    : 2020-12-28 10:24
# @Author  : XuGuangJun
# @FileName: getter.py
# @Software: PyCharm
import requests, re
from settings import HTTP_URL, HTTPS_URL
# from proxypool.settings import HTTP_URL, HTTPS_URL
import time


class IpProxyMeta(type):
    """
    继承python所有类的祖宗,即自定义元类
    """
    def __new__(cls, name, bases, attrs):
        # protocol 协议 指定为http还是https、scoks5
        attrs['__Protocol_Func__'] = []
        for k, v in attrs.items():
            if 'protocol_' in k:
                attrs['__Protocol_Func__'].append(k)
        return type.__new__(cls, name, bases, attrs)


class IpProxyGetter(metaclass=IpProxyMeta):
    """
    从IP代理服务商,提取代理IP
    http
    https
    socks5
    隧道代理
    """

    def get_raw_proxies(self, callfunc):
        """ # TODO 使用异步协程来处理, 这里并未实现真正意义上阈值处理
        通过给指定名字来调用对应方法,获取某种协议代理如callfunc = protocol_http
        获得的为http协议的IP
        :param callfunc:
        :return:
        """
        proxies = []
        for proxy in eval(f'self.{callfunc}()'):
            proxies.append(proxy)
        return proxies  # 这里有点不好,必须等所有的proxy完毕,才返回,也可以通过生成器 yield 改装一下 断点

    def protocol_http(self):
        """
          获取 http 类型代理
        """
        time.sleep(2) 
        if HTTP_URL != None:
            proxies = []
            response = requests.get(
                url=HTTP_URL
            )
            json = response.json()['data']
            # 获取时间
            for i in json:
                ip = i['ip'] + ":" + str(i['port'])
                res = {"ip": "{}".format(ip), "expire_time": "{}".format(i['expire_time']), "types": "http",
                       "use_num": '0'}
                proxies.append(res)
                # yield res
            return proxies

    def protocol_https(self):
        """
          获取 https 类型代理
        """
        time.sleep(2)
        if HTTPS_URL != None:
            proxies = []
            response = requests.get(
                url=HTTPS_URL
            )
            json = response.json()['data']
            # print(json)
            # 获取时间
            for i in json:
                ip = i['ip'] + ":" + str(i['port'])
                res = {"ip": "{}".format(ip), "expire_time": "{}".format(i['expire_time']), "types": "https",
                       "use_num": '0'}
                # proxies.append(res)
                yield res
            # return proxies
        return None

if __name__ == '__main__':
    f = IpProxyGetter()
    print(f.__dir__())
    # print(f.__Protocol_Func__)
    # print(IpProxyGetter().protocol_http())

3、调度器

调度器职责就是调度校验器、添加器

首先应该明白什么是调度器(设计结构)
搭建ip代理池_第7张图片

调取器就是,包含了控制开关,添加器,校验器功能,那么就需要逐一开发其中的功能

  • 校验器
  • 添加器
  • 开关 (开关功能尤为重要 )【分为校验器开关②、添加器开关①】

3.1 校验器

负责功能:

  • 校验ip 【从redis中取出一片校验,下载器交付的IP进行校验】,总的说从篮子中取出IP校验
  • 存储ip

创建类 VaildityTester(object)

  • 在类中定义好篮子 set_raw_proxies ,用于盛放从redis中取出的IP或者添加器调度下载器获得的IP
    def __init__(self):
        # 设为私有,不允许外部调用,存放待校验ip
        self.__raw_proxies = []

    def set_raw_proxies(self, proxiies):
        """
        set_raw_proxies 集原料代理
        放入未校验ip
        :param proxies: 未校验ip 从getter 中传入待校验ip
        :return:
        """
        self.__raw_proxies = proxiies
        # 数据库连接-->创建的方式会影响程序性能--》用的时候创建
        self.__conn = Reids_Client()  # redis数据库对象
  • 定义 get_ProxyPararmeter 方法
    其意义在于,判断是从reids中取出的ip(bytes类型),还是从代理商接口获得的ip(str类型),bytes需要进行转换。
    另外,实现校验IP是否过期功能,p过期不能刚好到那个点,应当提前,所以设置 IP_LIVE 参数,应当提前删除快过期IP
    def get_ProxyPararmeter(self, ProxyParameter, ip_live=IP_LIVE):
        """
        获取代理校验参数
        ProxyParameter:{'ip': ip, 'expire_time': i['expire_time'], 'types': 'https'}
        """
        # proxy是从redis取出,是bytes类型,所以无法判断,进行转换
        if isinstance(ProxyParameter, bytes):
            # 转换为字符串
            ProxyParameter = ProxyParameter.decode('utf-8')
        """
            这里有2个逻辑,getter下载器yield过来的为dcit
            进行校验的为str类型
        """
        if type(ProxyParameter) == str:
            ProxyParameter = json.loads(re.sub(r"'", '"', ProxyParameter))
        now_time = int(time.time())
        # 将其转换为时间戳
        timeArray = time.strptime(ProxyParameter['expire_time'], "%Y-%m-%d %H:%M:%S")
        Expiration_timeStamp = int(time.mktime(timeArray))
        # 判断是否过期,并且存储
        if (Expiration_timeStamp - ip_live) > now_time:
            if ProxyParameter['types'] == 'http':
                self.__conn.put(str(ProxyParameter), types='http')  # redis当中存储
            if ProxyParameter['types'] == 'https':
                self.__conn.put(str(ProxyParameter), types='https')  # redis当中存储
            print('校验成功代理:', ProxyParameter)
        else:
            print('\033[1;31m已过期代理:{}\033[0m'.format(ProxyParameter))

创建开关 verify_check_switch

此开关由调度器把控,开与不开,即scheduler调度,接收到调用信号就开始调用

    def verify_check_switch(self):
        """
        校验器开关
        """
        print('\033[1;30m{}\033[0m'.format('-' * 23 + '  VaildityTester 校验器启动(校验内存中IP/redis中的IP,并且存储)  ' + '-' * 23))
        for ProxyParameter in self.__raw_proxies:
            self.get_ProxyPararmeter(ProxyParameter)

3.2 添加器

负责功能:

  • 调度下载器(调度下载器 从渠道源获取IP 免费/付费/VPS)
  • 下载器需要进行判断IP代理池数量是否达到设定阈值
  • 需要调度线路① 开关,即调用下载器,添加代理IP到篮子

创建 PoolAdder 类
需要初始化条件

    def __init__(self, threshold=UPPER_THRESHOLD):
        # 创建存入redis数据库中最大代理池数量阈值 内置条件
        self.__threshold = threshold # 60
        # 创建开关实例
        self.__tester = VaildityTester()
        # 创建redis数据库对象实例
        self.__conn = Reids_Client()
        # 创建getter实例
        self.__getter = IpProxyGetter()

在类中继续完成,是否达到阈值

    def is_over_threshold(self, types):
        """
        判断代理池中代理的数量是否达到最大值
        :return True: 超过阈值
        """
        if int(self.__conn.queue_len(types=types)) >= self.__threshold:
            return True
        return False

创建 add_to_pool 类,其中types参数作为http与https区别,里面进行循环判断,时刻判断代理池是否高于阈值,其中__getter.__Protocol_Func__,中调用了封装的元类,通过调用,分别存储到不同的篮子
其中 proxy_count == 0,出现此异常为,付费代理请求API接口过快导致,或者API接口需要添加白名单。

 def add_to_pool(self, types):
        """
        添加代理 添加到篮子
        :return:
        """
        print('\033[1;30m{}\033[0m'.format('-' * 28 + '  PoolAdder 添加器启动(代理商获得IP 存入内存 启动校验) ' + '-' * 28))
        # 循环获取ip (获取材料)
        while True:
            if self.is_over_threshold(types=types):  # 判断是否低于阈值
                print(f'目前代理池的数量为限定{UPPER_THRESHOLD},不需要下载IP')
                break
            proxy_count = 0
            for _ in self.__getter.__Protocol_Func__:
                # 判断是那种类型
                if types == 'http':
                    proxies = self.__getter.get_raw_proxies('protocol_http')
                    if len(proxies) > 0:
                        proxy_count += len(proxies)
                        # 使用校验器校验,是否过期
                        self.__tester.set_raw_proxies(proxies)  # 放入材料
                        self.__tester.verify_check_switch()  # 开启机器(开启校验方法)
                if types == 'https':
                    proxies = self.__getter.get_raw_proxies('protocol_https')
                    if len(proxies) > 0:
                        proxy_count += len(proxies)
                        # 使用校验器校验,是否过期
                        self.__tester.set_raw_proxies(proxies)  # 放入材料
                        self.__tester.verify_check_switch()  # 开启机器(开启校验方法)
            if proxy_count == 0:  #
                # 判出一个运行时间的异常
                raise RuntimeError('获取代理时间过快,可忽略此异常!')

3.3 调度器

创建 scheduler.py

调度器负责

  • 控制校验器 VaildityTester()
  • 控制添加器 PoolAdder()

创建 Scheduler(object) ,那么还需要定义两个方法,valid_proxy 控制校验器,check_pool_add 控制添加器
添加和校验应当是都在一直运行的

  • 一直运行,需要循环
  • 两个一直运行, 定义 run()方法,使用多进程

完成控制校验器代码 VaildityTester()

关于 valid_proxy(cycle=CYCLE_VAILD_TIME),即调度器控制,控制调度器做什么?

  • 完成 ② 路线,即从redis当中获取一片IP进行校验,那么取多少量,通过定义 IP_CEHCK_COUNT参数可以指定
  • CYCLE_VAILD_TIME是针对while的一个循环时间,不断从代理池当中头部获取一片,进行定期检查
  • 最终校验完毕放入校验器 set_raw_proxies(proxies)篮子中校验
  • 启动开关 verify_check_switch()

完成控制添加器代码 PoolAdder()

创建check_pool_add类,需要传入三个参数

  • lower_threshold IP代理池最低数量时候,控制添加器开关(添加器控制下载器),获取一片新IP到篮子
  • upper_threshold IP代理池数量达到这个阈值的时候,关闭添加器开关
  • cycle 循环添加器功能,看是否最低或最高情况

因为有两种类型IP,http和https,因此需要线程添加http和https

class Scheduler(object):
    """
    调度器
    """

    # 1、循环校验过程 不断从带池中头部获取一片,做定期检查
    @staticmethod
    def valid_proxy(cycle=CYCLE_VAILD_TIME):
        """
        :param CYCLE_VAILD_TIME: # 需要设置一个循环校验时间,不然放待校验ip的速度,还赶不上校验的速度
        :return:
        """
        # 连接数据库 等会校验存入进去的代理
        conn = Reids_Client()
        # 校验器对象
        tester = VaildityTester()
        # 循环校验
        while True:
            print('\033[1;30m{}\033[0m'.format('-' * 30 + '  Scheduler 调度器启动(控制调度器、添加器 核心CPU) ' + '-' * 29))
            http_parameters, https_parameters = conn.queue_len()
            for i in [http_parameters, https_parameters]:
                count = int(i[0] * IP_CEHCK_COUNT)  # 代理池存放IP数量
                types = i[2]  # 代理池为什么类型的代理
                print('准备校验的代理IP数量', count)
                if count == 0:
                    time.sleep(cycle)
                    break
                print('' * 20 + '取之前长度', conn.queue_len())
                proxies = conn.get(count=count, types=types)
                print('取之后长度', conn.queue_len())
                print('取出待校验IP数量', len(proxies))
                # 校验
                tester.set_raw_proxies(proxies)
                tester.verify_check_switch()
            time.sleep(cycle)

    @staticmethod
    def check_pool_add(lower_threshold=LOWER_THRESHOLD,
                       upper_threshold=UPPER_THRESHOLD,
                       cycle=ADD_CYCLE_TIME):
        """
        添加器开关
        """
        conn = Reids_Client()
        adder = PoolAdder(upper_threshold)
        while True:
            # (已有IP长度,IP类型)
            http_parameters, https_parameters = conn.queue_len()

            if http_parameters[1] != None:
                if http_parameters[0] <= lower_threshold:
                    t1 = threading.Thread(target=adder.add_to_pool, args=(http_parameters[2],))
                    t1.start()
            if https_parameters[1] != None:
                if https_parameters[0] <= lower_threshold:
                    t2 = threading.Thread(target=adder.add_to_pool, args=(https_parameters[2],))
                    t2.start()

            # if http_parameters[1] != None:
            #     if http_parameters[0] <= lower_threshold:
            #         adder.add_to_pool(types=http_parameters[2])  # 195
            # if https_parameters[1] != None:
            #     if https_parameters[0] <= lower_threshold:
            #         adder.add_to_pool(types=https_parameters[2])

            time.sleep(cycle)

    def run(self):
        # Scheduler.valid_proxy()
        # 创建多进程
        p1 = Process(target=Scheduler.check_pool_add)  # 从启动代理,到校验保存逻辑没有问题
        p2 = Process(target=Scheduler.valid_proxy)
        p1.start()
        p2.start()

scheduler.py 完整代码

# -*- coding: utf-8 -*-
# @Time    : 2020-12-28 11:19
# @Author  : XuGuangJun
# @FileName: scheduler.py.py
# @Software: PyCharm
import time, threading
from db import Reids_Client
from getter import IpProxyGetter
import re
from multiprocessing import Process
from settings import CYCLE_VAILD_TIME, LOWER_THRESHOLD, UPPER_THRESHOLD, ADD_CYCLE_TIME, IP_LIVE, IP_CEHCK_COUNT
import json
import logging
"""
    功能:调度器
    包含:
        ---添加器:从代理服务器获取IP,传给校验器
        ---校验器:校验IP是否过期,校验成功存储到redis  [可扩展性]
"""


class VaildityTester(object):
    '''
       校验器
    '''

    def __init__(self):
        # 设为私有,不允许外部调用,存放待校验ip
        self.__raw_proxies = []

    def set_raw_proxies(self, proxiies):
        """
        set_raw_proxies 集原料代理
        放入未校验ip
        :param proxies: 未校验ip 从getter 中传入待校验ip
        :return:
        """
        self.__raw_proxies = proxiies
        # 数据库连接-->创建的方式会影响程序性能--》用的时候创建
        self.__conn = Reids_Client()  # redis数据库对象

    def get_ProxyPararmeter(self, ProxyParameter, ip_live=IP_LIVE):
        """
        获取代理校验参数
        ProxyParameter:{'ip': ip, 'expire_time': i['expire_time'], 'types': 'https'}
        """
        # proxy是从redis取出,是bytes类型,所以无法判断,进行转换
        if isinstance(ProxyParameter, bytes):
            # 转换为字符串
            ProxyParameter = ProxyParameter.decode('utf-8')
        """
            这里有2个逻辑,getter下载器yield过来的为dcit
            进行校验的为str类型
        """
        if type(ProxyParameter) == str:
            ProxyParameter = json.loads(re.sub(r"'", '"', ProxyParameter))
        now_time = int(time.time())
        # 将其转换为时间戳
        timeArray = time.strptime(ProxyParameter['expire_time'], "%Y-%m-%d %H:%M:%S")
        Expiration_timeStamp = int(time.mktime(timeArray))
        # 判断是否过期,并且存储
        if (Expiration_timeStamp - ip_live) > now_time:
            if ProxyParameter['types'] == 'http':
                self.__conn.put(str(ProxyParameter), types='http')  # redis当中存储
            if ProxyParameter['types'] == 'https':
                self.__conn.put(str(ProxyParameter), types='https')  # redis当中存储
            print('校验成功代理:', ProxyParameter)
        else:
            print('\033[1;31m已过期代理:{}\033[0m'.format(ProxyParameter))

    def verify_check_switch(self):
        """
        校验器开关
        """
        print('\033[1;30m{}\033[0m'.format('-' * 23 + '  VaildityTester 校验器启动(校验内存中IP/redis中的IP,并且存储)  ' + '-' * 23))
        for ProxyParameter in self.__raw_proxies:
            self.get_ProxyPararmeter(ProxyParameter)


class PoolAdder(object):
    """
    添加器
    """

    def __init__(self, threshold=UPPER_THRESHOLD):
        # 创建存入redis数据库中最大代理池数量阈值 内置条件
        self.__threshold = threshold # 60
        # 创建开关实例
        self.__tester = VaildityTester()
        # 创建redis数据库对象实例
        self.__conn = Reids_Client()
        # 创建getter实例
        self.__getter = IpProxyGetter()

    def is_over_threshold(self, types):
        """
        判断代理池中代理的数量是否达到最大值
        :return True: 超过阈值
        """
        if int(self.__conn.queue_len(types=types)) >= self.__threshold:
            return True
        return False

    def add_to_pool(self, types):
        """
        添加代理 添加到篮子
        :return:
        """
        print('\033[1;30m{}\033[0m'.format('-' * 28 + '  PoolAdder 添加器启动(代理商获得IP 存入内存 启动校验) ' + '-' * 28))
        # 循环获取ip (获取材料)
        while True:
            if self.is_over_threshold(types=types):  # 判断是否低于阈值
                print(f'目前代理池的数量为限定{UPPER_THRESHOLD},不需要下载IP')
                break
            proxy_count = 0
            for _ in self.__getter.__Protocol_Func__:
                # 判断是那种类型
                if types == 'http':
                    proxies = self.__getter.get_raw_proxies('protocol_http')
                    if len(proxies) > 0:
                        proxy_count += len(proxies)
                        # 使用校验器校验,是否过期
                        self.__tester.set_raw_proxies(proxies)  # 放入材料
                        self.__tester.verify_check_switch()  # 开启机器(开启校验方法)
                if types == 'https':
                    proxies = self.__getter.get_raw_proxies('protocol_https')
                    if len(proxies) > 0:
                        proxy_count += len(proxies)
                        # 使用校验器校验,是否过期
                        self.__tester.set_raw_proxies(proxies)  # 放入材料
                        self.__tester.verify_check_switch()  # 开启机器(开启校验方法)
            if proxy_count == 0:  #
                # 判出一个运行时间的异常
                raise RuntimeError('获取代理时间过快,可忽略此异常!')


class Scheduler(object):
    """
    调度器
    """

    # 1、循环校验过程 不断从带池中头部获取一片,做定期检查
    @staticmethod
    def valid_proxy(cycle=CYCLE_VAILD_TIME):
        """
        :param CYCLE_VAILD_TIME: # 需要设置一个循环校验时间,不然放待校验ip的速度,还赶不上校验的速度
        :return:
        """
        # 连接数据库 等会校验存入进去的代理
        conn = Reids_Client()
        # 校验器对象
        tester = VaildityTester()
        # 循环校验
        while True:
            print('\033[1;30m{}\033[0m'.format('-' * 30 + '  Scheduler 调度器启动(控制调度器、添加器 核心CPU) ' + '-' * 29))
            http_parameters, https_parameters = conn.queue_len()
            for i in [http_parameters, https_parameters]:
                count = int(i[0] * IP_CEHCK_COUNT)  # 代理池存放IP数量
                types = i[2]  # 代理池为什么类型的代理
                print('准备校验的代理IP数量', count)
                if count == 0:
                    time.sleep(cycle)
                    break
                print('' * 20 + '取之前长度', conn.queue_len())
                proxies = conn.get(count=count, types=types)
                print('取之后长度', conn.queue_len())
                print('取出待校验IP数量', len(proxies))
                # 校验
                tester.set_raw_proxies(proxies)
                tester.verify_check_switch()
            time.sleep(cycle)

    @staticmethod
    def check_pool_add(lower_threshold=LOWER_THRESHOLD,
                       upper_threshold=UPPER_THRESHOLD,
                       cycle=ADD_CYCLE_TIME):
        """
        添加器开关
        """
        conn = Reids_Client()
        adder = PoolAdder(upper_threshold)
        while True:
            # (已有IP长度,IP类型)
            http_parameters, https_parameters = conn.queue_len()

            if http_parameters[1] != None:
                if http_parameters[0] <= lower_threshold:
                    t1 = threading.Thread(target=adder.add_to_pool, args=(http_parameters[2],))
                    t1.start()
            if https_parameters[1] != None:
                if https_parameters[0] <= lower_threshold:
                    t2 = threading.Thread(target=adder.add_to_pool, args=(https_parameters[2],))
                    t2.start()

            # if http_parameters[1] != None:
            #     if http_parameters[0] <= lower_threshold:
            #         adder.add_to_pool(types=http_parameters[2])  # 195
            # if https_parameters[1] != None:
            #     if https_parameters[0] <= lower_threshold:
            #         adder.add_to_pool(types=https_parameters[2])

            time.sleep(cycle)

    def run(self):
        # Scheduler.valid_proxy()
        # 创建多进程
        p1 = Process(target=Scheduler.check_pool_add)  # 从启动代理,到校验保存逻辑没有问题
        p2 = Process(target=Scheduler.valid_proxy)
        p1.start()
        p2.start()


if __name__ == '__main__':
    # adder = PoolAdder()
    # adder.add_to_pool('http')
    Scheduler().valid_proxy()

4、settings.py

# -*- coding: utf-8 -*-
# @Time    : 2020-12-28 10:19
# @Author  : XuGuangJun
# @FileName: settings.py
# @Software: PyCharm

# ---------------------- redis 配置 -----------------------
HOST = 'localhost'
PORT = 6380
PASSWORD = ''

PROXY_NAME = 'proxies'
HTTP_PROXY_NAME = 'http_proxies'  # HTPP IP存储
HTTPS_PROXY_NAME = 'https_proxies'  # HTTPS IP存储

# -------------------- 代理商API接口  -----------------------
HTTP_URL = 'http://webapi.http.zhimacangku.com/getip?num=20&type=2&pro=0&city=0&yys=0&port=1&time=1&ts=1&ys=0&cs=0&lb=1&sb=0&pb=45&mr=1®ions='
HTTPS_URL = 'http://webapi.http.zhimacangku.com/getip?num=20&type=2&pro=0&city=0&yys=0&port=11&time=1&ts=1&ys=0&cs=0&lb=1&sb=0&pb=45&mr=1®ions='

# HTTPS_URL = None  # 不获得https类型

# ------------------- Scheduler 调度器配置 -------------------

TEST_TIME_OUT = 10  # 测试单个代理的超时时长
CYCLE_VAILD_TIME = 60  # 循环校验时间
ADD_CYCLE_TIME = 60  # 循环添加时间

LOWER_THRESHOLD = 20  # 代理池IP数量最小值
UPPER_THRESHOLD = 60  # 代理池IP数数量最大值

IP_LIVE = 120  # 代理API过期时间,实际上应当提前,避免IP失效了,造成请求接口响应失败误判为封禁,网络超时,数据丢失等
IP_CEHCK_COUNT = 0.3  # 每次校验10%

5、搭建flask可视化

  • / 进入首页
  • /proxy_index 代理池首页
  • /add_ip 添加IP表名单 (付费代理都需要添加白名单功能)
  • /get_http 获取http类型IP
  • /get_https 获取https类型IP

api.py

# -*- coding: utf-8 -*-
# @Time    : 2020-12-28 18:08
# @Author  : XuGuangJun
# @FileName: api.py
# @Software: PyCharm
from flask import Flask, g, render_template,request
from db import Reids_Client
import json
import re
import requests


app = Flask(__name__)

def get_conn():
    if not hasattr(g, 'redis_client'):
        g.redis_client = Reids_Client()
    return g.redis_client

@app.route('/')
def index():
    print('访问者的IP:',request.remote_addr)
    print(request.user_agent)
    return render_template("index.html", **locals())

@app.route('/proxy_index')
def proxy_index():
    return render_template("proxy_index.html", **locals())

@app.route('/add_ip',methods=['GET','POST'])
def add_ip():
    data = request.form
    ip = data.get('ip')
    if type(ip) == str:
        if len(ip) < 11:
            res = '请输入正确ip地址!'
            return render_template("add_ip.html", **locals())
        print('成功')
        api_url = 'http://wapi.http.linkudp.com/index/index/save_white?neek=253760&appkey=249595af6047611a7f0136c55508857d&white=' +ip
        print(api_url)
        res = requests.get(url=api_url).text
        return render_template("add_ip.html", **locals())
    return render_template("add_ip.html", **locals())

@app.route('/get_http')
def get_http():
    res = str(get_conn().pop(types='http'))
    res = json.loads(re.sub(r"'", '"', res))
    res['use_num'] = str(int(res['use_num']) + 1)
    get_conn().lput(str(res), types=res['types'])
    # return render_template("http_show.html", **locals())
    return res

@app.route('/get_https')
def get_https():
    res = str(get_conn().pop(types='https'))
    res = json.loads(re.sub(r"'", '"', res))
    res['use_num'] = str(int(res['use_num']) + 1)
    get_conn().lput(str(res), types=res['types'])
    # return render_template("https_show.html", **locals())
    return res

@app.route('/count')
def count():
    http_parameters, https_parameters = get_conn().queue_len()
    # ((54, 'http_proxies', 'http'), (56, 'https_proxies', 'https'))
    http_proxy_len = http_parameters[0]
    https_proxy_len = https_parameters[0]
    return render_template('show.html', **locals())

run.py

启动前要先启动redis服务

创建 run方法,一键启动,0.0.0.0 局域网用户都能访问

# -*- coding: utf-8 -*-
# @Time    : 2020-12-28 18:10
# @Author  : XuGuangJun
# @FileName: run.py
# @Software: PyCharm

from scheduler import Scheduler
from api import app

def main():
    s = Scheduler()
    s.run()
    app.run(host='0.0.0.0',port=5000,debug=True)

if __name__ == '__main__':
    main()

四、整体效果

完成功能如下

  1. 循环校验IP存活时间,并且使用过后能够重新存储到reids,过期IP删除
  2. 获取一个http类型IP
  3. 获取一个https类型IP
  4. 查看可用IP数量
  5. 通过参数能设置,循环校验时间,校验数量,存储可用IP数量
  6. IP使用次数(为了标记使用率和后期可视化开发)
  7. IP白名单手动添加,其实后台可以加入校验规则,输入正确密码才让加入,这样可以放置外网

项目文件如下:
搭建ip代理池_第8张图片
首页,即说明文档

搭建ip代理池_第9张图片

搭建ip代理池_第10张图片
另外一个页面,没有说明的
搭建ip代理池_第11张图片
获取一个http类型IP
搭建ip代理池_第12张图片
查看可用IP数量
搭建ip代理池_第13张图片

五、待完成

  1. 标记功能,A、B、C功能网站其中一个出现封禁,比如A,那么此IP进行标记 A,B与C正常使用,A跳过
  2. 使用率,可视化(即搭载IP计算成功率,效果)

你可能感兴趣的:(各种项目,Python爬虫,python)