Complete the part of the system crawler2.
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
|
||||
# 1. 不得用于任何商业用途。
|
||||
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
|
||||
# 3. 不得进行大规模爬取或对平台造成运营干扰。
|
||||
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
|
||||
# 5. 不得用于任何非法或不当的用途。
|
||||
#
|
||||
# 详细许可条款请参阅项目根目录下的LICENSE文件。
|
||||
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。
|
||||
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author : relakkes@gmail.com
|
||||
# @Time : 2023/12/2 14:37
|
||||
# @Desc : IP代理池入口
|
||||
from .base_proxy import *
|
||||
@@ -0,0 +1,75 @@
|
||||
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
|
||||
# 1. 不得用于任何商业用途。
|
||||
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
|
||||
# 3. 不得进行大规模爬取或对平台造成运营干扰。
|
||||
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
|
||||
# 5. 不得用于任何非法或不当的用途。
|
||||
#
|
||||
# 详细许可条款请参阅项目根目录下的LICENSE文件。
|
||||
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。
|
||||
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author : relakkes@gmail.com
|
||||
# @Time : 2023/12/2 11:18
|
||||
# @Desc : 爬虫 IP 获取实现
|
||||
# @Url : 快代理HTTP实现,官方文档:https://www.kuaidaili.com/?ref=ldwkjqipvz6c
|
||||
import json
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
import config
|
||||
from cache.abs_cache import AbstractCache
|
||||
from cache.cache_factory import CacheFactory
|
||||
from tools.utils import utils
|
||||
|
||||
from .types import IpInfoModel
|
||||
|
||||
|
||||
class IpGetError(Exception):
|
||||
""" ip get error"""
|
||||
|
||||
|
||||
class ProxyProvider(ABC):
|
||||
@abstractmethod
|
||||
async def get_proxy(self, num: int) -> List[IpInfoModel]:
|
||||
"""
|
||||
获取 IP 的抽象方法,不同的 HTTP 代理商需要实现该方法
|
||||
:param num: 提取的 IP 数量
|
||||
:return:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
|
||||
class IpCache:
|
||||
def __init__(self):
|
||||
self.cache_client: AbstractCache = CacheFactory.create_cache(cache_type=config.CACHE_TYPE_MEMORY)
|
||||
|
||||
def set_ip(self, ip_key: str, ip_value_info: str, ex: int):
|
||||
"""
|
||||
设置IP并带有过期时间,到期之后由 redis 负责删除
|
||||
:param ip_key:
|
||||
:param ip_value_info:
|
||||
:param ex:
|
||||
:return:
|
||||
"""
|
||||
self.cache_client.set(key=ip_key, value=ip_value_info, expire_time=ex)
|
||||
|
||||
def load_all_ip(self, proxy_brand_name: str) -> List[IpInfoModel]:
|
||||
"""
|
||||
从 redis 中加载所有还未过期的 IP 信息
|
||||
:param proxy_brand_name: 代理商名称
|
||||
:return:
|
||||
"""
|
||||
all_ip_list: List[IpInfoModel] = []
|
||||
all_ip_keys: List[str] = self.cache_client.keys(pattern=f"{proxy_brand_name}_*")
|
||||
try:
|
||||
for ip_key in all_ip_keys:
|
||||
ip_value = self.cache_client.get(ip_key)
|
||||
if not ip_value:
|
||||
continue
|
||||
all_ip_list.append(IpInfoModel(**json.loads(ip_value)))
|
||||
except Exception as e:
|
||||
utils.logger.error("[IpCache.load_all_ip] get ip err from redis db", e)
|
||||
return all_ip_list
|
||||
@@ -0,0 +1,18 @@
|
||||
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
|
||||
# 1. 不得用于任何商业用途。
|
||||
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
|
||||
# 3. 不得进行大规模爬取或对平台造成运营干扰。
|
||||
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
|
||||
# 5. 不得用于任何非法或不当的用途。
|
||||
#
|
||||
# 详细许可条款请参阅项目根目录下的LICENSE文件。
|
||||
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。
|
||||
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author : relakkes@gmail.com
|
||||
# @Time : 2024/4/5 10:13
|
||||
# @Desc :
|
||||
from .jishu_http_proxy import new_jisu_http_proxy
|
||||
from .kuaidl_proxy import new_kuai_daili_proxy
|
||||
from .wandou_http_proxy import new_wandou_http_proxy
|
||||
@@ -0,0 +1,99 @@
|
||||
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
|
||||
# 1. 不得用于任何商业用途。
|
||||
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
|
||||
# 3. 不得进行大规模爬取或对平台造成运营干扰。
|
||||
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
|
||||
# 5. 不得用于任何非法或不当的用途。
|
||||
#
|
||||
# 详细许可条款请参阅项目根目录下的LICENSE文件。
|
||||
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author : relakkes@gmail.com
|
||||
# @Time : 2024/4/5 09:32
|
||||
# @Desc : 已废弃!!!!!倒闭了!!!极速HTTP 代理IP实现. 请使用快代理实现(proxy/providers/kuaidl_proxy.py)
|
||||
import os
|
||||
from typing import Dict, List
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import httpx
|
||||
|
||||
from proxy import IpCache, IpGetError, ProxyProvider
|
||||
from proxy.types import IpInfoModel
|
||||
from tools import utils
|
||||
|
||||
|
||||
class JiSuHttpProxy(ProxyProvider):
|
||||
|
||||
def __init__(self, key: str, crypto: str, time_validity_period: int):
|
||||
"""
|
||||
极速HTTP 代理IP实现
|
||||
:param key: 提取key值 (去官网注册后获取)
|
||||
:param crypto: 加密签名 (去官网注册后获取)
|
||||
"""
|
||||
self.proxy_brand_name = "JISUHTTP"
|
||||
self.api_path = "https://api.jisuhttp.com"
|
||||
self.params = {
|
||||
"key": key,
|
||||
"crypto": crypto,
|
||||
"time": time_validity_period, # IP使用时长,支持3、5、10、15、30分钟时效
|
||||
"type": "json", # 数据结果为json
|
||||
"port": "2", # IP协议:1:HTTP、2:HTTPS、3:SOCKS5
|
||||
"pw": "1", # 是否使用账密验证, 1:是,0:否,否表示白名单验证;默认为0
|
||||
"se": "1", # 返回JSON格式时是否显示IP过期时间, 1:显示,0:不显示;默认为0
|
||||
}
|
||||
self.ip_cache = IpCache()
|
||||
|
||||
async def get_proxy(self, num: int) -> List[IpInfoModel]:
|
||||
"""
|
||||
:param num:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# 优先从缓存中拿 IP
|
||||
ip_cache_list = self.ip_cache.load_all_ip(proxy_brand_name=self.proxy_brand_name)
|
||||
if len(ip_cache_list) >= num:
|
||||
return ip_cache_list[:num]
|
||||
|
||||
# 如果缓存中的数量不够,从IP代理商获取补上,再存入缓存中
|
||||
need_get_count = num - len(ip_cache_list)
|
||||
self.params.update({"num": need_get_count})
|
||||
ip_infos = []
|
||||
async with httpx.AsyncClient() as client:
|
||||
url = self.api_path + "/fetchips" + '?' + urlencode(self.params)
|
||||
utils.logger.info(f"[JiSuHttpProxy.get_proxy] get ip proxy url:{url}")
|
||||
response = await client.get(url, headers={
|
||||
"User-Agent": "MediaCrawler https://github.com/NanmiCoder/MediaCrawler",
|
||||
})
|
||||
res_dict: Dict = response.json()
|
||||
if res_dict.get("code") == 0:
|
||||
data: List[Dict] = res_dict.get("data")
|
||||
current_ts = utils.get_unix_timestamp()
|
||||
for ip_item in data:
|
||||
ip_info_model = IpInfoModel(
|
||||
ip=ip_item.get("ip"),
|
||||
port=ip_item.get("port"),
|
||||
user=ip_item.get("user"),
|
||||
password=ip_item.get("pass"),
|
||||
expired_time_ts=utils.get_unix_time_from_time_str(ip_item.get("expire")),
|
||||
)
|
||||
ip_key = f"JISUHTTP_{ip_info_model.ip}_{ip_info_model.port}_{ip_info_model.user}_{ip_info_model.password}"
|
||||
ip_value = ip_info_model.json()
|
||||
ip_infos.append(ip_info_model)
|
||||
self.ip_cache.set_ip(ip_key, ip_value, ex=ip_info_model.expired_time_ts - current_ts)
|
||||
else:
|
||||
raise IpGetError(res_dict.get("msg", "unkown err"))
|
||||
return ip_cache_list + ip_infos
|
||||
|
||||
|
||||
def new_jisu_http_proxy() -> JiSuHttpProxy:
|
||||
"""
|
||||
构造极速HTTP实例
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return JiSuHttpProxy(
|
||||
key=os.getenv("jisu_key", ""), # 通过环境变量的方式获取极速HTTPIP提取key值
|
||||
crypto=os.getenv("jisu_crypto", ""), # 通过环境变量的方式获取极速HTTPIP提取加密签名
|
||||
time_validity_period=30 # 30分钟(最长时效)
|
||||
)
|
||||
@@ -0,0 +1,145 @@
|
||||
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
|
||||
# 1. 不得用于任何商业用途。
|
||||
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
|
||||
# 3. 不得进行大规模爬取或对平台造成运营干扰。
|
||||
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
|
||||
# 5. 不得用于任何非法或不当的用途。
|
||||
#
|
||||
# 详细许可条款请参阅项目根目录下的LICENSE文件。
|
||||
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。
|
||||
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author : relakkes@gmail.com
|
||||
# @Time : 2024/4/5 09:43
|
||||
# @Desc : 快代理HTTP实现,官方文档:https://www.kuaidaili.com/?ref=ldwkjqipvz6c
|
||||
import os
|
||||
import re
|
||||
from typing import Dict, List
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from proxy import IpCache, IpInfoModel, ProxyProvider
|
||||
from proxy.types import ProviderNameEnum
|
||||
from tools import utils
|
||||
|
||||
|
||||
class KuaidailiProxyModel(BaseModel):
|
||||
ip: str = Field("ip")
|
||||
port: int = Field("端口")
|
||||
expire_ts: int = Field("过期时间")
|
||||
|
||||
|
||||
def parse_kuaidaili_proxy(proxy_info: str) -> KuaidailiProxyModel:
|
||||
"""
|
||||
解析快代理的IP信息
|
||||
Args:
|
||||
proxy_info:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
proxies: List[str] = proxy_info.split(":")
|
||||
if len(proxies) != 2:
|
||||
raise Exception("not invalid kuaidaili proxy info")
|
||||
|
||||
pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5}),(\d+)'
|
||||
match = re.search(pattern, proxy_info)
|
||||
if not match.groups():
|
||||
raise Exception("not match kuaidaili proxy info")
|
||||
|
||||
return KuaidailiProxyModel(
|
||||
ip=match.groups()[0],
|
||||
port=int(match.groups()[1]),
|
||||
expire_ts=int(match.groups()[2])
|
||||
)
|
||||
|
||||
|
||||
class KuaiDaiLiProxy(ProxyProvider):
|
||||
def __init__(self, kdl_user_name: str, kdl_user_pwd: str, kdl_secret_id: str, kdl_signature: str):
|
||||
"""
|
||||
|
||||
Args:
|
||||
kdl_user_name:
|
||||
kdl_user_pwd:
|
||||
"""
|
||||
self.kdl_user_name = kdl_user_name
|
||||
self.kdl_user_pwd = kdl_user_pwd
|
||||
self.api_base = "https://dps.kdlapi.com/"
|
||||
self.secret_id = kdl_secret_id
|
||||
self.signature = kdl_signature
|
||||
self.ip_cache = IpCache()
|
||||
self.proxy_brand_name = ProviderNameEnum.KUAI_DAILI_PROVIDER.value
|
||||
self.params = {
|
||||
"secret_id": self.secret_id,
|
||||
"signature": self.signature,
|
||||
"pt": 1,
|
||||
"format": "json",
|
||||
"sep": 1,
|
||||
"f_et": 1,
|
||||
}
|
||||
|
||||
async def get_proxy(self, num: int) -> List[IpInfoModel]:
|
||||
"""
|
||||
快代理实现
|
||||
Args:
|
||||
num:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
uri = "/api/getdps/"
|
||||
|
||||
# 优先从缓存中拿 IP
|
||||
ip_cache_list = self.ip_cache.load_all_ip(proxy_brand_name=self.proxy_brand_name)
|
||||
if len(ip_cache_list) >= num:
|
||||
return ip_cache_list[:num]
|
||||
|
||||
# 如果缓存中的数量不够,从IP代理商获取补上,再存入缓存中
|
||||
need_get_count = num - len(ip_cache_list)
|
||||
self.params.update({"num": need_get_count})
|
||||
|
||||
ip_infos: List[IpInfoModel] = []
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(self.api_base + uri, params=self.params)
|
||||
|
||||
if response.status_code != 200:
|
||||
utils.logger.error(f"[KuaiDaiLiProxy.get_proxies] statuc code not 200 and response.txt:{response.text}")
|
||||
raise Exception("get ip error from proxy provider and status code not 200 ...")
|
||||
|
||||
ip_response: Dict = response.json()
|
||||
if ip_response.get("code") != 0:
|
||||
utils.logger.error(f"[KuaiDaiLiProxy.get_proxies] code not 0 and msg:{ip_response.get('msg')}")
|
||||
raise Exception("get ip error from proxy provider and code not 0 ...")
|
||||
|
||||
proxy_list: List[str] = ip_response.get("data", {}).get("proxy_list")
|
||||
for proxy in proxy_list:
|
||||
proxy_model = parse_kuaidaili_proxy(proxy)
|
||||
ip_info_model = IpInfoModel(
|
||||
ip=proxy_model.ip,
|
||||
port=proxy_model.port,
|
||||
user=self.kdl_user_name,
|
||||
password=self.kdl_user_pwd,
|
||||
expired_time_ts=proxy_model.expire_ts,
|
||||
|
||||
)
|
||||
ip_key = f"{self.proxy_brand_name}_{ip_info_model.ip}_{ip_info_model.port}"
|
||||
self.ip_cache.set_ip(ip_key, ip_info_model.model_dump_json(), ex=ip_info_model.expired_time_ts)
|
||||
ip_infos.append(ip_info_model)
|
||||
|
||||
return ip_cache_list + ip_infos
|
||||
|
||||
|
||||
def new_kuai_daili_proxy() -> KuaiDaiLiProxy:
|
||||
"""
|
||||
构造快代理HTTP实例
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return KuaiDaiLiProxy(
|
||||
kdl_secret_id=os.getenv("kdl_secret_id", "你的快代理secert_id"),
|
||||
kdl_signature=os.getenv("kdl_signature", "你的快代理签名"),
|
||||
kdl_user_name=os.getenv("kdl_user_name", "你的快代理用户名"),
|
||||
kdl_user_pwd=os.getenv("kdl_user_pwd", "你的快代理密码"),
|
||||
)
|
||||
@@ -0,0 +1,110 @@
|
||||
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
|
||||
# 1. 不得用于任何商业用途。
|
||||
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
|
||||
# 3. 不得进行大规模爬取或对平台造成运营干扰。
|
||||
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
|
||||
# 5. 不得用于任何非法或不当的用途。
|
||||
#
|
||||
# 详细许可条款请参阅项目根目录下的LICENSE文件。
|
||||
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author : relakkes@gmail.com
|
||||
# @Time : 2025/7/31
|
||||
# @Desc : 豌豆HTTP 代理IP实现
|
||||
import os
|
||||
from typing import Dict, List
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import httpx
|
||||
|
||||
from proxy import IpCache, IpGetError, ProxyProvider
|
||||
from proxy.types import IpInfoModel
|
||||
from tools import utils
|
||||
|
||||
|
||||
class WanDouHttpProxy(ProxyProvider):
|
||||
|
||||
def __init__(self, app_key: str, num: int = 100):
|
||||
"""
|
||||
豌豆HTTP 代理IP实现
|
||||
:param app_key: 开放的app_key,可以通过用户中心获取
|
||||
:param num: 单次提取IP数量,最大100
|
||||
"""
|
||||
self.proxy_brand_name = "WANDOUHTTP"
|
||||
self.api_path = "https://api.wandouapp.com/"
|
||||
self.params = {
|
||||
"app_key": app_key,
|
||||
"num": num,
|
||||
}
|
||||
self.ip_cache = IpCache()
|
||||
|
||||
async def get_proxy(self, num: int) -> List[IpInfoModel]:
|
||||
"""
|
||||
:param num:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# 优先从缓存中拿 IP
|
||||
ip_cache_list = self.ip_cache.load_all_ip(
|
||||
proxy_brand_name=self.proxy_brand_name
|
||||
)
|
||||
if len(ip_cache_list) >= num:
|
||||
return ip_cache_list[:num]
|
||||
|
||||
# 如果缓存中的数量不够,从IP代理商获取补上,再存入缓存中
|
||||
need_get_count = num - len(ip_cache_list)
|
||||
self.params.update({"num": min(need_get_count, 100)}) # 最大100
|
||||
ip_infos = []
|
||||
async with httpx.AsyncClient() as client:
|
||||
url = self.api_path + "?" + urlencode(self.params)
|
||||
utils.logger.info(f"[WanDouHttpProxy.get_proxy] get ip proxy url:{url}")
|
||||
response = await client.get(
|
||||
url,
|
||||
headers={
|
||||
"User-Agent": "MediaCrawler https://github.com/NanmiCoder/MediaCrawler",
|
||||
},
|
||||
)
|
||||
res_dict: Dict = response.json()
|
||||
if res_dict.get("code") == 200:
|
||||
data: List[Dict] = res_dict.get("data", [])
|
||||
current_ts = utils.get_unix_timestamp()
|
||||
for ip_item in data:
|
||||
ip_info_model = IpInfoModel(
|
||||
ip=ip_item.get("ip"),
|
||||
port=ip_item.get("port"),
|
||||
user="", # 豌豆HTTP不需要用户名密码认证
|
||||
password="",
|
||||
expired_time_ts=utils.get_unix_time_from_time_str(
|
||||
ip_item.get("expire_time")
|
||||
),
|
||||
)
|
||||
ip_key = f"WANDOUHTTP_{ip_info_model.ip}_{ip_info_model.port}"
|
||||
ip_value = ip_info_model.model_dump_json()
|
||||
ip_infos.append(ip_info_model)
|
||||
self.ip_cache.set_ip(
|
||||
ip_key, ip_value, ex=ip_info_model.expired_time_ts - current_ts
|
||||
)
|
||||
else:
|
||||
error_msg = res_dict.get("msg", "unknown error")
|
||||
# 处理具体错误码
|
||||
error_code = res_dict.get("code")
|
||||
if error_code == 10001:
|
||||
error_msg = "通用错误,具体错误信息查看msg内容"
|
||||
elif error_code == 10048:
|
||||
error_msg = "没有可用套餐"
|
||||
raise IpGetError(f"{error_msg} (code: {error_code})")
|
||||
return ip_cache_list + ip_infos
|
||||
|
||||
|
||||
def new_wandou_http_proxy() -> WanDouHttpProxy:
|
||||
"""
|
||||
构造豌豆HTTP实例
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return WanDouHttpProxy(
|
||||
app_key=os.getenv(
|
||||
"wandou_app_key", "你的豌豆HTTP app_key"
|
||||
), # 通过环境变量的方式获取豌豆HTTP app_key
|
||||
)
|
||||
@@ -0,0 +1,136 @@
|
||||
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
|
||||
# 1. 不得用于任何商业用途。
|
||||
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
|
||||
# 3. 不得进行大规模爬取或对平台造成运营干扰。
|
||||
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
|
||||
# 5. 不得用于任何非法或不当的用途。
|
||||
#
|
||||
# 详细许可条款请参阅项目根目录下的LICENSE文件。
|
||||
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author : relakkes@gmail.com
|
||||
# @Time : 2023/12/2 13:45
|
||||
# @Desc : ip代理池实现
|
||||
import random
|
||||
from typing import Dict, List
|
||||
|
||||
import httpx
|
||||
from tenacity import retry, stop_after_attempt, wait_fixed
|
||||
|
||||
import config
|
||||
from proxy.providers import (
|
||||
new_kuai_daili_proxy,
|
||||
new_wandou_http_proxy,
|
||||
)
|
||||
from tools import utils
|
||||
|
||||
from .base_proxy import ProxyProvider
|
||||
from .types import IpInfoModel, ProviderNameEnum
|
||||
|
||||
|
||||
class ProxyIpPool:
|
||||
|
||||
def __init__(
|
||||
self, ip_pool_count: int, enable_validate_ip: bool, ip_provider: ProxyProvider
|
||||
) -> None:
|
||||
"""
|
||||
|
||||
Args:
|
||||
ip_pool_count:
|
||||
enable_validate_ip:
|
||||
ip_provider:
|
||||
"""
|
||||
self.valid_ip_url = "https://echo.apifox.cn/" # 验证 IP 是否有效的地址
|
||||
self.ip_pool_count = ip_pool_count
|
||||
self.enable_validate_ip = enable_validate_ip
|
||||
self.proxy_list: List[IpInfoModel] = []
|
||||
self.ip_provider: ProxyProvider = ip_provider
|
||||
|
||||
async def load_proxies(self) -> None:
|
||||
"""
|
||||
加载IP代理
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.proxy_list = await self.ip_provider.get_proxy(self.ip_pool_count)
|
||||
|
||||
async def _is_valid_proxy(self, proxy: IpInfoModel) -> bool:
|
||||
"""
|
||||
验证代理IP是否有效
|
||||
:param proxy:
|
||||
:return:
|
||||
"""
|
||||
utils.logger.info(
|
||||
f"[ProxyIpPool._is_valid_proxy] testing {proxy.ip} is it valid "
|
||||
)
|
||||
try:
|
||||
# httpx 0.28.1 需要直接传入代理URL字符串,而不是字典
|
||||
if proxy.user and proxy.password:
|
||||
proxy_url = f"http://{proxy.user}:{proxy.password}@{proxy.ip}:{proxy.port}"
|
||||
else:
|
||||
proxy_url = f"http://{proxy.ip}:{proxy.port}"
|
||||
|
||||
async with httpx.AsyncClient(proxy=proxy_url) as client:
|
||||
response = await client.get(self.valid_ip_url)
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception as e:
|
||||
utils.logger.info(
|
||||
f"[ProxyIpPool._is_valid_proxy] testing {proxy.ip} err: {e}"
|
||||
)
|
||||
raise e
|
||||
|
||||
@retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
|
||||
async def get_proxy(self) -> IpInfoModel:
|
||||
"""
|
||||
从代理池中随机提取一个代理IP
|
||||
:return:
|
||||
"""
|
||||
if len(self.proxy_list) == 0:
|
||||
await self._reload_proxies()
|
||||
|
||||
proxy = random.choice(self.proxy_list)
|
||||
self.proxy_list.remove(proxy) # 取出来一个IP就应该移出掉
|
||||
if self.enable_validate_ip:
|
||||
if not await self._is_valid_proxy(proxy):
|
||||
raise Exception(
|
||||
"[ProxyIpPool.get_proxy] current ip invalid and again get it"
|
||||
)
|
||||
return proxy
|
||||
|
||||
async def _reload_proxies(self):
|
||||
"""
|
||||
# 重新加载代理池
|
||||
:return:
|
||||
"""
|
||||
self.proxy_list = []
|
||||
await self.load_proxies()
|
||||
|
||||
|
||||
IpProxyProvider: Dict[str, ProxyProvider] = {
|
||||
ProviderNameEnum.KUAI_DAILI_PROVIDER.value: new_kuai_daili_proxy(),
|
||||
ProviderNameEnum.WANDOU_HTTP_PROVIDER.value: new_wandou_http_proxy(),
|
||||
}
|
||||
|
||||
|
||||
async def create_ip_pool(ip_pool_count: int, enable_validate_ip: bool) -> ProxyIpPool:
|
||||
"""
|
||||
创建 IP 代理池
|
||||
:param ip_pool_count: ip池子的数量
|
||||
:param enable_validate_ip: 是否开启验证IP代理
|
||||
:return:
|
||||
"""
|
||||
pool = ProxyIpPool(
|
||||
ip_pool_count=ip_pool_count,
|
||||
enable_validate_ip=enable_validate_ip,
|
||||
ip_provider=IpProxyProvider.get(config.IP_PROXY_PROVIDER_NAME),
|
||||
)
|
||||
await pool.load_proxies()
|
||||
return pool
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -0,0 +1,35 @@
|
||||
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
|
||||
# 1. 不得用于任何商业用途。
|
||||
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
|
||||
# 3. 不得进行大规模爬取或对平台造成运营干扰。
|
||||
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
|
||||
# 5. 不得用于任何非法或不当的用途。
|
||||
#
|
||||
# 详细许可条款请参阅项目根目录下的LICENSE文件。
|
||||
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。
|
||||
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Author : relakkes@gmail.com
|
||||
# @Time : 2024/4/5 10:18
|
||||
# @Desc : 基础类型
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ProviderNameEnum(Enum):
|
||||
KUAI_DAILI_PROVIDER: str = "kuaidaili"
|
||||
WANDOU_HTTP_PROVIDER: str = "wandouhttp"
|
||||
|
||||
|
||||
class IpInfoModel(BaseModel):
|
||||
"""Unified IP model"""
|
||||
|
||||
ip: str = Field(title="ip")
|
||||
port: int = Field(title="端口")
|
||||
user: str = Field(title="IP代理认证的用户名")
|
||||
protocol: str = Field(default="https://", title="代理IP的协议")
|
||||
password: str = Field(title="IP代理认证用户的密码")
|
||||
expired_time_ts: Optional[int] = Field(title="IP 过期时间")
|
||||
Reference in New Issue
Block a user