From 828f9889ed6f238814947297480154204eef7e84 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 21 Jun 2026 22:30:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E4=BB=A3=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bg.jpg | Bin 0 -> 8073 bytes config.yaml | 12 +++- douyu/__init__.py | 3 +- douyu/config.py | 6 ++ douyu/login.py | 145 ++++++++++++++++++++++++++++++++++----------- douyu/proxy.py | 115 +++++++++++++++++++++++++++++++++++ fullbg.jpg | Bin 0 -> 8025 bytes geetest/network.py | 57 ++++++++++++++++-- main.py | 28 +++++++-- slice.jpg | Bin 0 -> 5268 bytes 10 files changed, 317 insertions(+), 49 deletions(-) create mode 100644 bg.jpg create mode 100644 douyu/proxy.py create mode 100644 fullbg.jpg create mode 100644 slice.jpg diff --git a/bg.jpg b/bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..16f066b18aac44b0aa750e8a6ff7d92e9d544fbc GIT binary patch literal 8073 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 dict: + """获取极验配置""" + return self._config.get('geetest', {'max_retries': 5}) + def get_cookie_dir(self) -> str: """获取Cookie存储目录""" return self._config.get('cookie_dir', 'data/cookies') diff --git a/douyu/login.py b/douyu/login.py index e4f5f5e..86cccec 100644 --- a/douyu/login.py +++ b/douyu/login.py @@ -12,6 +12,7 @@ from loguru import logger from .config import Account from .crypto import encrypt_password, encrypt_nickname_or_phone from .email_verifier import EmailVerifier +from .proxy import ProxyManager, get_proxy_manager # 导入geetest滑块模块 import sys @@ -49,12 +50,19 @@ class DouyuLogin: self, account: Account, proxy: Optional[str | Mapping[str, str]] = None, + proxy_api_url: Optional[str] = None, timeout: tuple[float, float] = REQUEST_TIMEOUT, + max_geetest_retries: int = 5, ): self.account = account self.proxy = proxy self.timeout = timeout + self.max_geetest_retries = max_geetest_retries self.session = requests.Session() + + # 初始化代理管理器 + self.proxy_manager = get_proxy_manager(proxy_api_url) + self._setup_session() def _setup_session(self) -> None: @@ -71,7 +79,19 @@ class DouyuLogin: 'X-Requested-With': 'XMLHttpRequest', }) - if self.proxy: + # 设置代理 + self._apply_proxy() + + def _apply_proxy(self, proxy: str = None) -> None: + """应用代理到Session""" + if proxy: + # 使用指定的代理 + self.session.proxies = { + 'http': proxy, + 'https': proxy, + } + elif self.proxy: + # 使用配置的代理 if isinstance(self.proxy, str): self.session.proxies = { 'http': self.proxy, @@ -83,6 +103,22 @@ class DouyuLogin: for scheme, url in self.proxy.items() if url } + else: + # 从代理管理器获取代理 + new_proxy = self.proxy_manager.get_proxy() + if new_proxy: + self.session.proxies = { + 'http': new_proxy, + 'https': new_proxy, + } + + def _refresh_proxy(self) -> Optional[str]: + """刷新代理IP""" + new_proxy = self.proxy_manager.get_proxy() + if new_proxy: + self._apply_proxy(new_proxy) + logger.info(f"已切换代理: {new_proxy}") + return new_proxy def _safe_url(self, url: str) -> str: """隐藏查询参数,避免日志泄露登录回调 code。""" @@ -226,62 +262,101 @@ class DouyuLogin: def _solve_geetest(self, gt: str, challenge: str) -> Tuple[str, str]: """ - 解决极验滑块验证 + 解决极验滑块验证(带重试机制) Args: gt: 极验gt参数 - challenge: 极验challenge参数 + challenge: 极验challenge参数(第一次登录返回的) Returns: (validate, seccode) """ logger.info("开始极验滑块验证...") - # 使用geetest-v3-silde-crack的完整流程 - str_16 = _generate_seed() + for attempt in range(self.max_geetest_retries): + try: + logger.info(f"极验验证尝试 {attempt + 1}/{self.max_geetest_retries}") - # 获取JS地址 - get_js_address(gt) + # 使用geetest-v3-silde-crack的完整流程 + str_16 = _generate_seed() - # 获取第一个w值 - w1 = get_w1(gt, challenge, str_16) + # 获取JS地址 + get_js_address(gt) - # 获取c和s - c, s = get_c_s(gt, challenge, w1) + # 获取第一个w值 + w1 = get_w1(gt, challenge, str_16) - # 获取第二个w值 - w2 = get_w2(gt, challenge, c, s, str_16) + # 获取c和s + c, s = get_c_s(gt, challenge, w1) - # 请求滑块 - req_slide(gt, challenge, w2) + # 获取第二个w值 + w2 = get_w2(gt, challenge, c, s, str_16) - # 获取图片 - bg, fullbg, c, s, slice, challenge_new = get_picture(gt, challenge) + # 请求滑块 + req_slide(gt, challenge, w2) - # 识别缺口位置 - hkjl = download_picture(bg, fullbg, slice) + # 获取图片 + result = get_picture(gt, challenge) - # 获取第三个w值 - w3 = get_w3(str_16, challenge_new, hkjl, c, s, gt) + # 检查返回的数据结构 + if len(result) == 6: + bg, fullbg, c, s, slice, challenge_new = result + else: + # 如果返回的数据结构不对,可能是点选验证码,需要重试 + logger.warning(f"返回数据结构异常,可能是点选验证码,重试中...") + # 刷新代理 + self._refresh_proxy() + time.sleep(1) + continue - # 最后验证 - result = req_end(gt, challenge_new, w3) + # 检查是否是滑块验证码(bg和slice不为空) + if not bg or not slice: + logger.warning(f"未获取到滑块图片,可能是点选验证码,重试中...") + # 刷新代理 + self._refresh_proxy() + time.sleep(1) + continue - # 从req_end的响应中提取validate - # req_end返回的是验证结果,成功时包含validate - if isinstance(result, dict): - validate = result.get('validate', '') - else: - # 如果返回的是字符串,可能是直接的validate值 - validate = str(result) + # 识别缺口位置 + hkjl = download_picture(bg, fullbg, slice) - if not validate: - raise ValueError("极验验证失败,未获取到validate") + # 获取第三个w值 + w3 = get_w3(str_16, challenge_new, hkjl, c, s, gt) - seccode = f"{validate}|jordan" + # 最后验证 + result = req_end(gt, challenge_new, w3) - logger.success(f"极验滑块验证成功! validate={validate[:20]}...") - return validate, seccode + # 从req_end的响应中提取validate + if isinstance(result, dict): + validate = result.get('validate', '') + success = result.get('success', 0) + message = result.get('message', '') + + if success == 1 and validate: + seccode = f"{validate}|jordan" + logger.success(f"极验滑块验证成功! validate={validate[:20]}...") + return validate, seccode + else: + logger.warning(f"极验验证失败: {message},重试中...") + # 刷新代理 + self._refresh_proxy() + time.sleep(1) + continue + else: + validate = str(result) + if validate: + seccode = f"{validate}|jordan" + logger.success(f"极验滑块验证成功! validate={validate[:20]}...") + return validate, seccode + + except Exception as e: + logger.warning(f"极验验证异常: {e},重试中...") + # 刷新代理 + self._refresh_proxy() + time.sleep(1) + continue + + raise ValueError(f"极验验证失败,已重试 {self.max_geetest_retries} 次") def _second_login(self, gt: str, challenge: str, validate: str, seccode: str, code_token: str) -> str: diff --git a/douyu/proxy.py b/douyu/proxy.py new file mode 100644 index 0000000..7ec518e --- /dev/null +++ b/douyu/proxy.py @@ -0,0 +1,115 @@ +"""代理管理模块""" + +import re +import requests +from typing import Optional, List +from loguru import logger + + +class ProxyManager: + """代理管理器""" + + def __init__(self, api_url: str = None): + self.api_url = api_url or "http://api.xiequ.cn/VAD/GetIp.aspx?act=get&uid=106015&vkey=97111DB5379E38E3BC2FF09A1B00A0C7&num=1&time=30&plat=1&re=0&type=0&so=1&ow=1&spl=1&addr=&db=1" + self.current_proxy: Optional[str] = None + + def get_proxy(self) -> Optional[str]: + """ + 从代理API获取代理IP + + Returns: + 代理URL,格式: http://ip:port + """ + try: + logger.info("获取代理IP...") + response = requests.get(self.api_url, timeout=10) + response.raise_for_status() + + text = response.text.strip() + logger.debug(f"代理API响应: {text}") + + # 解析IP:Port格式 + match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', text) + if match: + ip = match.group(1) + port = match.group(2) + proxy = f"http://{ip}:{port}" + self.current_proxy = proxy + logger.info(f"获取到代理: {proxy}") + return proxy + else: + logger.warning(f"无法解析代理地址: {text}") + return None + + except Exception as e: + logger.error(f"获取代理失败: {e}") + return None + + def get_proxies_dict(self, proxy: str = None) -> dict: + """ + 获取requests使用的proxies字典 + + Args: + proxy: 代理URL,如果不提供则使用当前代理 + + Returns: + proxies字典 + """ + proxy = proxy or self.current_proxy + if proxy: + return { + 'http': proxy, + 'https': proxy, + } + return {} + + def verify_proxy(self, proxy: str = None) -> bool: + """ + 验证代理是否可用 + + Args: + proxy: 代理URL + + Returns: + 是否可用 + """ + proxy = proxy or self.current_proxy + if not proxy: + return False + + try: + response = requests.get( + 'https://httpbin.org/ip', + proxies={'http': proxy, 'https': proxy}, + timeout=10 + ) + if response.status_code == 200: + data = response.json() + logger.info(f"代理验证成功,当前IP: {data.get('origin')}") + return True + return False + except Exception as e: + logger.error(f"代理验证失败: {e}") + return False + + +# 全局代理管理器实例 +_proxy_manager: Optional[ProxyManager] = None + + +def get_proxy_manager(api_url: str = None) -> ProxyManager: + """获取全局代理管理器实例""" + global _proxy_manager + if _proxy_manager is None: + _proxy_manager = ProxyManager(api_url) + return _proxy_manager + + +def get_proxy() -> Optional[str]: + """获取代理URL的便捷函数""" + return get_proxy_manager().get_proxy() + + +def get_proxies_dict() -> dict: + """获取proxies字典的便捷函数""" + return get_proxy_manager().get_proxies_dict() diff --git a/fullbg.jpg b/fullbg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9d13d43571dd30489f94ce1427929bd87e880077 GIT binary patch literal 8025 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 Tuple[list[int],str]: } response = requests.get( - 'https://apiv6.geetest.com/get.php?gt=' + gt + '&challenge=' + challenge + '&lang=zh-cn&pt=0&client_type=web&w=' + w + '&callback=geetest_' + str( + 'https://api.geetest.com/get.php?gt=' + gt + '&challenge=' + challenge + '&lang=zh-cn&pt=0&client_type=web&w=' + w + '&callback=geetest_' + str( int(round(time.time() * 1000))), headers=headers, timeout=REQUEST_TIMEOUT, ) response.raise_for_status() + + # 检查是否是错误响应 + if response.text.startswith('{'): + # 直接JSON响应 + data = json.loads(response.text) + if 'status' in data and data['status'] == 'error': + raise ValueError(f"极验请求失败: {data.get('user_error', data.get('error', '未知错误'))}") + match = re.search(r'\((.*)\)$', response.text) if match: json_str = match.group(1) - return json.loads(json_str)['data']['c'], json.loads(json_str)['data']['s'] + data = json.loads(json_str) + + # 检查是否是错误响应 + if 'status' in data and data['status'] == 'error': + raise ValueError(f"极验请求失败: {data.get('user_error', data.get('error', '未知错误'))}") + + return data['data']['c'], data['data']['s'] else: raise ValueError("无法解析 JSONP 响应") @@ -212,8 +226,30 @@ def get_picture(gt:str, challenge:str) -> tuple[str, str, list[int], str, str, s match = re.search(r'\((.*)\)$', response.text) if match: json_str = match.group(1) - return json.loads(json_str)['bg'], json.loads(json_str)['fullbg'], json.loads(json_str)['c'], \ - json.loads(json_str)['s'], json.loads(json_str)['slice'], json.loads(json_str)['challenge'] + data = json.loads(json_str) + + # 检查验证码类型 + if 'data' in data and isinstance(data['data'], dict): + # 新版极验格式 + inner_data = data['data'] + return ( + inner_data.get('bg', ''), + inner_data.get('fullbg', ''), + inner_data.get('c', []), + inner_data.get('s', ''), + inner_data.get('slice', ''), + inner_data.get('challenge', '') + ) + else: + # 旧版极验格式 + return ( + data.get('bg', ''), + data.get('fullbg', ''), + data.get('c', []), + data.get('s', ''), + data.get('slice', ''), + data.get('challenge', '') + ) else: raise ValueError("无法解析 JSONP 响应") @@ -245,6 +281,17 @@ def req_end(gt:str, challenge:str, w:str) -> dict: if match: json_str = match.group(1) data = json.loads(json_str) - return data + print(f"极验验证响应: {data}") + + # 检查验证是否成功 + if data.get('success') == 1: + return data + else: + # 如果验证失败,返回错误信息 + return { + 'success': 0, + 'message': data.get('message', '验证失败'), + 'validate': '' + } else: raise ValueError("无法解析 JSONP 响应") diff --git a/main.py b/main.py index 4d0da1b..dbda755 100644 --- a/main.py +++ b/main.py @@ -41,13 +41,29 @@ def login_single(config: Config, account_index: int = 0) -> str: # 创建登录器 proxy = config.get_proxy() proxy_url = None - if proxy.enabled: - proxy_url = { - 'http': proxy.http or proxy.https, - 'https': proxy.https or proxy.http, - } + proxy_api_url = None - loginer = DouyuLogin(account, proxy=proxy_url) + if proxy.enabled: + if proxy.api_url: + # 使用代理API + proxy_api_url = proxy.api_url + elif proxy.http or proxy.https: + # 使用静态代理 + proxy_url = { + 'http': proxy.http or proxy.https, + 'https': proxy.https or proxy.http, + } + + # 获取极验配置 + geetest_config = config.get_geetest_config() + max_retries = geetest_config.get('max_retries', 5) + + loginer = DouyuLogin( + account, + proxy=proxy_url, + proxy_api_url=proxy_api_url, + max_geetest_retries=max_retries, + ) # 执行登录 result = loginer.login() diff --git a/slice.jpg b/slice.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4b5311baf9c48a6a7b0e166a20631789c4f2de14 GIT binary patch literal 5268 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 ajlToXveJnYM|ySu0000