feat(proxy): 增强代理服务和错误处理机制

- 在 ProxyService 类中添加了代理失败标记和重试机制
- 优化了代理测试逻辑,增加了对多个测试 URL 的支持
- 在 AppleClient 类中实现了 SSL 证书验证禁用和重试策略
- 更新了请求头设置,提升了请求的兼容性和稳定性
- 增加了对登录重试次数的限制和错误处理
This commit is contained in:
danial
2025-05-01 19:09:08 +08:00
parent f813d4208a
commit 108aeb8a8a
3 changed files with 183 additions and 65 deletions

View File

@@ -28,3 +28,8 @@ proxies:
- http://fga3568:fga3568@219.152.50.19:6588 - http://fga3568:fga3568@219.152.50.19:6588
- http://fga3568:fga3568@125.74.88.251:6588 - http://fga3568:fga3568@125.74.88.251:6588
- http://fga3568:fga3568@36.111.202.63:6588 - http://fga3568:fga3568@36.111.202.63:6588
- http://fga3568:fga3568@219.152.50.20:6588
- http://fga3568:fga3568@219.152.50.21:6588
- http://fga3568:fga3568@219.152.50.22:6588
- http://fga3568:fga3568@219.152.50.23:6588
- http://fga3568:fga3568@219.152.50.24:6588

View File

