From 47ba116ba13068b231ebc2a2ec442b4ed9bb1475 Mon Sep 17 00:00:00 2001 From: theluyuan <1162963624@qq.com> Date: Sat, 21 Feb 2026 19:53:04 +0800 Subject: [PATCH] =?UTF-8?q?Initial=20commit:=20=E9=A1=B9=E7=9B=AE=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .gitignore | 26 +++++++++++ box.png | Bin 0 -> 703 bytes cookies_loader.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++ main.py | 93 ++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 5 files changed, 232 insertions(+) create mode 100644 .gitignore create mode 100644 box.png create mode 100644 cookies_loader.py create mode 100644 main.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b638bc6 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/box.png b/box.png new file mode 100644 index 0000000000000000000000000000000000000000..1e96f5887ba91667b2b9c0f4ecd57daaee4ef971 GIT binary patch literal 703 zcmeAS@N?(olHy`uVBq!ia0vp^MnG)9!3HFQziaLSQmknyuFgR!lOHoYU7a)&D8O0Z z5n0T@AbAah86_^BddtATl;G*&7*fIb^hoj+)%05p4}Vv#&iZ`g`Ig)x+l-Ap*-qZr zmV0}fU{BKRwA(hdYtF8!nxB8QfAh7ty;d_bmZk6ee!MhLZ~E8&%*-GDH!}YCe}KVe zUb}qVjP-S2SMT{Wb^Q}o{R+n4m$l=6dhPplb^WIghxwT|m@{wC7mu$ood5Go`i|G@ zc58@mF}(3_b!y!GZddpDnrFr_H6M>MTrpy(uKw|`{pZ>II_3lQi|_w=W^VK6!(j#= zpv3M+UD^yDG8GRR|9n|)|8&!7z2y2|Fa7IJHL}ZbH~=NrF(gc4ym5PRza5vuBcKV! z!OQ(v6Y6JKm1^bJeV)zeVe{+7Vz5!#K-*Q8`OW1zP~W^VcsbJqkaK``{Fj$#_|MOC z;6FRt10?_bsZU_|XKz3|>2K4{KWFrKb7LcO!+$pGvNsG06M+IVQW(B%D|s2jAY}jd zrT@*1$?OcjFZan>{{VWL%OUCIrKMocSU>JDX7IRE|NrkEV7NWAJYUK8`||Ajf70IV z|6j)-ls>1BjbSp+fqT>RH5Tp$3#*(4h7#cqR_|Lz8+4IX<=Nm8qGI+ZBxvX`)E> literal 0 HcmV?d00001 diff --git a/cookies_loader.py b/cookies_loader.py new file mode 100644 index 0000000..c2d4994 --- /dev/null +++ b/cookies_loader.py @@ -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 diff --git a/main.py b/main.py new file mode 100644 index 0000000..86effaa --- /dev/null +++ b/main.py @@ -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()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..819d361 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +nodriver