Files
Mortal-Rating/install_uc_driver.py
e2hang 9f4ff8fdc6 无头化改造 + 移除 Turnstile 模拟点击 + 跨平台支持
- 删除 uc_gui_click_captcha / _poke_captcha,Turnstile 改为无头自动放行
- _wait_for_turnstile_token 改为纯轮询,新增 _acquire_turnstile_token 做 JS 重置 + 重开页面恢复
- headless 默认 True,--headless 改用 BooleanOptionalAction(--no-headless 调试)
- 新增 proxy: "direct" 配置,通过 chromium_arg --proxy-server=direct:// 绕过系统代理
  (mjai.ekyu.moe Turnstile 风控机房代理 IP,需直连)
- install_uc_driver.py 平台感知:Linux/Mac/Win 选对应 chromedriver 包和二进制名
- .gitignore 加 .DS_Store/venv/pytest_cache/个人配置
- README 加 Fedora 支持、proxy direct、headless 默认说明
- docs/headless-turnstile-troubleshooting.md 排查记录
2026-07-16 15:46:25 +08:00

193 lines
7.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import re
import os
import urllib.request
import json
import zipfile
import shutil
import platform
import subprocess
def get_chrome_version():
"""自动检测 Chrome 的主版本号及完整版本号"""
try:
# Windows
output = subprocess.check_output(
r'reg query "HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" /v version',
shell=True
).decode('utf-8')
full_version = re.search(r'([\d\.]+)', output).group(1)
major_version = int(full_version.split('.')[0])
return major_version, full_version
except Exception:
pass
# Linux / Mac 回退方案
candidates = [
["google-chrome", "--version"],
["google-chrome-stable", "--version"],
["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "--version"],
]
for cmd in candidates:
try:
output = subprocess.check_output(cmd).decode("utf-8")
full_version = re.search(r'([\d\.]+)', output).group(1)
major_version = int(full_version.split('.')[0])
return major_version, full_version
except Exception:
continue
return None, None
def _platform_archive_info(major_version):
"""根据平台和 Chrome 主版本返回 (archive_name, arch_dir, binary_suffix)。
binary_suffix 为 '.exe' 表示 Windows'' 表示 Linux/Macchromedriver 无后缀)。
"""
is_windows = os.name == 'nt'
is_mac = platform.system() == 'Darwin'
if major_version >= 115:
if is_windows:
is_64bit = platform.architecture()[0] == '64bit'
return ("chromedriver-win64.zip" if is_64bit else "chromedriver-win32.zip",
"win64" if is_64bit else "win32", ".exe")
if is_mac:
is_arm = platform.machine() in ("arm64", "aarch64")
return ("chromedriver-mac-arm64.zip" if is_arm else "chromedriver-mac-x64.zip",
"mac-arm64" if is_arm else "mac-x64", "")
# Linux
return "chromedriver-linux64.zip", "linux64", ""
# Chrome 114 及以下
if is_windows:
return "chromedriver_win32.zip", None, ".exe"
# 旧版 Chrome 在 npmmirror 上只有 win32 包Linux/Mac 无对应旧版包;
# 让上层用 None 触发 "未找到" 路径,交给 SeleniumBase 自带下载兜底
return None, None, ""
def get_npm_mirror_download_url(major_version, full_version):
"""根据 Chrome 版本从国内 npmmirror 获取 ChromeDriver 下载链接"""
archive_name, arch_dir, _ = _platform_archive_info(major_version)
if archive_name is None:
print(f"❌ 错误npmmirror 未提供 Chrome {major_version} 在当前平台的包,跳过(交给 SeleniumBase 自带下载)。")
return None
# Chrome 115 及以上更改了分发路径
if major_version >= 115:
base_url = "https://registry.npmmirror.com/-/binary/chrome-for-testing/"
else:
base_url = "https://registry.npmmirror.com/-/binary/chromedriver/"
try:
print(f"正在向镜像站请求可用版本列表: {base_url}")
req = urllib.request.Request(base_url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode('utf-8'))
# 提取所有可用的版本号文件夹
available_versions = [x['name'].strip('/') for x in data if x['type'] == 'dir']
# 匹配策略
best_version = None
if full_version in available_versions:
best_version = full_version # 精确匹配(最佳)
else:
# 找不到精确匹配时,找同一个主版本下最新的补丁版本
prefix = f"{major_version}."
matches = [v for v in available_versions if v.startswith(prefix)]
if not matches:
return None
# 将版本号按照数字排序,取最大值
best_version = sorted(matches, key=lambda x: [int(p) for p in x.split('.')])[-1]
print(f"匹配到最合适的驱动版本: {best_version}")
# 拼接最终的下载 URL
if major_version >= 115:
return f"https://registry.npmmirror.com/-/binary/chrome-for-testing/{best_version}/{arch_dir}/{archive_name}"
else:
return f"https://registry.npmmirror.com/-/binary/chromedriver/{best_version}/{archive_name}"
except Exception as e:
print(f"获取版本列表失败: {e}")
return None
def install_uc_driver():
print("=== 开始自动配置 uc_driver (国内镜像加速) ===")
try:
import seleniumbase
except ImportError:
print("❌ 错误:此环境尚未安装 seleniumbase。请先执行 pip install seleniumbase")
return
major, full = get_chrome_version()
if not major:
print("❌ 错误:无法在系统中检测到 Google Chrome请确认是否已安装。")
return
print(f"✅ 检测到本地 Chrome: 完整版本 {full} (主版本 {major})")
download_url = get_npm_mirror_download_url(major, full)
if not download_url:
print(f"❌ 错误:未能在镜像站找到 Chrome {major} 对应的驱动程序。")
return
print(f"🔗 驱动下载链接: {download_url}")
zip_path = "chromedriver_temp.zip"
try:
print("⬇️ 正在下载驱动,请稍候...")
req = urllib.request.Request(download_url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req, timeout=60) as response, open(zip_path, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
print("✅ 下载完成。")
except Exception as e:
print(f"❌ 下载失败: {e}")
return
# 获取当前 Python 环境中 seleniumbase 的 driver 存放目录
sb_drivers_dir = os.path.join(seleniumbase.__path__[0], 'drivers')
os.makedirs(sb_drivers_dir, exist_ok=True)
# Windows 下 uc_driver 带后缀Linux/Mac 无后缀
_, _, binary_suffix = _platform_archive_info(major)
uc_driver_name = 'uc_driver.exe' if binary_suffix == '.exe' else 'uc_driver'
uc_driver_path = os.path.join(sb_drivers_dir, uc_driver_name)
# 压缩包内目标二进制名(应对不同版本的目录嵌套)
target_binary = 'chromedriver.exe' if binary_suffix == '.exe' else 'chromedriver'
try:
print("📦 正在解压并配置...")
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
exe_name = None
for name in zip_ref.namelist():
# 取路径最后一段比较,兼容 chromedriver-linux64/chromedriver 这类嵌套
last_segment = name.rstrip('/').split('/')[-1].lower()
if last_segment == target_binary.lower():
exe_name = name
break
if not exe_name:
print(f"❌ 错误:未在压缩包中找到 {target_binary}")
return
# 直接提取内容并写入到期望的 uc_driver 文件位置
with zip_ref.open(exe_name) as source, open(uc_driver_path, "wb") as target:
shutil.copyfileobj(source, target)
# Linux/Mac 下需要可执行权限
if binary_suffix != '.exe':
os.chmod(uc_driver_path, 0o755)
print(f"🎉 成功!{uc_driver_name} 已自动安装至:")
print(f" -> {uc_driver_path}")
print("现在程序可以正常运行了。")
except Exception as e:
print(f"❌ 解压或配置失败: {e}")
finally:
# 清理临时下载的压缩包
if os.path.exists(zip_path):
os.remove(zip_path)
if __name__ == '__main__':
install_uc_driver()