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