From 4dca691890f02bb2f7540454795d700b17c78f71 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 21 Jun 2026 22:48:14 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95=20ok=20=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/main.cpython-312.pyc | Bin 5558 -> 5927 bytes bg.jpg | Bin 8073 -> 6648 bytes config.yaml | 4 +- douyu/crypto.py | 11 +- douyu/login.py | 188 +++++++++++++++++-------------- fullbg.jpg | Bin 8025 -> 6349 bytes geetest/network.py | 127 ++++++++++++++------- slice.jpg | Bin 5268 -> 4030 bytes 8 files changed, 197 insertions(+), 133 deletions(-) diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc index 1764664151baf3858e85e9bd6302f6b34ac2e7f4..bcaa6e9b3c8093597ac23e69a6a0bbda402747b8 100644 GIT binary patch delta 1005 zcmY*X>q}E%7~k2sZD$wVaTnLjW?C|=S%%$AEu_q1MGzEajH5f+S#FbeXE5W8Mg(05 zBwhloK!wD_pu~zG`U9dODyVVjgF#Y#C}#P!%ljO)@cr<6Zol92ythYn6Jh6bheI%t zwSQq-{6opK^BZMuq46*3cSVfLaT*hMLUrXrJy@c>X5;|F)-VTSCf4k*p#`PJd;IEJW*UKT2f>bcbQ0Qoj<- zpgz3}n7y*B%4v03k%3$MJp5$t>GiqW(_bEp?+k|Ac#Lh^pz}*QR7xnwML-vOW%XZ& z?o_fz>D6hH!{6CmlpWV`l_9uT-MToK3`HJ@oy$`fHAo~$e4MM~?ZgQNgZM00C4`9& z>~t3%=gysTm**q1y!~SCe6C4ng?X0tJ1=z|tQ}_406}RROesY!2d`SGY25P=7;t|V1s zUnq=Uigk6{L9-PAep#9)KyDirGG;LeNlBt(K$xj6yGn6~!EMJ6-TQVQ0QV38oJnV5 zO1En4$KdOhXf!D$qWq+*#qxdlnM$y2{-2PF=9izZ_3(}sE6_7kFF L7GLm$84&&j0znFh delta 687 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 str: """MD5加密""" @@ -58,6 +62,7 @@ def encrypt_password(password: str) -> str: def encrypt_nickname_or_phone(text: str) -> str: """加密昵称或手机号""" - # 斗鱼使用特殊的Base64编码 - encoded = base64.b64encode(text.encode('utf-8')).decode('utf-8') - return encoded + key = DOUYU_RC4_KEY.encode('utf-8') + cipher = ARC4.new(key) + encrypted = cipher.encrypt(text.encode('utf-8')) + return base64.b64encode(encrypted).decode('utf-8') diff --git a/douyu/login.py b/douyu/login.py index 86cccec..e42c18b 100644 --- a/douyu/login.py +++ b/douyu/login.py @@ -19,10 +19,13 @@ import sys sys.path.insert(0, str(Path(__file__).parent.parent)) from geetest import run_solver from geetest.solver import ( - _generate_seed, get_w1, get_w2, get_w3, + _generate_seed, get_w1, get_w2, +) +from geetest.network import ( + get_js_address, + get_c_s, + req_fullpage_validate, ) -from geetest.network import get_js_address, get_c_s, req_slide, get_picture, req_end -from geetest.imaging import download_picture class LoginResult: @@ -44,7 +47,16 @@ class DouyuLogin: VERIFY_API = "https://passport.douyu.com/wgapi/member/passport/remotelogin/verify" LOGIN_CALLBACK_API = "https://www.douyu.com/api/passport/login" WEBLOGIN_API = "https://msg.douyu.com/webLogin" - REQUEST_TIMEOUT = (3.05, 12) + LOGIN_REFERER = ( + "https://passport.douyu.com/index/login?" + "passport_reg_callback=PASSPORT_REG_SUCCESS_CALLBACK&" + "passport_login_callback=PASSPORT_LOGIN_SUCCESS_CALLBACK&" + "passport_close_callback=PASSPORT_CLOSE_CALLBACK&" + "passport_dp_callback=PASSPORT_DP_CALLBACK&" + "type=login&client_id=1&" + "state=https%3A%2F%2Fwww.douyu.com%2Fdirectory" + ) + REQUEST_TIMEOUT = (10, 30) def __init__( self, @@ -61,7 +73,7 @@ class DouyuLogin: self.session = requests.Session() # 初始化代理管理器 - self.proxy_manager = get_proxy_manager(proxy_api_url) + self.proxy_manager = get_proxy_manager(proxy_api_url) if proxy_api_url else None self._setup_session() @@ -74,8 +86,8 @@ class DouyuLogin: 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'Referer': 'https://www.douyu.com/', - 'Origin': 'https://www.douyu.com', + 'Referer': self.LOGIN_REFERER, + 'Origin': 'https://passport.douyu.com', 'X-Requested-With': 'XMLHttpRequest', }) @@ -105,15 +117,19 @@ class DouyuLogin: } else: # 从代理管理器获取代理 - new_proxy = self.proxy_manager.get_proxy() - if new_proxy: - self.session.proxies = { - 'http': new_proxy, - 'https': new_proxy, - } + if self.proxy_manager: + 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""" + if not self.proxy_manager: + return None + new_proxy = self.proxy_manager.get_proxy() if new_proxy: self._apply_proxy(new_proxy) @@ -125,32 +141,47 @@ class DouyuLogin: parsed = urlsplit(url) return urlunsplit((parsed.scheme, parsed.netloc, parsed.path, "", "")) - def _request(self, method: str, url: str, **kwargs) -> requests.Response: - """统一发送请求,附带分段超时和更明确的错误信息。""" + def _request(self, method: str, url: str, max_retries: int = 3, **kwargs) -> requests.Response: + """ + 统一发送请求,附带分段超时和更明确的错误信息。 + 代理连接失败时自动重试获取新的代理IP。 + """ timeout = kwargs.pop('timeout', self.timeout) safe_url = self._safe_url(url) - started = time.monotonic() - try: - response = self.session.request(method, url, timeout=timeout, **kwargs) - except requests.Timeout as exc: - elapsed = time.monotonic() - started - raise TimeoutError( - f"{method.upper()} {safe_url} 超时,耗时 {elapsed:.1f}s," - f"timeout={timeout}" - ) from exc - except requests.RequestException as exc: - elapsed = time.monotonic() - started - raise ConnectionError( - f"{method.upper()} {safe_url} 请求失败,耗时 {elapsed:.1f}s: {exc}" - ) from exc + for attempt in range(max_retries): + started = time.monotonic() - elapsed = time.monotonic() - started - logger.debug( - f"{method.upper()} {safe_url} -> {response.status_code} " - f"({elapsed:.2f}s)" - ) - return response + try: + response = self.session.request(method, url, timeout=timeout, **kwargs) + elapsed = time.monotonic() - started + logger.debug( + f"{method.upper()} {safe_url} -> {response.status_code} " + f"({elapsed:.2f}s)" + ) + return response + except requests.Timeout as exc: + elapsed = time.monotonic() - started + raise TimeoutError( + f"{method.upper()} {safe_url} 超时,耗时 {elapsed:.1f}s," + f"timeout={timeout}" + ) from exc + except requests.ProxyError as exc: + elapsed = time.monotonic() - started + logger.warning(f"代理连接失败,尝试 {attempt + 1}/{max_retries}: {exc}") + if attempt < max_retries - 1: + # 刷新代理 + self._refresh_proxy() + time.sleep(1) + continue + raise ConnectionError( + f"{method.upper()} {safe_url} 代理连接失败,已重试 {max_retries} 次" + ) from exc + except requests.RequestException as exc: + elapsed = time.monotonic() - started + raise ConnectionError( + f"{method.upper()} {safe_url} 请求失败,耗时 {elapsed:.1f}s: {exc}" + ) from exc def _request_json(self, method: str, url: str, source: str, **kwargs) -> dict: """请求 JSON 接口,并在响应异常时输出可定位的信息。""" @@ -181,8 +212,8 @@ class DouyuLogin: logger.info("步骤1: 第一次登录,获取极验参数...") gt, challenge, code_token, initial_cookies = self._first_login() - # 2️⃣ 极验滑块验证 - logger.info("步骤2: 极验滑块验证...") + # 2️⃣ 极验 fullpage 验证 + logger.info("步骤2: 极验 fullpage 验证...") validate, seccode = self._solve_geetest(gt, challenge) # 3️⃣ 第二次登录(带极验) @@ -229,7 +260,12 @@ class DouyuLogin: 'password': encrypted_password, 'biz_type': '1', 'room_id': '0', - 'redirect_url': 'https://www.douyu.com/', + 'redirect_url': self.LOGIN_REFERER, + 't': str(int(time.time() * 1000)), + 'client_id': '1', + 'did': '', + 'lang': '', + 'isMultiAccount': '0', } payload = self._request_json( @@ -262,7 +298,7 @@ class DouyuLogin: def _solve_geetest(self, gt: str, challenge: str) -> Tuple[str, str]: """ - 解决极验滑块验证(带重试机制) + 解决极验 fullpage 验证(带重试机制) Args: gt: 极验gt参数 @@ -271,70 +307,41 @@ class DouyuLogin: Returns: (validate, seccode) """ - logger.info("开始极验滑块验证...") + logger.info("开始极验 fullpage 验证...") for attempt in range(self.max_geetest_retries): try: logger.info(f"极验验证尝试 {attempt + 1}/{self.max_geetest_retries}") - # 使用geetest-v3-silde-crack的完整流程 + # 按斗鱼登录页 HAR:fullpage 智能检测流程,不进入图片滑块。 str_16 = _generate_seed() + proxies = dict(self.session.proxies) # 获取JS地址 - get_js_address(gt) + get_js_address(gt, proxies=proxies) # 获取第一个w值 w1 = get_w1(gt, challenge, str_16) # 获取c和s - c, s = get_c_s(gt, challenge, w1) + c, s = get_c_s(gt, challenge, w1, proxies=proxies) # 获取第二个w值 w2 = get_w2(gt, challenge, c, s, str_16) - # 请求滑块 - req_slide(gt, challenge, w2) + # HAR 中 ajax.php 直接返回 validate。 + result = req_fullpage_validate(gt, challenge, w2, proxies=proxies) - # 获取图片 - result = get_picture(gt, challenge) - - # 检查返回的数据结构 - if len(result) == 6: - bg, fullbg, c, s, slice, challenge_new = result - else: - # 如果返回的数据结构不对,可能是点选验证码,需要重试 - logger.warning(f"返回数据结构异常,可能是点选验证码,重试中...") - # 刷新代理 - self._refresh_proxy() - time.sleep(1) - continue - - # 检查是否是滑块验证码(bg和slice不为空) - if not bg or not slice: - logger.warning(f"未获取到滑块图片,可能是点选验证码,重试中...") - # 刷新代理 - self._refresh_proxy() - time.sleep(1) - continue - - # 识别缺口位置 - hkjl = download_picture(bg, fullbg, slice) - - # 获取第三个w值 - w3 = get_w3(str_16, challenge_new, hkjl, c, s, gt) - - # 最后验证 - result = req_end(gt, challenge_new, w3) - - # 从req_end的响应中提取validate + # 从 fullpage ajax.php 响应中提取 validate if isinstance(result, dict): - validate = result.get('validate', '') - success = result.get('success', 0) - message = result.get('message', '') + data = result.get('data', {}) + validate = data.get('validate', '') or result.get('validate', '') + success = data.get('result') == 'success' or result.get('success') == 1 + message = data.get('result') or result.get('message', '') - if success == 1 and validate: + if success and validate: seccode = f"{validate}|jordan" - logger.success(f"极验滑块验证成功! validate={validate[:20]}...") + logger.success(f"极验 fullpage 验证成功! validate={validate[:20]}...") return validate, seccode else: logger.warning(f"极验验证失败: {message},重试中...") @@ -346,14 +353,15 @@ class DouyuLogin: validate = str(result) if validate: seccode = f"{validate}|jordan" - logger.success(f"极验滑块验证成功! validate={validate[:20]}...") + logger.success(f"极验 fullpage 验证成功! validate={validate[:20]}...") return validate, seccode except Exception as e: logger.warning(f"极验验证异常: {e},重试中...") # 刷新代理 self._refresh_proxy() - time.sleep(1) + # 增加等待时间,避免请求过于频繁 + time.sleep(2) continue raise ValueError(f"极验验证失败,已重试 {self.max_geetest_retries} 次") @@ -363,6 +371,13 @@ class DouyuLogin: """ 第二次登录(带极验验证) + Args: + gt: 极验gt参数 + challenge: 第一次登录返回的challenge + validate: 极验验证返回的validate + seccode: 极验验证返回的seccode + code_token: 第一次登录返回的code_token + Returns: remote_code: 用于邮箱验证的code """ @@ -370,6 +385,7 @@ class DouyuLogin: encrypted_password = encrypt_password(self.account.password) # 参考HAR文件中的完整参数 + # 注意:geetest_challenge应该使用第一次登录返回的challenge data = { 'type': '1', 'nicknameOrPhoneEncrypt': encrypted_username, @@ -386,7 +402,7 @@ class DouyuLogin: 'code_data[geetest_seccode]': seccode, 'code_data[gt_version]': 'v3', 'code_data[code]': '', - 'redirect_url': 'https://www.douyu.com/', + 'redirect_url': self.LOGIN_REFERER, 't': str(int(time.time() * 1000)), 'client_id': '1', 'did': '', @@ -395,6 +411,8 @@ class DouyuLogin: 'biz_type': '1', } + logger.debug(f"第二次登录参数: challenge={challenge[:20]}..., validate={validate[:20]}...") + payload = self._request_json( 'post', self.LOGIN_API, diff --git a/fullbg.jpg b/fullbg.jpg index 9d13d43571dd30489f94ce1427929bd87e880077..962777be49b4740dcff65c1c2793df2e34b162b1 100644 GIT binary patch literal 6349 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%0D30IcD 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 requests.Response: + """发送极验 GET 请求,确保使用同一个代理出口。""" + return requests.get( + url, + params=params, + headers=headers, + proxies=dict(proxies or {}), + timeout=REQUEST_TIMEOUT, + ) + + +def _parse_jsonp_response(response: requests.Response, source: str) -> dict: + """解析极验 JSONP 响应。""" + response.raise_for_status() + match = re.search(r'\((.*)\)$', response.text) + if not match: + raise ValueError(f"{source} 无法解析 JSONP 响应") + + data = json.loads(match.group(1)) + if data.get('status') == 'error': + raise ValueError(f"{source} 失败: {data.get('user_error', data.get('error', '未知错误'))}") + return data def _parse_json_response(response: requests.Response, source: str) -> dict: @@ -83,18 +114,18 @@ def get_challenge_gt() -> Tuple[str, str]: -def get_js_address(gt:str) -> dict: +def get_js_address(gt: str, proxies: Optional[Mapping[str, str]] = None) -> dict: headers = { 'accept': '*/*', 'accept-language': 'zh-CN,zh;q=0.9', - 'referer': 'https://demos.geetest.com/', - 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"', + 'referer': PASSPORT_REFERER, + 'sec-ch-ua': '"Google Chrome";v="149", "Chromium";v="149", "Not)A;Brand";v="24"', 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', + 'sec-ch-ua-platform': '"macOS"', 'sec-fetch-dest': 'script', 'sec-fetch-mode': 'no-cors', - 'sec-fetch-site': 'same-site', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36', + 'sec-fetch-site': 'cross-site', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36', } params = { @@ -102,62 +133,72 @@ def get_js_address(gt:str) -> dict: 'callback': 'geetest_' + str(int(round(time.time() * 1000))), } - response = requests.get( - 'https://apiv6.geetest.com/gettype.php', + response = _get( + 'https://api.geetest.com/gettype.php', params=params, headers=headers, - timeout=REQUEST_TIMEOUT, + proxies=proxies, ) - response.raise_for_status() - match = re.search(r'\((.*)\)$', response.text) - if match: - json_str = match.group(1) - return json.loads(json_str) - else: - raise ValueError("无法解析 JSONP 响应") + return _parse_jsonp_response(response, "极验 gettype") -def get_c_s(gt:str, challenge:str, w:str) -> Tuple[list[int],str]: +def get_c_s( + gt: str, + challenge: str, + w: str, + proxies: Optional[Mapping[str, str]] = None, +) -> Tuple[list[int], str]: headers = { 'accept': '*/*', 'accept-language': 'zh-CN,zh;q=0.9', - 'referer': 'https://demos.geetest.com/', - 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"', + 'referer': PASSPORT_REFERER, + 'sec-ch-ua': '"Google Chrome";v="149", "Chromium";v="149", "Not)A;Brand";v="24"', 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', + 'sec-ch-ua-platform': '"macOS"', 'sec-fetch-dest': 'script', 'sec-fetch-mode': 'no-cors', - 'sec-fetch-site': 'same-site', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36', + 'sec-fetch-site': 'cross-site', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36', } - response = requests.get( + response = _get( '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, + proxies=proxies, ) - response.raise_for_status() + data = _parse_jsonp_response(response, "极验 get.php") + return data['data']['c'], data['data']['s'] - # 检查是否是错误响应 - 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) - data = json.loads(json_str) +def req_fullpage_validate( + gt: str, + challenge: str, + w: str, + proxies: Optional[Mapping[str, str]] = None, +) -> dict: + """HAR 中的 fullpage 最终校验,成功后直接返回 validate。""" + headers = { + 'Accept': '*/*', + 'Accept-Language': 'zh-CN,zh;q=0.9', + 'Connection': 'keep-alive', + 'Referer': PASSPORT_REFERER, + 'Sec-Fetch-Dest': 'script', + 'Sec-Fetch-Mode': 'no-cors', + 'Sec-Fetch-Site': 'cross-site', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36', + 'sec-ch-ua': '"Google Chrome";v="149", "Chromium";v="149", "Not)A;Brand";v="24"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + } - # 检查是否是错误响应 - 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 响应") + response = _get( + 'https://api.geetest.com/ajax.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, + proxies=proxies, + ) + return _parse_jsonp_response(response, "极验 fullpage ajax.php") def req_slide(gt:str, challenge:str, w2:str) -> None: headers = { diff --git a/slice.jpg b/slice.jpg index 4b5311baf9c48a6a7b0e166a20631789c4f2de14..843ba4944e496a0397d71581da509c2723ffaccf 100644 GIT binary patch literal 4030 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 ajlToXveJnYM|ySu0000