This commit is contained in:
2026-01-18 03:45:02 +08:00
parent 0d19a9a96d
commit a4ca3ec093
4 changed files with 390 additions and 100 deletions

View File

@@ -14,9 +14,11 @@ from config import (
BROWSER_WAIT_TIMEOUT,
BROWSER_SHORT_WAIT,
BROWSER_HEADLESS,
BROWSER_RANDOM_FINGERPRINT,
AUTH_PROVIDER,
get_random_name,
get_random_birthday
get_random_birthday,
get_random_fingerprint
)
from email_service import unified_get_verification_code
from crs_service import crs_generate_auth_url, crs_exchange_code, crs_add_account, extract_code_from_url
@@ -218,6 +220,84 @@ def cleanup_chrome_processes():
pass # 静默处理,不影响主流程
def _inject_fingerprint(page: ChromiumPage, fingerprint: dict):
"""注入浏览器指纹伪装脚本
Args:
page: 浏览器页面对象
fingerprint: 指纹配置字典
"""
try:
webgl_vendor = fingerprint.get("webgl_vendor", "Google Inc. (NVIDIA)")
webgl_renderer = fingerprint.get("webgl_renderer", "ANGLE (NVIDIA)")
platform = fingerprint.get("platform", "Win32")
screen = fingerprint.get("screen", {"width": 1920, "height": 1080})
# 注入指纹伪装脚本
js_script = f'''
// 伪装 WebGL 指纹
const getParameterProxyHandler = {{
apply: function(target, thisArg, args) {{
const param = args[0];
const gl = thisArg;
// UNMASKED_VENDOR_WEBGL
if (param === 37445) {{
return "{webgl_vendor}";
}}
// UNMASKED_RENDERER_WEBGL
if (param === 37446) {{
return "{webgl_renderer}";
}}
return Reflect.apply(target, thisArg, args);
}}
}};
// 代理 WebGL getParameter
const originalGetParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = new Proxy(originalGetParameter, getParameterProxyHandler);
// 代理 WebGL2 getParameter
if (typeof WebGL2RenderingContext !== 'undefined') {{
const originalGetParameter2 = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = new Proxy(originalGetParameter2, getParameterProxyHandler);
}}
// 伪装 platform
Object.defineProperty(navigator, 'platform', {{
get: () => "{platform}"
}});
// 伪装屏幕分辨率
Object.defineProperty(screen, 'width', {{ get: () => {screen["width"]} }});
Object.defineProperty(screen, 'height', {{ get: () => {screen["height"]} }});
Object.defineProperty(screen, 'availWidth', {{ get: () => {screen["width"]} }});
Object.defineProperty(screen, 'availHeight', {{ get: () => {screen["height"]} }});
// 隐藏 webdriver 特征
Object.defineProperty(navigator, 'webdriver', {{
get: () => undefined
}});
// 伪装 languages (保持中文)
Object.defineProperty(navigator, 'languages', {{
get: () => ["zh-CN", "zh", "en-US", "en"]
}});
// 伪装 plugins (模拟真实浏览器)
Object.defineProperty(navigator, 'plugins', {{
get: () => [
{{ name: "Chrome PDF Plugin", filename: "internal-pdf-viewer" }},
{{ name: "Chrome PDF Viewer", filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai" }},
{{ name: "Native Client", filename: "internal-nacl-plugin" }}
]
}});
'''
page.run_js(js_script)
except Exception as e:
log.warning(f"指纹注入失败: {e}")
def init_browser(max_retries: int = BROWSER_MAX_RETRIES) -> ChromiumPage:
"""初始化 DrissionPage 浏览器 (带重试机制)
@@ -229,6 +309,22 @@ def init_browser(max_retries: int = BROWSER_MAX_RETRIES) -> ChromiumPage:
"""
log.info("初始化浏览器...", icon="browser")
# 根据配置决定是否使用随机指纹
fingerprint = None
if BROWSER_RANDOM_FINGERPRINT:
fingerprint = get_random_fingerprint()
log.step(f"随机指纹: {fingerprint['webgl_renderer'][:40]}...")
else:
# 使用默认指纹
fingerprint = {
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
"platform": "Win32",
"webgl_vendor": "Google Inc. (NVIDIA)",
"webgl_renderer": "ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0)",
"screen": {"width": 1920, "height": 1080}
}
log.step("使用默认指纹")
last_error = None
is_linux = platform.system() == "Linux"
@@ -249,8 +345,11 @@ def init_browser(max_retries: int = BROWSER_MAX_RETRIES) -> ChromiumPage:
co.set_argument('--no-sandbox') # 服务器环境需要
co.set_argument('--disable-blink-features=AutomationControlled') # 隐藏自动化特征
# 设置 User-Agent (模拟真实浏览器)
co.set_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
# 设置 User-Agent
co.set_argument(f'--user-agent={fingerprint["user_agent"]}')
# 设置语言为中文简体
co.set_argument('--lang=zh-CN')
# Linux 服务器特殊配置
if is_linux:
@@ -277,9 +376,10 @@ def init_browser(max_retries: int = BROWSER_MAX_RETRIES) -> ChromiumPage:
co.auto_port() # Windows 使用自动分配端口
# 无头模式 (服务器运行)
screen = fingerprint.get("screen", {"width": 1920, "height": 1080})
if BROWSER_HEADLESS:
co.set_argument('--headless=new')
co.set_argument('--window-size=1920,1080')
co.set_argument(f'--window-size={screen["width"]},{screen["height"]}')
log.step("启动 Chrome (无头模式)...")
else:
log.step("启动 Chrome (无痕模式)...")
@@ -288,6 +388,11 @@ def init_browser(max_retries: int = BROWSER_MAX_RETRIES) -> ChromiumPage:
co.set_timeouts(base=PAGE_LOAD_TIMEOUT, page_load=PAGE_LOAD_TIMEOUT * 2)
page = ChromiumPage(co)
# 注入指纹伪装脚本 (仅在启用随机指纹时)
if BROWSER_RANDOM_FINGERPRINT:
_inject_fingerprint(page, fingerprint)
log.success("浏览器启动成功")
return page
@@ -614,6 +719,74 @@ def human_delay(min_sec: float = None, max_sec: float = None):
time.sleep(random.uniform(min_sec, max_sec))
def _detect_page_language(page) -> str:
"""检测页面语言
Returns:
str: 'zh' 中文, 'en' 英文
"""
try:
# 检查页面是否包含中文特征
html = page.html
# 检查中文关键词
chinese_keywords = ['确认', '继续', '登录', '注册', '验证', '邮箱', '密码', '姓名', '生日']
for keyword in chinese_keywords:
if keyword in html:
return 'zh'
return 'en'
except Exception:
return 'en' # 默认英文
def _input_birthday(page, birthday: dict):
"""智能输入生日 (自动适配中英文界面)
中文界面: yyyy/mm/dd (年/月/日)
英文界面: mm/dd/yyyy (月/日/年)
Args:
page: 浏览器页面对象
birthday: {'year': '2000', 'month': '01', 'day': '15'}
"""
# 获取三个输入框
year_input = wait_for_element(page, 'css:[data-type="year"]', timeout=10)
month_input = wait_for_element(page, 'css:[data-type="month"]', timeout=5)
day_input = wait_for_element(page, 'css:[data-type="day"]', timeout=5)
if not all([year_input, month_input, day_input]):
log.warning("未找到完整的生日输入框")
return False
# 检测页面语言
lang = _detect_page_language(page)
log.step(f"输入生日: {birthday['year']}/{birthday['month']}/{birthday['day']} (界面语言: {lang})")
# 根据语言决定输入顺序
if lang == 'zh':
# 中文: 年 -> 月 -> 日
input_order = [(year_input, birthday['year']),
(month_input, birthday['month']),
(day_input, birthday['day'])]
else:
# 英文: 月 -> 日 -> 年
input_order = [(month_input, birthday['month']),
(day_input, birthday['day']),
(year_input, birthday['year'])]
# 按顺序输入
for input_elem, value in input_order:
try:
input_elem.click()
time.sleep(0.15)
input_elem.input(value, clear=True)
time.sleep(0.2)
except Exception as e:
log.warning(f"生日输入异常: {e}")
log.success("生日已输入")
return True
def check_and_handle_error(page, max_retries=5) -> bool:
"""检查并处理页面错误 (带自动重试)"""
for attempt in range(max_retries):
@@ -726,8 +899,9 @@ def register_openai_account(page, email: str, password: str) -> bool:
# 在 chatgpt.com检查是否有注册按钮
page_ok = page.ele('css:[data-testid="signup-button"]', timeout=1) or \
page.ele('text:免费注册', timeout=1) or \
page.ele('text:Sign up', timeout=1) or \
page.ele('text:登录', timeout=1) # 也可能显示登录按钮
page.ele('text:Sign up for free', timeout=1) or \
page.ele('text:登录', timeout=1) or \
page.ele('text:Log in', timeout=1)
if not page_ok:
log.warning("页面加载异常3秒后刷新...")
save_debug_screenshot(page, 'page_load_abnormal')
@@ -750,7 +924,7 @@ def register_openai_account(page, email: str, password: str) -> bool:
if not signup_btn:
signup_btn = wait_for_element(page, 'text:免费注册', timeout=3)
if not signup_btn:
signup_btn = wait_for_element(page, 'text:Sign up', timeout=3)
signup_btn = wait_for_element(page, 'text:Sign up for free', timeout=3)
if signup_btn:
old_url = page.url
signup_btn.click()
@@ -803,12 +977,12 @@ def register_openai_account(page, email: str, password: str) -> bool:
page.refresh()
wait_for_page_stable(page, timeout=8)
log_current_url(page, "刷新后", force=True)
# 重新点击注册按钮
log.step("重新点击免费注册...")
signup_btn = wait_for_element(page, 'css:[data-testid="signup-button"]', timeout=8) or \
wait_for_element(page, 'text:免费注册', timeout=5) or \
wait_for_element(page, 'text:Sign up', timeout=3) or \
wait_for_element(page, 'text:Sign up for free', timeout=3) or \
wait_for_element(page, 'text:Get started', timeout=2)
if signup_btn:
signup_btn.click()
@@ -989,40 +1163,15 @@ def register_openai_account(page, email: str, password: str) -> bool:
name_input = wait_for_element(page, 'css:input[name="name"]', timeout=5)
if not name_input:
name_input = wait_for_element(page, 'css:input[autocomplete="name"]', timeout=3)
# 输入姓名
random_name = get_random_name()
log.step(f"输入姓名: {random_name}")
type_slowly(page, 'css:input[name="name"], input[autocomplete="name"]', random_name)
# 输入生日 (与正常注册流程一致)
# 输入生日 (使用智能适配函数)
birthday = get_random_birthday()
log.step(f"输入生日: {birthday['year']}/{birthday['month']}/{birthday['day']}")
# 年份
year_input = wait_for_element(page, 'css:[data-type="year"]', timeout=10)
if year_input:
year_input.click()
time.sleep(0.15)
year_input.input(birthday['year'], clear=True)
time.sleep(0.2)
# 月份
month_input = wait_for_element(page, 'css:[data-type="month"]', timeout=5)
if month_input:
month_input.click()
time.sleep(0.15)
month_input.input(birthday['month'], clear=True)
time.sleep(0.2)
# 日期
day_input = wait_for_element(page, 'css:[data-type="day"]', timeout=5)
if day_input:
day_input.click()
time.sleep(0.15)
day_input.input(birthday['day'], clear=True)
log.success("生日已输入")
_input_birthday(page, birthday)
# 点击提交
log.step("点击最终提交...")
@@ -1171,34 +1320,9 @@ def register_openai_account(page, email: str, password: str) -> bool:
name_input = wait_for_element(page, 'css:input[autocomplete="name"]', timeout=5)
type_slowly(page, 'css:input[name="name"], input[autocomplete="name"]', random_name)
# 输入生日 (随机 2000-2005)
# 输入生日 (使用智能适配函数)
birthday = get_random_birthday()
log.step(f"输入生日: {birthday['year']}/{birthday['month']}/{birthday['day']}")
# 年份
year_input = wait_for_element(page, 'css:[data-type="year"]', timeout=10)
if year_input:
year_input.click()
time.sleep(0.15)
year_input.input(birthday['year'], clear=True)
time.sleep(0.2)
# 月份
month_input = wait_for_element(page, 'css:[data-type="month"]', timeout=5)
if month_input:
month_input.click()
time.sleep(0.15)
month_input.input(birthday['month'], clear=True)
time.sleep(0.2)
# 日期
day_input = wait_for_element(page, 'css:[data-type="day"]', timeout=5)
if day_input:
day_input.click()
time.sleep(0.15)
day_input.input(birthday['day'], clear=True)
log.success("生日已输入")
_input_birthday(page, birthday)
# 最终提交
log.step("点击最终提交...")