119 lines
4.2 KiB
Python
119 lines
4.2 KiB
Python
"""
|
||
使用 Nodriver 访问 Cloudflare 保护的 audiences.me/torrents.php,
|
||
启动时从根目录 cookies.txt 加载 Cookie,采用等待自动通过 + 选择器点击 Turnstile 过验证。
|
||
"""
|
||
|
||
import nodriver as uc
|
||
from cookies_loader import (
|
||
get_cookies_path,
|
||
cookies_file_exists,
|
||
is_netscape_format,
|
||
load_cookies_nodriver_native,
|
||
inject_netscape_cookies,
|
||
)
|
||
|
||
TARGET_URL = "https://dash.cloudflare.com/sign-up"
|
||
|
||
|
||
async def get_search_btn_value(page):
|
||
"""获取 #search_btn 的 value,用于判断是否需要验证。"""
|
||
try:
|
||
el = await page.query_selector("#search_btn")
|
||
if el is None:
|
||
return None
|
||
raw = await el.get_attribute("value")
|
||
return (raw or "").strip()
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
async def try_click_cf_turnstile(page):
|
||
"""
|
||
通过截图+模板匹配定位 CF Turnstile 复选框并点击(与语言无关)。
|
||
复选框在跨域 iframe + Shadow DOM 内,用选择器点不到,需用坐标点击。
|
||
依赖:pip install opencv-python
|
||
"""
|
||
try:
|
||
# 用自定义模板 box.png 在截图中匹配复选框区域,返回中心坐标(截图是设备像素)
|
||
pos = await page.template_location(template_image="box.png")
|
||
if pos is None:
|
||
return
|
||
x, y = int(pos[0]), int(pos[1])
|
||
# 保存带点击位置标记的截图(便于调试)
|
||
try:
|
||
await page.save_screenshot("click_position.png")
|
||
import cv2
|
||
im = cv2.imread("click_position.png")
|
||
if im is not None:
|
||
r = max(15, min(im.shape[:2]) // 30)
|
||
cv2.circle(im, (x, y), r, (0, 0, 255), 3)
|
||
cv2.line(im, (x - r - 5, y), (x + r + 5, y), (0, 0, 255), 2)
|
||
cv2.line(im, (x, y - r - 5), (x, y + r + 5), (0, 0, 255), 2)
|
||
cv2.imwrite("click_position.png", im)
|
||
except Exception:
|
||
pass
|
||
# 截图坐标是设备像素,CDP 鼠标事件要 CSS 像素,需除以 devicePixelRatio
|
||
try:
|
||
raw = await page.evaluate("window.devicePixelRatio")
|
||
dpr = float(getattr(raw, "value", raw) if hasattr(raw, "value") else raw) if raw is not None else 1.0
|
||
except Exception:
|
||
dpr = 1.0
|
||
if dpr <= 0:
|
||
dpr = 1.0
|
||
x_css, y_css = x / dpr, y / dpr
|
||
# 先模拟人类移动再点击,提高通过率
|
||
await page.mouse_move(x_css, y_css, steps=10)
|
||
await page.sleep(0.3)
|
||
await page.mouse_click(x_css, y_css)
|
||
await page.sleep(3)
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
async def main():
|
||
# 建议非无头模式以提高 CF 通过率
|
||
browser = await uc.start(headless=False)
|
||
path = get_cookies_path()
|
||
|
||
if cookies_file_exists():
|
||
if is_netscape_format(path):
|
||
# Netscape 格式:先打开目标页再注入 cookies,再重新打开使 cookies 生效(避免 reload 导致 Not attached)
|
||
page = await browser.get(TARGET_URL)
|
||
await inject_netscape_cookies(page, path)
|
||
page = await browser.get(TARGET_URL)
|
||
else:
|
||
# 先尝试 nodriver 原生格式加载,再打开目标页
|
||
await load_cookies_nodriver_native(browser, path)
|
||
page = await browser.get(TARGET_URL)
|
||
else:
|
||
page = await browser.get(TARGET_URL)
|
||
|
||
while True:
|
||
# 进入页面后等待一下再判断
|
||
await page.sleep(2)
|
||
val = await get_search_btn_value(page)
|
||
if val == "给我搜":
|
||
# 不需要验证,直接结束
|
||
await browser.stop()
|
||
return
|
||
|
||
# 需要验证:等待 CF 自动通过后尝试点击 Turnstile
|
||
await page.sleep(15)
|
||
await try_click_cf_turnstile(page)
|
||
await page.sleep(3)
|
||
|
||
# 验证后等 10 秒看是否变为「给我搜」
|
||
await page.sleep(10)
|
||
val_after = await get_search_btn_value(page)
|
||
if val_after == "给我搜":
|
||
# 已通过,关闭程序
|
||
await browser.stop()
|
||
return
|
||
|
||
# 仍未通过,刷新页面重新走验证
|
||
page = await browser.get(TARGET_URL)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
uc.loop().run_until_complete(main())
|