26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
venv/
|
||||
.venv/
|
||||
env/
|
||||
.env.local
|
||||
|
||||
# 敏感/本地数据(勿提交)
|
||||
cookies.txt
|
||||
|
||||
# 调试生成的截图
|
||||
click_position.png
|
||||
|
||||
# IDE / 编辑器
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# 系统
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
112
cookies_loader.py
Normal file
112
cookies_loader.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# 从根目录 cookies.txt 加载并注入 cookies(支持 Netscape 与 nodriver 原生格式)
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# 项目根目录(本文件所在目录的父目录即 audcf)
|
||||
ROOT_DIR = Path(__file__).resolve().parent
|
||||
COOKIES_FILE = ROOT_DIR / "cookies.txt"
|
||||
|
||||
|
||||
def get_cookies_path() -> Path:
|
||||
return COOKIES_FILE
|
||||
|
||||
|
||||
def cookies_file_exists() -> bool:
|
||||
return COOKIES_FILE.is_file()
|
||||
|
||||
|
||||
def is_netscape_format(path: Path) -> bool:
|
||||
"""根据首行判断是否为 Netscape cookies.txt 格式。"""
|
||||
if not path.is_file():
|
||||
return False
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
first = f.readline().strip()
|
||||
return first.startswith("# Netscape") or first.startswith("# HTTP Cookie File")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def parse_netscape_cookies(path: Path) -> list[dict]:
|
||||
"""
|
||||
解析 Netscape 格式 cookies.txt(含 Cookie-Editor 等导出的多行注释)。
|
||||
每行(非注释):domain, subdomain, path, secure, expires, name, value(制表符分隔;value 可含制表符故用 maxsplit=6)
|
||||
返回字典列表供转为 CookieParam。
|
||||
"""
|
||||
cookies = []
|
||||
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = line.split("\t", 6)
|
||||
if len(parts) < 7:
|
||||
continue
|
||||
domain, _subdomain, path_val, secure_val, expires_str, name, value = parts
|
||||
secure = secure_val.upper() == "TRUE"
|
||||
try:
|
||||
expires = float(expires_str) if expires_str and expires_str != "0" else None
|
||||
except ValueError:
|
||||
expires = None
|
||||
cookies.append({
|
||||
"name": name,
|
||||
"value": value,
|
||||
"domain": domain,
|
||||
"path": path_val or "/",
|
||||
"expires": expires,
|
||||
"http_only": False,
|
||||
"secure": secure,
|
||||
"same_site": "Lax",
|
||||
})
|
||||
return cookies
|
||||
|
||||
|
||||
async def load_cookies_nodriver_native(browser, path: Path) -> bool:
|
||||
"""
|
||||
使用 nodriver 原生格式加载 cookies(browser.cookies.load)。
|
||||
成功返回 True,失败返回 False。
|
||||
"""
|
||||
try:
|
||||
await browser.cookies.load(file=str(path))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _dict_to_cookie_param(d: dict):
|
||||
"""将解析后的 cookie 字典转为 nodriver CDP 所需的 CookieParam。"""
|
||||
from nodriver.cdp import network
|
||||
expires = d.get("expires")
|
||||
if expires is not None:
|
||||
expires = network.TimeSinceEpoch(expires)
|
||||
same_site = d.get("same_site")
|
||||
if same_site:
|
||||
same_site = network.CookieSameSite(same_site)
|
||||
return network.CookieParam(
|
||||
name=d["name"],
|
||||
value=d["value"],
|
||||
domain=d.get("domain"),
|
||||
path=d.get("path") or "/",
|
||||
secure=d.get("secure"),
|
||||
http_only=d.get("http_only"),
|
||||
same_site=same_site,
|
||||
expires=expires,
|
||||
)
|
||||
|
||||
|
||||
async def inject_netscape_cookies(tab, path: Path) -> bool:
|
||||
"""
|
||||
解析 Netscape 格式的 cookies.txt 并通过 CDP 注入到当前 tab。
|
||||
应在已打开目标域页面后调用。
|
||||
成功返回 True,失败返回 False。
|
||||
"""
|
||||
try:
|
||||
from nodriver import cdp
|
||||
cookies_list = parse_netscape_cookies(path)
|
||||
if not cookies_list:
|
||||
return True
|
||||
cookies_param = [_dict_to_cookie_param(d) for d in cookies_list]
|
||||
await tab.send(cdp.storage.set_cookies(cookies=cookies_param))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
93
main.py
Normal file
93
main.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
使用 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 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)
|
||||
|
||||
# 等待 CF 自动通过(约 3~8 秒)
|
||||
await page.sleep(15)
|
||||
# 若仍为挑战页,尝试点击 Turnstile
|
||||
await try_click_cf_turnstile(page)
|
||||
await page.sleep(3)
|
||||
|
||||
# 后续可在此对 page 做操作,或保存截图等
|
||||
# await page.save_screenshot("bypass_result.png")
|
||||
# content = await page.get_content()
|
||||
|
||||
return page
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uc.loop().run_until_complete(main())
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
nodriver
|
||||
Reference in New Issue
Block a user