@@ -1,9 +1,11 @@
import pickle import pickle
import re import re
import traceback import traceback
import urllib3
import requests import requests
from loguru import logger from loguru import logger
from urllib3.util import Retry
from requests.adapters import HTTPAdapter
from src.integrations.itunes.models.login import ( from src.integrations.itunes.models.login import (
ItunesLoginResponse, ItunesLoginResponse,
@@ -19,15 +21,34 @@ from src.integrations.june.models.login import LoginSignatureModel, ItunesLoginM
from src.integrations.june.models.redeem import AuthenticateModel, ItunesRedeemModel from src.integrations.june.models.redeem import AuthenticateModel, ItunesRedeemModel
from src.service.proxy import ProxyService from src.service.proxy import ProxyService
# 禁用 SSL 警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class AppleClient: class AppleClient:
def __init__(self): def __init__(self):
self.__session = requests.Session() self.__session = requests.Session()
self.__session.adapters.update( self.__session.verify = False # 禁用 SSL 证书验证
{
"DEFAULT_RETRIES": 5, # 设置重试策略
} retry_strategy = Retry(
total=3, # 总重试次数
backoff_factor=1, # 重试间隔增加到1秒
status_forcelist=[429, 500, 502, 503, 504], # 需要重试的HTTP状态码
allowed_methods=["GET", "POST"], # 允许重试的 HTTP 方法
raise_on_status=False, # 不抛出 HTTP 错误状态的异常
raise_on_redirect=False, # 不抛出重定向异常
) )
adapter = HTTPAdapter(max_retries=retry_strategy)
self.__session.mount("http://", adapter)
self.__session.mount("https://", adapter)
# 设置默认请求头
self.__session.headers.update({
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive'
})
def query_sign_sap_setup(self, signature: LoginSignatureModel, retries=3) -> str: def query_sign_sap_setup(self, signature: LoginSignatureModel, retries=3) -> str:
if retries <= 0: if retries <= 0:
@@ -68,6 +89,18 @@ class AppleClient:
def login( def login(
self, sign_map: AuthenticateModel, account_info: ItunesAccountInfo, server_id: str = "", retries: int = 5 self, sign_map: AuthenticateModel, account_info: ItunesAccountInfo, server_id: str = "", retries: int = 5
) -> ItunesLoginResponse: ) -> ItunesLoginResponse:
if retries <= 0:
logger.error("登录重试次数已用完")
return ItunesLoginResponse(
serverId="",
response=ItunesFailLoginPlistData(
status=30,
failureType="MAX_RETRIES_EXCEEDED",
customerMessage="登录重试次数已用完"
),
originLog="登录重试次数已用完"
)
headers = { headers = {
"X-Apple-ActionSignature": sign_map.signature, "X-Apple-ActionSignature": sign_map.signature,
"X-Apple-Store-Front": "143465-19,17", "X-Apple-Store-Front": "143465-19,17",
@@ -83,68 +116,93 @@ class AppleClient:
"Content-Type": "application/x-apple-plist; Charset=UTF-8", "Content-Type": "application/x-apple-plist; Charset=UTF-8",
"Referer": "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate", "Referer": "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate",
} }
# # 生成cookie
cookies = { cookies = {
"mzf_in": "07281", "mzf_in": "07281",
"s_vi": "", "s_vi": "",
"itsMetricsR": "Genre-CN-Mobile Software Applications-29099@@Mobile Software Applications-main@@@@", "itsMetricsR": "Genre-CN-Mobile Software Applications-29099@@Mobile Software Applications-main@@@@",
"s_vnum_n2_us": "0|1", "s_vnum_n2_us": "0|1",
} }
params = {} params = {}
if server_id != "": if server_id != "":
url = ( url = f"https://p{server_id}-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate"
"https://p"
+ server_id
+ "-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate"
)
params = {"Pod": server_id, "PRH": server_id} params = {"Pod": server_id, "PRH": server_id}
else: else:
url = ( url = "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate"
"https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate"
)
self.__session.headers["X-Apple-Store-Front"] = "143465-19,12" self.__session.headers["X-Apple-Store-Front"] = "143465-19,12"
response = self.__session.post(
url, try:
headers=headers, response = self.__session.post(
cookies=cookies, url,
params=params, headers=headers,
data=sign_map.post, cookies=cookies,
allow_redirects=False, params=params,
timeout=30, data=sign_map.post,
proxies=ProxyService().get_wrap_proxy(account_info.account_name), allow_redirects=False,
) timeout=30,
proxies=ProxyService().get_wrap_proxy(account_info.account_name),
)
except (requests.exceptions.SSLError, requests.exceptions.ProxyError) as e:
logger.error(f"SSL/代理错误: {str(e)}")
# 代理错误时,清除当前代理并重试
ProxyService().set_expire_strategy()
return self.login(sign_map, account_info, server_id, retries - 1)
except requests.exceptions.RequestException as e:
logger.error(f"请求错误: {str(e)}")
return self.login(sign_map, account_info, server_id, retries - 1)
if response.is_redirect: if response.is_redirect:
redirect_url = response.headers["Location"] redirect_url = response.headers["Location"]
groups = re.search(r"https://p(\d+)-buy.itunes.apple.com", redirect_url) groups = re.search(r"https://p(\d+)-buy.itunes.apple.com", redirect_url)
server_id = groups.group(1) if groups:
return self.login(sign_map, account_info, server_id, retries - 1) server_id = groups.group(1)
response_dict_data = parse_xml(response.text) return self.login(sign_map, account_info, server_id, retries - 1)
else:
logger.error(f"无法解析重定向URL: {redirect_url}")
return ItunesLoginResponse(
serverId="",
response=ItunesFailLoginPlistData(
status=30,
failureType="INVALID_REDIRECT",
customerMessage="无法解析重定向URL"
),
originLog=response.text
)
try:
response_dict_data = parse_xml(response.text)
except Exception as e:
logger.error(f"解析响应XML失败: {str(e)}")
return ItunesLoginResponse(
serverId=server_id,
response=ItunesFailLoginPlistData(
status=30,
failureType="PARSE_ERROR",
customerMessage="解析响应失败"
),
originLog=response.text
)
if "failureType" in response_dict_data: if "failureType" in response_dict_data:
status = 31 status = 31
# 账户被禁用 if response_dict_data.get("metrics", {}).get("dialogId") == "MZFinance.AccountDisabled":
if (
response_dict_data.get("metrics", {}).get("dialogId")
== "MZFinance.AccountDisabled"
):
status = 14 status = 14
# 账户被锁定 if response_dict_data.get("metrics", {}).get("dialogId") == "MZFinance.DisabledAndFraudLocked":
if (
response_dict_data.get("metrics", {}).get("dialogId")
== "MZFinance.DisabledAndFraudLocked"
):
status = 14 status = 14
# 密码错误
if response_dict_data.get("failureType") == "-5000": if response_dict_data.get("failureType") == "-5000":
status = 13 status = 13
if status == 31: if status == 31:
logger.warning("登录状态未知:", response_dict_data) logger.warning("登录状态未知:", response_dict_data)
response_model = ItunesFailLoginPlistData( response_model = ItunesFailLoginPlistData(**{"status": status, **response_dict_data})
**{"status": status, **response_dict_data}
)
else: else:
response_model = ItunesSuccessLoginPlistData(**response_dict_data) response_model = ItunesSuccessLoginPlistData(**response_dict_data)
return ItunesLoginResponse( return ItunesLoginResponse(
serverId=server_id, response=response_model, originLog=response.text serverId=server_id,
response=response_model,
originLog=response.text
) )
def redeem( def redeem(

View File

@@ -1,19 +1,27 @@
import copy import copy
import time import time
import requests
from loguru import logger
from src.initialization import setting
import random import random
import requests
import urllib3
from loguru import logger
from src.initialization import setting
# 禁用 SSL 警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 账户列表 # 账户列表
account_list = {} account_list = {}
class ProxyService: class ProxyService:
def __init__(self) -> None: def __init__(self) -> None:
self.proxy_list = setting.proxies.address self.proxy_list = setting.proxies.address
self.proxy_timeout = 5 # 代理超时时间
self.test_urls = [
"https://www.baidu.com",
"https://www.apple.com",
"https://buy.itunes.apple.com"
]
self.failed_proxies = {} # 记录失败的代理
def get_wrap_proxy(self, account_name: str): def get_wrap_proxy(self, account_name: str):
proxy = self.get_proxy(account_name) proxy = self.get_proxy(account_name)
@@ -36,31 +44,78 @@ class ProxyService:
def set_proxy(self, account_name: str): def set_proxy(self, account_name: str):
proxy = "" proxy = ""
if self.proxy_list: if self.proxy_list:
for _ in range(len(self.proxy_list)): # 随机打乱代理列表顺序
proxy = random.choice(self.proxy_list) available_proxies = [p for p in self.proxy_list if not self.is_proxy_failed(p)]
if self.test_baidu(proxy): if not available_proxies:
# 如果所有代理都失败,重置失败记录
self.failed_proxies.clear()
available_proxies = self.proxy_list
random.shuffle(available_proxies)
for proxy in available_proxies:
if self.test_proxy(proxy):
account_list[account_name] = { account_list[account_name] = {
"address": proxy, "address": proxy,
"timeout": time.time() + 60 * 60 * 24, "timeout": time.time() + 60 * 30, # 30分钟过期
} }
return proxy return proxy
return proxy return proxy
# 设置触发账号过期策略
def set_expire_strategy(self): def set_expire_strategy(self):
current_time = time.time()
for key, value in copy.deepcopy(account_list).items(): for key, value in copy.deepcopy(account_list).items():
if value.get("timeout") > time.time(): if value.get("timeout", 0) < current_time:
account_list.pop(key) account_list.pop(key)
# 测试下百度是否可以联通 def is_proxy_failed(self, proxy: str) -> bool:
def test_baidu(self, proxy: str): """检查代理是否在失败列表中且未过期"""
try: if proxy in self.failed_proxies:
return ( if time.time() - self.failed_proxies[proxy] < 300: # 5分钟内不重用失败的代理
requests.get( return True
"https://www.baidu.com", proxies=self.warp_proxy(proxy), timeout=5 else:
).status_code == 200 del self.failed_proxies[proxy]
) return False
except Exception as e:
logger.info(f"[+] 获取签到证书耗时: 测试proxy失败当前proxy", self.warp_proxy(proxy))
return False def mark_proxy_failed(self, proxy: str):
"""标记代理为失败"""
self.failed_proxies[proxy] = time.time()
def test_proxy(self, proxy: str) -> bool:
"""测试代理是否可用
Args:
proxy: 代理地址
Returns:
bool: 代理是否可用
"""
proxies = self.warp_proxy(proxy)
if not proxies:
return False
for url in self.test_urls:
try:
response = requests.get(
url,
proxies=proxies,
timeout=self.proxy_timeout,
verify=False, # 禁用 SSL 证书验证
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive'
}
)
if response.status_code == 200:
return True
except (requests.exceptions.SSLError,
requests.exceptions.ProxyError,
requests.exceptions.ConnectionError,
requests.exceptions.Timeout) as e:
logger.debug(f"代理测试失败: {proxy}, URL: {url}, 错误: {str(e)}")
continue
# 如果所有URL都测试失败标记代理为失败
self.mark_proxy_failed(proxy)
return False