339 lines
12 KiB
Python
339 lines
12 KiB
Python
import time
|
|
import requests
|
|
import json
|
|
import re
|
|
from typing import Mapping, Optional, Tuple
|
|
|
|
REQUEST_TIMEOUT = (3.05, 12)
|
|
PASSPORT_REFERER = "https://passport.douyu.com/"
|
|
|
|
|
|
def _get(
|
|
url: str,
|
|
*,
|
|
params: Optional[dict] = None,
|
|
headers: Optional[dict] = None,
|
|
proxies: Optional[Mapping[str, str]] = None,
|
|
) -> 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:
|
|
response.raise_for_status()
|
|
|
|
body = response.text.strip()
|
|
if not body:
|
|
raise ValueError(f"{source} 返回为空,无法解析 JSON")
|
|
|
|
try:
|
|
return response.json()
|
|
except json.JSONDecodeError as exc:
|
|
preview = body[:200].replace("\n", "\\n")
|
|
raise ValueError(f"{source} 返回的不是有效 JSON: {preview}") from exc
|
|
|
|
|
|
def get_challenge_gt_bak() -> Tuple[str, str]:
|
|
headers = {
|
|
'accept': 'application/json, text/javascript, */*; q=0.01',
|
|
'accept-language': 'zh-CN,zh;q=0.9',
|
|
'priority': 'u=1, i',
|
|
'referer': 'https://demos.geetest.com/slide-float.html',
|
|
'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
|
'sec-ch-ua-mobile': '?0',
|
|
'sec-ch-ua-platform': '"Windows"',
|
|
'sec-fetch-dest': 'empty',
|
|
'sec-fetch-mode': 'cors',
|
|
'sec-fetch-site': 'same-origin',
|
|
'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',
|
|
'x-requested-with': 'XMLHttpRequest',
|
|
}
|
|
|
|
params = {
|
|
't': str(int(round(time.time() * 1000))),
|
|
}
|
|
|
|
response = requests.get(
|
|
'https://demos.geetest.com/gt/register-slide',
|
|
params=params,
|
|
headers=headers,
|
|
timeout=10,
|
|
)
|
|
data = _parse_json_response(response, "极验 demo register-slide")
|
|
return data["gt"], data["challenge"]
|
|
|
|
|
|
def get_challenge_gt() -> Tuple[str, str]:
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36',
|
|
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'referer': 'https://passport.douyu.com/member/login?state=https%3A%2F%2Fwww.douyu.com%2Fmember%2FcpSecurity%2Fcheck_geetest_status',
|
|
}
|
|
|
|
data = {
|
|
'type': '1',
|
|
'nicknameOrPhoneEncrypt': '03ILaBwtmmCm0A==',
|
|
'password': '57219dddec71c31b7647683fa5306103',
|
|
'biz_type': '1',
|
|
}
|
|
|
|
response = requests.post(
|
|
'https://passport.douyu.com/wgapi/member/passport/login',
|
|
headers=headers,
|
|
data=data,
|
|
timeout=10,
|
|
)
|
|
payload = _parse_json_response(response, "斗鱼登录接口")
|
|
|
|
try:
|
|
code_data = payload["data"]["geetest"]["code_data"]
|
|
return code_data["gt"], code_data["challenge"]
|
|
except KeyError as exc:
|
|
preview = json.dumps(payload, ensure_ascii=False)[:300]
|
|
raise ValueError(f"斗鱼登录接口返回中缺少极验参数: {preview}") from exc
|
|
|
|
|
|
|
|
def get_js_address(gt: str, proxies: Optional[Mapping[str, str]] = None) -> dict:
|
|
headers = {
|
|
'accept': '*/*',
|
|
'accept-language': 'zh-CN,zh;q=0.9',
|
|
'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': '"macOS"',
|
|
'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',
|
|
}
|
|
|
|
params = {
|
|
'gt': gt,
|
|
'callback': 'geetest_' + str(int(round(time.time() * 1000))),
|
|
}
|
|
|
|
response = _get(
|
|
'https://api.geetest.com/gettype.php',
|
|
params=params,
|
|
headers=headers,
|
|
proxies=proxies,
|
|
)
|
|
|
|
return _parse_jsonp_response(response, "极验 gettype")
|
|
|
|
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': 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': '"macOS"',
|
|
'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',
|
|
}
|
|
|
|
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,
|
|
proxies=proxies,
|
|
)
|
|
data = _parse_jsonp_response(response, "极验 get.php")
|
|
return data['data']['c'], data['data']['s']
|
|
|
|
|
|
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"',
|
|
}
|
|
|
|
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 = {
|
|
'Accept': '*/*',
|
|
'Accept-Language': 'zh-CN,zh;q=0.9',
|
|
'Connection': 'keep-alive',
|
|
'Referer': 'https://demos.geetest.com/',
|
|
'Sec-Fetch-Dest': 'script',
|
|
'Sec-Fetch-Mode': 'no-cors',
|
|
'Sec-Fetch-Site': 'cross-site',
|
|
'Sec-Fetch-Storage-Access': 'active',
|
|
'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-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
|
'sec-ch-ua-mobile': '?0',
|
|
'sec-ch-ua-platform': '"Windows"',
|
|
}
|
|
|
|
response = requests.get(
|
|
'https://api.geevisit.com/ajax.php?gt=' + gt + '&challenge=' + challenge + '&lang=zh-cn&pt=0&client_type=web&w=' + w2 + '&callback=geetest_' + str(
|
|
int(round(time.time() * 1000))),
|
|
headers=headers,
|
|
timeout=REQUEST_TIMEOUT,
|
|
)
|
|
response.raise_for_status()
|
|
|
|
def get_picture(gt:str, challenge:str) -> tuple[str, str, list[int], str, str, str]:
|
|
headers = {
|
|
'Accept': '*/*',
|
|
'Accept-Language': 'zh-CN,zh;q=0.9',
|
|
'Connection': 'keep-alive',
|
|
'Referer': 'https://demos.geetest.com/',
|
|
'Sec-Fetch-Dest': 'script',
|
|
'Sec-Fetch-Mode': 'no-cors',
|
|
'Sec-Fetch-Site': 'cross-site',
|
|
'Sec-Fetch-Storage-Access': 'active',
|
|
'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-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
|
'sec-ch-ua-mobile': '?0',
|
|
'sec-ch-ua-platform': '"Windows"',
|
|
}
|
|
params = {
|
|
'is_next': 'true',
|
|
'type': 'slide3',
|
|
'gt': gt,
|
|
'challenge': challenge,
|
|
'lang': 'zh-cn',
|
|
'https': 'true',
|
|
'protocol': 'https://',
|
|
'offline': 'false',
|
|
'product': 'embed',
|
|
'api_server': 'api.geevisit.com',
|
|
'isPC': 'true',
|
|
'autoReset': 'true',
|
|
'width': '100%',
|
|
'callback': 'geetest_'+str(int(round(time.time() * 1000))),
|
|
}
|
|
|
|
response = requests.get(
|
|
'https://api.geevisit.com/get.php',
|
|
params=params,
|
|
headers=headers,
|
|
timeout=REQUEST_TIMEOUT,
|
|
)
|
|
response.raise_for_status()
|
|
print(response.text)
|
|
match = re.search(r'\((.*)\)$', response.text)
|
|
if match:
|
|
json_str = match.group(1)
|
|
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 响应")
|
|
|
|
def req_end(gt:str, challenge:str, w:str) -> dict:
|
|
|
|
headers = {
|
|
'Accept': '*/*',
|
|
'Accept-Language': 'zh-CN,zh;q=0.9',
|
|
'Connection': 'keep-alive',
|
|
'Referer': 'https://demos.geetest.com/',
|
|
'Sec-Fetch-Dest': 'script',
|
|
'Sec-Fetch-Mode': 'no-cors',
|
|
'Sec-Fetch-Site': 'cross-site',
|
|
'Sec-Fetch-Storage-Access': 'active',
|
|
'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-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
|
'sec-ch-ua-mobile': '?0',
|
|
'sec-ch-ua-platform': '"Windows"',
|
|
}
|
|
|
|
response = requests.get(
|
|
'https://api.geevisit.com/ajax.php?gt='+gt+'&challenge='+challenge+'&lang=zh-cn&%24_BCm=0&client_type=web&w='+w+'&callback=geetest_'+str(int(round(time.time() * 1000))),
|
|
headers=headers,
|
|
timeout=REQUEST_TIMEOUT,
|
|
)
|
|
response.raise_for_status()
|
|
print(response.text)
|
|
match = re.search(r'\((.*)\)$', response.text)
|
|
if match:
|
|
json_str = match.group(1)
|
|
data = json.loads(json_str)
|
|
print(f"极验验证响应: {data}")
|
|
|
|
# 检查验证是否成功
|
|
if data.get('success') == 1:
|
|
return data
|
|
else:
|
|
# 如果验证失败,返回错误信息
|
|
return {
|
|
'success': 0,
|
|
'message': data.get('message', '验证失败'),
|
|
'validate': ''
|
|
}
|
|
else:
|
|
raise ValueError("无法解析 JSONP 响应")
|