From 97fff0b30ef027449046fdd8b8a87f32213724ce Mon Sep 17 00:00:00 2001 From: Kai Tan Date: Wed, 1 Apr 2026 00:03:53 +0800 Subject: [PATCH] release: cut v0.01 with working downloads and unstable proxy rotation --- .gitignore | 3 + __pycache__/zlib_dl.cpython-314.pyc | Bin 64211 -> 0 bytes test1.log | 5 - test_503.log | 4 - test_503.py | 88 -------- test_503.yaml | 23 -- test_503_2.py | 61 ------ test_clash.yaml | 13 -- test_cookie.py | 6 - test_html.py | 7 - test_link.py | 16 -- test_mihomo.py | 22 -- test_mihomo_bounce.py | 72 ------- test_mihomo_bypass.py | 46 ---- test_mihomo_fix.py | 52 ----- test_mihomo_gen.py | 42 ---- test_mihomo_ip.py | 19 -- test_out.txt | 3 - test_search.py | 10 - test_us_clean.py | 13 -- test_us_detail.py | 21 -- test_what_page_url_is.py | 9 - zlib_dl.py | 315 ++++++++++++++++++++++------ 23 files changed, 250 insertions(+), 600 deletions(-) create mode 100644 .gitignore delete mode 100644 __pycache__/zlib_dl.cpython-314.pyc delete mode 100644 test1.log delete mode 100644 test_503.log delete mode 100644 test_503.py delete mode 100644 test_503.yaml delete mode 100644 test_503_2.py delete mode 100644 test_clash.yaml delete mode 100644 test_cookie.py delete mode 100644 test_html.py delete mode 100644 test_link.py delete mode 100644 test_mihomo.py delete mode 100644 test_mihomo_bounce.py delete mode 100644 test_mihomo_bypass.py delete mode 100644 test_mihomo_fix.py delete mode 100644 test_mihomo_gen.py delete mode 100644 test_mihomo_ip.py delete mode 100644 test_out.txt delete mode 100644 test_search.py delete mode 100644 test_us_clean.py delete mode 100644 test_us_detail.py delete mode 100644 test_what_page_url_is.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3531e17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +downloads/ +__pycache__/ +*.pyc diff --git a/__pycache__/zlib_dl.cpython-314.pyc b/__pycache__/zlib_dl.cpython-314.pyc deleted file mode 100644 index 595ce3507c9a876d57462adbbe5c74db54117b7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64211 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 diff --git a/test1.log b/test1.log deleted file mode 100644 index 9a53c16..0000000 --- a/test1.log +++ /dev/null @@ -1,5 +0,0 @@ - 📡 已加载 56 个代理 - 🔗 已启动代理隧道: 🇭🇰 香港 01 -> 127.0.0.1:10800 - ⏳ 下载遇到 PoW 挑战,正在解决... - ✅ 挑战已解决 (耗时 0.024s,节点 zh.ngd101.ru) -下载返回了 HTML 页面(可能是限制或错误) diff --git a/test_503.log b/test_503.log deleted file mode 100644 index bb3230d..0000000 --- a/test_503.log +++ /dev/null @@ -1,4 +0,0 @@ -Proxy IP: 61.171.248.193 -Index status: 503 -Index status after PoW: 503 -Search status: 503 diff --git a/test_503.py b/test_503.py deleted file mode 100644 index 63f80cb..0000000 --- a/test_503.py +++ /dev/null @@ -1,88 +0,0 @@ -import urllib.request, time, hashlib, re, os, subprocess -from curl_cffi import requests - -def solve_challenge(html): - m = re.search(r"\['([A-F0-9]{40})'", html) - if not m: return None, None - c = m.group(1) - n1 = int(c[0], 16) - i = 0 - t0 = time.time() - while True: - d = hashlib.sha1((c + str(i)).encode()).digest() - if d[n1] == 0xb0 and d[n1+1] == 0x0b: break - i += 1 - return c + str(i), f"{time.time() - t0:.3f}" - -yaml_config = """ -mode: global -mixed-port: 10899 -bind-address: '127.0.0.1' -proxies: -""" -proxy_str = "" -with open("proxies.txt") as f: - for line in f: - if "ss," in line: - proxy_str = line.strip() - break -parts = proxy_str.split("=", 1) -cfg = parts[1].strip().split(",") -params = dict(p.strip().split("=", 1) for p in cfg[3:] if "=" in p) -yaml_config += f""" - name: "test" - type: ss - server: "{cfg[1].strip()}" - port: {cfg[2].strip()} - cipher: "{params.get('encrypt-method', '')}" - password: "{params.get('password', '')}" -""" -if "obfs" in params: - yaml_config += f" plugin: obfs\n plugin-opts:\n mode: {params['obfs']}\n host: {params['obfs-host']}\n" -with open("test_503.yaml", "w") as f: f.write(yaml_config) - -proc = subprocess.Popen(["./.venv/bin/mihomo", "-f", "test_503.yaml"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) -time.sleep(2) - -s = requests.Session() -p = {"http": "socks5://127.0.0.1:10899", "https": "socks5://127.0.0.1:10899"} - -try: - with open("test_503.log", "w") as out: - ip_resp = s.get("http://httpbin.org/ip", proxies=p, impersonate="chrome", timeout=5) - out.write(f"Proxy IP: {ip_resp.json()['origin']}\n") - - r1 = s.get("https://zlib.li/", proxies=p, impersonate="chrome", timeout=10) - out.write(f"Index status: {r1.status_code}\n") - - if r1.status_code == 503 and "Checking your browser" in r1.text: - ct, cti = solve_challenge(r1.text) - if ct: - s.cookies.set("c_token", ct, domain="zlib.li", path="/") - s.cookies.set("c_time", cti, domain="zlib.li", path="/") - r1 = s.get("https://zlib.li/", proxies=p, impersonate="chrome", timeout=10) - out.write(f"Index status after PoW: {r1.status_code}\n") - - r2 = s.get("https://zlib.li/s/Project", proxies=p, impersonate="chrome", timeout=10) - out.write(f"Search status: {r2.status_code}\n") - - from bs4 import BeautifulSoup - soup = BeautifulSoup(r2.text, "lxml") - card = soup.find("z-bookcard") - if card and card.get("download"): - dl = card.get("download") - out.write(f"Got dl path: {dl}\n") - r3 = s.get("https://zlib.li" + dl, proxies=p, impersonate="chrome", allow_redirects=True, timeout=10) - out.write(f"Download status: {r3.status_code}\n") - if r3.status_code == 503: - if "Checking your browser" in r3.text: - ct, cti = solve_challenge(r3.text) - if ct: - s.cookies.set("c_token", ct, domain="zlib.li", path="/") - s.cookies.set("c_time", cti, domain="zlib.li", path="/") - r4 = s.get("https://zlib.li" + dl, proxies=p, impersonate="chrome", allow_redirects=True, timeout=10) - out.write(f"Retry status: {r4.status_code}\n") - with open("test_503_body.html", "w") as f2: f2.write(r4.text) - else: - out.write("Not PoW 503\n") -finally: - proc.terminate() diff --git a/test_503.yaml b/test_503.yaml deleted file mode 100644 index 28c7629..0000000 --- a/test_503.yaml +++ /dev/null @@ -1,23 +0,0 @@ - -mode: rule -mixed-port: 10899 -bind-address: '127.0.0.1' -proxies: - - name: "test" - type: ss - server: "d32b6cd.gylcxo.xyz" - port: 18300 - cipher: "aes-128-gcm" - password: "ced24190439d1ed1" - plugin: obfs - plugin-opts: - mode: http - host: Sm0lO349ch.microsoft.com - -proxy-groups: - - name: "Proxy" - type: select - proxies: - - "test" -rules: - - MATCH, Proxy diff --git a/test_503_2.py b/test_503_2.py deleted file mode 100644 index fa40cdc..0000000 --- a/test_503_2.py +++ /dev/null @@ -1,61 +0,0 @@ -import urllib.request, time, hashlib, re, os, subprocess -from curl_cffi import requests - -def solve_challenge(html): - m = re.search(r"\['([A-F0-9]{40})'", html) - if not m: return None, None - c = m.group(1) - n1 = int(c[0], 16) - i = 0 - t0 = time.time() - while True: - d = hashlib.sha1((c + str(i)).encode()).digest() - if d[n1] == 0xb0 and d[n1+1] == 0x0b: break - i += 1 - return c + str(i), f"{time.time() - t0:.3f}" - -yaml_config = """ -mode: global -mixed-port: 10899 -bind-address: '127.0.0.1' -proxies: -""" -proxy_str = "" -with open("proxies.txt") as f: - for line in f: - if "ss," in line: - proxy_str = line.strip() - break -parts = proxy_str.split("=", 1) -cfg = parts[1].strip().split(",") -params = dict(p.strip().split("=", 1) for p in cfg[3:] if "=" in p) -yaml_config += f""" - name: "test" - type: ss - server: "{cfg[1].strip()}" - port: {cfg[2].strip()} - cipher: "{params.get('encrypt-method', '')}" - password: "{params.get('password', '')}" -""" -if "obfs" in params: - yaml_config += f" plugin: obfs\n plugin-opts:\n mode: {params['obfs']}\n host: {params['obfs-host']}\n" -with open("test_503.yaml", "w") as f: f.write(yaml_config) - -proc = subprocess.Popen(["./.venv/bin/mihomo", "-f", "test_503.yaml"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) -time.sleep(2) - -s = requests.Session() -p = {"http": "socks5://127.0.0.1:10899", "https": "socks5://127.0.0.1:10899"} -try: - r1 = s.get("https://zlib.li/", proxies=p, impersonate="chrome", timeout=10) - print("Initial:", r1.status_code) - - if r1.status_code == 503 and "Checking your browser" in r1.text: - ct, cti = solve_challenge(r1.text) - s.cookies.set("c_token", ct, domain="zlib.li", path="/") - s.cookies.set("c_time", cti, domain="zlib.li", path="/") - r1 = s.get("https://zlib.li/", proxies=p, impersonate="chrome", timeout=10) - print("After PoW:", r1.status_code) - if r1.status_code == 503: - print(r1.text[:500]) -finally: - proc.terminate() diff --git a/test_clash.yaml b/test_clash.yaml deleted file mode 100644 index a0fe9b4..0000000 --- a/test_clash.yaml +++ /dev/null @@ -1,13 +0,0 @@ -port: 7890 -socks-port: 7891 -proxies: - - name: "ss-obfs" - type: ss - server: "127.0.0.1" - port: 18300 - cipher: "aes-128-gcm" - password: "ced" - plugin: obfs - plugin-opts: - mode: http - host: Sm0lO349ch.microsoft.com diff --git a/test_cookie.py b/test_cookie.py deleted file mode 100644 index 95d803a..0000000 --- a/test_cookie.py +++ /dev/null @@ -1,6 +0,0 @@ -from curl_cffi import requests -s = requests.Session() -s.cookies.set("1", "A", domain="zlib.li", path="/") -s.cookies.set("2", "B", domain=".zlib.li", path="/") -s.get("http://httpbin.org/cookies", impersonate="chrome") -print(s.cookies) diff --git a/test_html.py b/test_html.py deleted file mode 100644 index c1847c8..0000000 --- a/test_html.py +++ /dev/null @@ -1,7 +0,0 @@ -from zlib_dl import ProxyManager, ZLibSession -mgr = ProxyManager("proxies.txt", verbose=True) -mgr.rotate() -s = ZLibSession(proxy_url=mgr.get_current_proxy_url(), verbose=True) -res, msg = s.download_file("/dl/22909249/a93325", "test.epub") -print(msg) -import os; os.path.exists("test.epub") and print("Saved epub size:", os.path.getsize("test.epub")) diff --git a/test_link.py b/test_link.py deleted file mode 100644 index 67bed91..0000000 --- a/test_link.py +++ /dev/null @@ -1,16 +0,0 @@ -from zlib_dl import ZLibSession -import re - -s1 = ZLibSession(verbose=True) -resp1 = s1._request_download("https://zlib.li/book/22909249/a93325") -match = re.search(r'href="(/dl/[^"]+)"', resp1.text) -if match: - print("Found exact link:", match.group(1)) -else: - print("Could not find /dl/ link in HTML!") - - lines = [line.strip() for line in resp1.text.split('\n')] - for i, line in enumerate(lines): - if "download" in line.lower() or "href" in line.lower(): - if "/22909249/" in line: - print(f"Possible DL Line [{i}]: {line[:300]}") diff --git a/test_mihomo.py b/test_mihomo.py deleted file mode 100644 index a1bfc37..0000000 --- a/test_mihomo.py +++ /dev/null @@ -1,22 +0,0 @@ -import urllib.request -import platform - -system = platform.system().lower() -machine = platform.machine().lower() -is_arm = machine in ("arm64", "aarch64") - -if system == "darwin": - arch_str = "arm64" if is_arm else "amd64-compatible" -elif system == "linux": - arch_str = "arm64" if is_arm else "amd64-compatible" - -version = "v1.19.21" -url = f"https://github.com/MetaCubeX/mihomo/releases/download/{version}/mihomo-{system}-{arch_str}-{version}.gz" -print("URL:", url) - -try: - req = urllib.request.Request(url, method='HEAD') - resp = urllib.request.urlopen(req, timeout=5) - print("Status:", resp.status) -except Exception as e: - print("Error:", e) diff --git a/test_mihomo_bounce.py b/test_mihomo_bounce.py deleted file mode 100644 index 15042c5..0000000 --- a/test_mihomo_bounce.py +++ /dev/null @@ -1,72 +0,0 @@ -import subprocess -import time -import os -import urllib.request -from curl_cffi import requests - -# We will read the first proxy from proxies.txt to really test it -proxy_str = "" -with open("proxies.txt") as f: - for line in f: - if "ss," in line: - proxy_str = line.strip() - break - -parts = proxy_str.split("=", 1) -name = parts[0].strip() -cfg = parts[1].strip().split(",") -host = cfg[1].strip() -port = cfg[2].strip() -params = {} -for p in cfg[3:]: - if "=" in p: - k, v = p.split("=", 1) - params[k.strip()] = v.strip() - -method = params.get("encrypt-method", "") -password = params.get("password", "") -obfs = params.get("obfs", "") -obfs_host = params.get("obfs-host", "") - -def test_mihomo(use_en0): - port_local = 11111 if use_en0 else 11112 - interface_cfg = "\n interface-name: en0" if use_en0 else "" - plugin_cfg = f""" - plugin: obfs - plugin-opts: - mode: {obfs} - host: {obfs_host} -""" if obfs else "" - - yaml_cfg = f""" -mode: global -mixed-port: {port_local} -bind-address: '127.0.0.1' -dns: - enable: true - nameserver: - - 223.5.5.5 -proxies: - - name: "test_proxy" - type: ss - server: "{host}" - port: {port} - cipher: "{method}" - password: "{password}"{interface_cfg}{plugin_cfg} -""" - with open(f"test_cfg_{port_local}.yaml", "w") as f: f.write(yaml_cfg) - proc = subprocess.Popen(["./.venv/bin/mihomo", "-f", f"test_cfg_{port_local}.yaml"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - time.sleep(2) - try: - prox = {"http": f"socks5://127.0.0.1:{port_local}", "https": f"socks5://127.0.0.1:{port_local}"} - resp = requests.get("http://httpbin.org/ip", proxies=prox, impersonate="chrome", timeout=5) - return resp.json()["origin"] - except Exception as e: - return f"Error: {e}" - finally: - proc.terminate() - proc.wait(timeout=2) - os.remove(f"test_cfg_{port_local}.yaml") - -print("Without en0:", test_mihomo(False)) -print("With en0:", test_mihomo(True)) diff --git a/test_mihomo_bypass.py b/test_mihomo_bypass.py deleted file mode 100644 index 36deead..0000000 --- a/test_mihomo_bypass.py +++ /dev/null @@ -1,46 +0,0 @@ -import subprocess -import time -import os -import json -import yaml - -# 1. 启动 mihomo 并绑定到具体网卡 (en0) -local_port = 10850 - -cfg = """ -mode: global -mixed-port: 10850 -bind-address: '127.0.0.1' - -dns: - enable: true - nameserver: - - https://223.5.5.5/dns-query - - https://1.1.1.1/dns-query - -proxies: - - name: "test-direct" - type: direct - interface-name: en0 -""" -with open("test_b.yaml", "w") as f: - f.write(cfg) - -# 找到 mihomo -mihomo = "./.venv/bin/mihomo" -if not os.path.exists(mihomo): - mihomo = "mihomo" - -proc = subprocess.Popen([mihomo, "-f", "test_b.yaml"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) -time.sleep(2) - -try: - from curl_cffi import requests - resp = requests.get("http://httpbin.org/ip", proxies={"http": "socks5://127.0.0.1:10850", "https": "socks5://127.0.0.1:10850"}, impersonate="chrome") - print("Mihomo (direct bound to en0) IP:", resp.json()["origin"]) - - resp_direct = requests.get("http://httpbin.org/ip", impersonate="chrome") - print("System (Surge default) IP:", resp_direct.json()["origin"]) -finally: - proc.kill() - os.remove("test_b.yaml") diff --git a/test_mihomo_fix.py b/test_mihomo_fix.py deleted file mode 100644 index a9233f3..0000000 --- a/test_mihomo_fix.py +++ /dev/null @@ -1,52 +0,0 @@ -import urllib.request, time, os, subprocess -from curl_cffi import requests - -yaml_config = """ -mode: rule -mixed-port: 10899 -bind-address: '127.0.0.1' -proxies: -""" -proxy_str = "" -with open("proxies.txt") as f: - for line in f: - if "ss," in line: - proxy_str = line.strip() - break -parts = proxy_str.split("=", 1) -cfg = parts[1].strip().split(",") -params = dict(p.strip().split("=", 1) for p in cfg[3:] if "=" in p) -yaml_config += f""" - name: "test" - type: ss - server: "{cfg[1].strip()}" - port: {cfg[2].strip()} - cipher: "{params.get('encrypt-method', '')}" - password: "{params.get('password', '')}" -""" -if "obfs" in params: - yaml_config += f" plugin: obfs\n plugin-opts:\n mode: {params['obfs']}\n host: {params['obfs-host']}\n" - -yaml_config += """ -proxy-groups: - - name: "Proxy" - type: select - proxies: - - "test" -rules: - - MATCH, Proxy -""" -with open("test_503.yaml", "w") as f: f.write(yaml_config) - -proc = subprocess.Popen(["./.venv/bin/mihomo", "-f", "test_503.yaml"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) -time.sleep(2) - -s = requests.Session() -p = {"http": "socks5://127.0.0.1:10899", "https": "socks5://127.0.0.1:10899"} -try: - ip_resp = s.get("http://httpbin.org/ip", proxies=p, impersonate="chrome", timeout=5) - print("Proxy IP:", ip_resp.json()["origin"]) - - r1 = s.get("https://zlib.li/", proxies=p, impersonate="chrome", timeout=10) - print("Index status:", r1.status_code) -finally: - proc.terminate() diff --git a/test_mihomo_gen.py b/test_mihomo_gen.py deleted file mode 100644 index e768364..0000000 --- a/test_mihomo_gen.py +++ /dev/null @@ -1,42 +0,0 @@ -import platform, os, gzip, urllib.request, json -import yaml - -def gen_config(proxy, local_port): - cfg = { - "port": 0, - "socks-port": local_port, - "mode": "Global", - "proxies": [{ - "name": proxy["name"], - "type": "ss", - "server": proxy["host"], - "port": proxy["port"], - "cipher": proxy["method"], - "password": proxy["password"], - "plugin": "obfs", - "plugin-opts": { - "mode": proxy["obfs"], - "host": proxy["obfs_host"] - } - }], - "proxy-groups": [{ - "name": "Proxy", - "type": "select", - "proxies": [proxy["name"]] - }], - "rules": [ - "MATCH,Proxy" - ] - } - return yaml.dump(cfg) - -proxy_test = { - "name": "HK 01", - "host": "d32b6cd.gylcxo.xyz", - "port": 18300, - "method": "aes-128-gcm", - "password": "ced", - "obfs": "http", - "obfs_host": "Sm0lO349ch.microsoft.com" -} -print(gen_config(proxy_test, 10800)) diff --git a/test_mihomo_ip.py b/test_mihomo_ip.py deleted file mode 100644 index b07b8c1..0000000 --- a/test_mihomo_ip.py +++ /dev/null @@ -1,19 +0,0 @@ -import urllib.request -import json - -from bs4 import BeautifulSoup - -def get_ip(proxy_url=None): - import subprocess - import sys - try: - from curl_cffi import requests - session = requests.Session() - proxies = {"http": proxy_url, "https": proxy_url} if proxy_url else None - resp = session.get("http://httpbin.org/ip", proxies=proxies, impersonate="chrome") - return resp.json()["origin"] - except Exception as e: - return f"Error: {e}" - -print("Direct IP:", get_ip()) - diff --git a/test_out.txt b/test_out.txt deleted file mode 100644 index 0841b47..0000000 --- a/test_out.txt +++ /dev/null @@ -1,3 +0,0 @@ - ⏳ 下载遇到 PoW 挑战,正在解决... - ✅ 挑战已解决 (耗时 0.003s,节点 zlib.li) -Could not find /dl/ link in HTML! diff --git a/test_search.py b/test_search.py deleted file mode 100644 index 0d8783a..0000000 --- a/test_search.py +++ /dev/null @@ -1,10 +0,0 @@ -from zlib_dl import ZLibSession -s = ZLibSession(verbose=True) -resp = s._request_download("https://zlib.li/s/%E6%B5%AA%E6%BD%AE%E4%B9%8B%E5%B7%85") -from bs4 import BeautifulSoup -soup = BeautifulSoup(resp.text, "html.parser") -cards = soup.find_all("z-bookcard") -if cards: - print(cards[0].prettify()) -else: - print("No cards found") diff --git a/test_us_clean.py b/test_us_clean.py deleted file mode 100644 index dc69e68..0000000 --- a/test_us_clean.py +++ /dev/null @@ -1,13 +0,0 @@ -from zlib_dl import ZLibSession -import re -url = "https://zlib.li/book/mDJxkg7yO6/%E6%B5%AA%E6%BD%AE%E4%B9%8B%E5%B7%85%E7%AC%AC%E5%9B%9B%E7%89%88.html" -s = ZLibSession(verbose=True, proxy_url="socks5://127.0.0.1:10800") -print("Requesting clean detail URL:", url) -resp = s._request_download(url) -print("FINAL URL:", resp.url) -print("HTML Title:", re.search(r'(.*?)', resp.text).group(1)) -match = re.search(r'href="(/dl/[^"]+)"', resp.text) -if match: - print("DOWNLOAD LINK FOUND:", match.group(1)) -else: - print("NO DOWNLOAD LINK FOUND") diff --git a/test_us_detail.py b/test_us_detail.py deleted file mode 100644 index 600cab2..0000000 --- a/test_us_detail.py +++ /dev/null @@ -1,21 +0,0 @@ -from zlib_dl import ZLibSession -s = ZLibSession(verbose=True, proxy_url="socks5://127.0.0.1:10800") -# Hit the detail page manually -resp = s._request_download("https://zlib.li/book/mDJxkg7yO6/%E6%B5%AA%E6%BD%AE%E4%B9%8B%E5%B7%85%E7%AC%AC%E5%9B%9B%E7%89%88.html") -print(f"STATUS CODE: {resp.status_code}") -print(f"FINAL URL: {resp.url}") -content = resp.text -print(f"CONTENT LENGTH: {len(content)}") -if '/dl/' in content: - print("FOUND /dl/ literally in content!") -else: - print("NO /dl/ FOUND!") - -import re -links = re.findall(r'href="(.*?)"', content) -print("Some links found on page:") -for l in [x for x in links if 'book' in x or 'dl' in x or 'download' in x][:10]: - print(" - ", l) - -if "daily limit" in content.lower() or "limit reached" in content.lower() or "已达下载" in content: - print("HIT RATE LIMIT MESSAGE ON DETAIL PAGE!") diff --git a/test_what_page_url_is.py b/test_what_page_url_is.py deleted file mode 100644 index 0b3c5ec..0000000 --- a/test_what_page_url_is.py +++ /dev/null @@ -1,9 +0,0 @@ -from zlib_dl import ZLibSession - -s = ZLibSession() -results = s.search("浪潮之巅") -if results: - for i, r in enumerate(results[:3]): - print(f"Item {i}:") - print(f" title: {r.title}") - print(f" page_url: {r.page_url}") diff --git a/zlib_dl.py b/zlib_dl.py index c6c4a44..16161c8 100644 --- a/zlib_dl.py +++ b/zlib_dl.py @@ -198,6 +198,39 @@ class ZLibSession: return {"https": self.proxy_url, "http": self.proxy_url} return None + def _debug_log_exit_ip(self, stage: str): + """在 verbose 模式下打印当前会话出口 IP,便于核对代理是否一致""" + if not self.verbose: + return + + proxies = self._get_proxies() + probe_targets = [ + ("http://httpbin.org/ip", lambda data: data.get("origin", "unknown")), + ("http://ip-api.com/json", lambda data: data.get("query", "unknown")), + ("https://api.ipify.org?format=json", lambda data: data.get("ip", "unknown")), + ] + + proxy_desc = self.proxy_url if self.proxy_url else "直连" + last_error = None + for probe_url, parser in probe_targets: + try: + resp = self.session.get( + probe_url, + impersonate="chrome", + proxies=proxies, + timeout=15, + ) + ip = parser(resp.json()) + print( + f" 🌐 [{stage}] 当前出口 IP: {ip} (proxy={proxy_desc}, probe={probe_url})", + file=sys.stderr, + ) + return + except Exception as e: + last_error = e + + print(f" 🌐 [{stage}] 当前出口 IP 获取失败: {last_error}", file=sys.stderr) + def _request(self, url: str, **kwargs) -> cffi_requests.Response: """发起请求,自动处理 PoW 挑战""" proxies = self._get_proxies() @@ -231,6 +264,7 @@ class ZLibSession: def search(self, query: str, max_results: int = 50) -> List[BookResult]: """搜索图书,返回结果列表""" + self._debug_log_exit_ip("search") url = f"{self.BASE_URL}/s/?q={query}" resp = self._request(url) @@ -271,8 +305,9 @@ class ZLibSession: def _request_download(self, url: str, **kwargs) -> cffi_requests.Response: """发起下载请求,自动处理 PoW 挑战(支持 allow_redirects)""" proxies = self._get_proxies() + timeout = kwargs.pop("timeout", (20, 90)) resp = self.session.get( - url, impersonate="chrome", proxies=proxies, timeout=300, + url, impersonate="chrome", proxies=proxies, timeout=timeout, allow_redirects=True, **kwargs ) @@ -303,7 +338,7 @@ class ZLibSession: # 无论什么页面,PoW 是在哪个域名的哪个具体 URL 上发生的,就全盘用带着 Cookie 的新状态重新向该域名发出最初的请求 resp = self.session.get( - resp.url, impersonate="chrome", proxies=proxies, timeout=300, + resp.url, impersonate="chrome", proxies=proxies, timeout=timeout, allow_redirects=True, **kwargs ) @@ -327,6 +362,7 @@ class ZLibSession: if detail_url: if self.verbose: print(f" 🔗 正在预热书籍详情页以注册当前代理指纹和获取新 Session...", file=sys.stderr) + self._debug_log_exit_ip("warmup") try: dt_resp = self._request_download(detail_url, headers={"Referer": self.BASE_URL + "/"}) import re @@ -348,7 +384,13 @@ class ZLibSession: return False, f"预热书籍详情页或解析出错: {e}" try: - resp = self._request_download(url, headers={"Referer": detail_url} if detail_url else {}) + self._debug_log_exit_ip("download") + resp = self._request_download( + url, + headers={"Referer": detail_url} if detail_url else {}, + stream=True, + timeout=(20, 120), + ) except Exception as e: return False, f"下载请求失败: {e}" @@ -376,12 +418,67 @@ class ZLibSession: else: return False, "下载失败,服务端依然返回了 HTML 重定向网页!" - # 写入文件 + # 写入文件(流式下载:只要持续有数据就继续) os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) - with open(output_path, "wb") as f: - f.write(resp.content) + tmp_path = output_path + ".part" + total_bytes = 0 - return True, f"下载完成: {len(resp.content)} bytes" + try: + content_length = resp.headers.get("Content-Length", "").strip() + expected_total = int(content_length) if content_length.isdigit() else 0 + + if self.verbose: + if expected_total > 0: + print( + f" 📦 服务器文件大小: {expected_total / (1024 * 1024):.2f} MB", + file=sys.stderr, + ) + else: + print(" 📦 服务器未返回文件大小(Content-Length)", file=sys.stderr) + + started_at = time.time() + last_log_at = started_at + + with open(tmp_path, "wb") as f: + for chunk in resp.iter_content(chunk_size=262144): + if not chunk: + continue + f.write(chunk) + total_bytes += len(chunk) + + now = time.time() + if self.verbose and now - last_log_at >= 3: + elapsed = max(0.001, now - started_at) + speed = total_bytes / elapsed + if expected_total > 0: + pct = (total_bytes / expected_total) * 100 + print( + f" ⏬ 已下载 {total_bytes / (1024 * 1024):.2f} / {expected_total / (1024 * 1024):.2f} MB ({pct:.1f}%), 速度 {speed / (1024 * 1024):.2f} MB/s", + file=sys.stderr, + ) + else: + print( + f" ⏬ 已下载 {total_bytes / (1024 * 1024):.2f} MB, 速度 {speed / (1024 * 1024):.2f} MB/s", + file=sys.stderr, + ) + last_log_at = now + + os.replace(tmp_path, output_path) + if expected_total > 0: + return True, f"下载完成: {total_bytes} bytes / 期望 {expected_total} bytes" + return True, f"下载完成: {total_bytes} bytes" + except Exception as e: + try: + if os.path.exists(tmp_path): + os.remove(tmp_path) + except Exception: + pass + return False, f"下载流中断: {e}" + finally: + try: + resp.close() + except Exception: + pass # ============================================================ @@ -589,6 +686,12 @@ proxies: port: {proxy.port} cipher: "{proxy.method}" password: "{proxy.password}"{plugin_yaml}{interface_yaml} + +proxy-groups: + - name: GLOBAL + type: select + proxies: + - "{proxy.name}" """ with open(cfg_path, 'w', encoding='utf-8') as f: f.write(yaml_config) @@ -679,6 +782,18 @@ proxies: proxy = self.proxies[self.current_index] return proxy.downloads_count >= self.MAX_DOWNLOADS_PER_PROXY + def has_proxy_pool(self) -> bool: + """是否配置了可轮换的代理池""" + return len(self.proxies) > 0 + + def ensure_active(self) -> bool: + """确保当前有活跃代理(有代理池时)""" + if not self.proxies: + return True + if 0 <= self.current_index < len(self.proxies): + return True + return self.rotate() + def rotate(self) -> bool: """ 轮换到下一个代理。 @@ -991,16 +1106,53 @@ def sanitize_filename(name: str) -> str: return name.strip()[:200] +def _should_rotate_proxy(message: str) -> bool: + """根据错误信息判断是否应该切换代理重试""" + msg_lower = message.lower() + return ( + "限" in message + or "limit" in msg_lower + or "429" in message + or "503" in message + or "504" in message + or "403" in message + or "502" in message + or "timeout" in msg_lower + or "请求失败" in message + or "html" in msg_lower + ) + + +def _create_session_for_current_proxy( + proxy_mgr: ProxyManager, + explicit_proxy: Optional[str], + verbose: bool, +) -> ZLibSession: + """根据当前代理状态创建新的 ZLibSession""" + proxy_url = explicit_proxy if explicit_proxy else proxy_mgr.get_current_proxy_url() + return ZLibSession(proxy_url=proxy_url, verbose=verbose) + + def cmd_download(args): """执行下载命令""" + explicit_proxy = getattr(args, "proxy", None) + proxy_file = getattr(args, "proxy_file", None) + if explicit_proxy and proxy_file: + print("错误: --proxy 与 --proxy-file 不能同时使用", file=sys.stderr) + sys.exit(1) + output_dir = Path(args.output_dir) output_dir.mkdir(parents=True, exist_ok=True) # 代理管理器 proxy_mgr = ProxyManager( - proxy_file=getattr(args, "proxy_file", None), verbose=args.verbose + proxy_file=proxy_file, verbose=args.verbose ) + if proxy_mgr.has_proxy_pool() and not proxy_mgr.ensure_active(): + print("错误: 代理池不可用,无法启动任何代理", file=sys.stderr) + sys.exit(1) + download_results: List[DownloadResult] = [] try: @@ -1014,11 +1166,13 @@ def cmd_download(args): for dl_path in dl_paths: result = _download_one( dl_path=dl_path, + page_url="", title=title, author=author, fmt=fmt or "", output_dir=output_dir, proxy_mgr=proxy_mgr, + explicit_proxy=explicit_proxy, verbose=args.verbose, ) download_results.append(result) @@ -1037,8 +1191,11 @@ def cmd_download(args): print(f" 🔍 搜索: {query_str}", file=sys.stderr) # 搜索 - proxy_url = proxy_mgr.get_current_proxy_url() - session = ZLibSession(proxy_url=proxy_url, verbose=args.verbose) + session = _create_session_for_current_proxy( + proxy_mgr=proxy_mgr, + explicit_proxy=explicit_proxy, + verbose=args.verbose, + ) results = session.search(query_str) if not results: @@ -1066,6 +1223,7 @@ def cmd_download(args): fmt=book.format, output_dir=output_dir, proxy_mgr=proxy_mgr, + explicit_proxy=explicit_proxy, verbose=args.verbose, ) download_results.append(result) @@ -1082,6 +1240,7 @@ def cmd_download(args): fmt=book.format, output_dir=output_dir, proxy_mgr=proxy_mgr, + explicit_proxy=explicit_proxy, verbose=args.verbose, ) download_results.append(result) @@ -1120,6 +1279,7 @@ def _download_one( fmt: str, output_dir: Path, proxy_mgr: ProxyManager, + explicit_proxy: Optional[str], verbose: bool, ) -> DownloadResult: """下载单个文件,带代理轮询逻辑""" @@ -1147,8 +1307,77 @@ def _download_one( message="文件已存在,跳过", ) - # 检查代理轮换 - if proxy_mgr.should_rotate(): + uses_proxy_pool = proxy_mgr.has_proxy_pool() + + # 进入下载前,若当前代理已达配额,先轮换 + if uses_proxy_pool and proxy_mgr.should_rotate() and not proxy_mgr.rotate(): + return DownloadResult( + status="error", + title=title, + author=author, + format=fmt, + filesize="", + filepath="", + message="所有代理已用完,且当前代理下载次数已达上限", + ) + + while True: + if uses_proxy_pool and not proxy_mgr.ensure_active(): + return DownloadResult( + status="error", + title=title, + author=author, + format=fmt, + filesize="", + filepath="", + message="所有代理已用完", + ) + + if uses_proxy_pool: + proxy_name = proxy_mgr.get_current_proxy_name() + elif explicit_proxy: + proxy_name = explicit_proxy + else: + proxy_name = "直连" + + if verbose: + print(f" ⬇️ 下载: {filename} (代理: {proxy_name})", file=sys.stderr) + + # 每次下载尝试都创建全新 Session;切代理后会自动带新出口重建会话 + session = _create_session_for_current_proxy( + proxy_mgr=proxy_mgr, + explicit_proxy=explicit_proxy, + verbose=verbose, + ) + success, message = session.download_file(dl_path, page_url, output_path) + + if success: + if uses_proxy_pool: + proxy_mgr.record_download() + filesize = os.path.getsize(output_path) + size_str = _format_size(filesize) + return DownloadResult( + status="success", + title=title, + author=author, + format=fmt, + filesize=size_str, + filepath=output_path, + message=f"下载成功 ({size_str})", + proxy_used=proxy_name, + ) + + if not uses_proxy_pool or not _should_rotate_proxy(message): + return DownloadResult( + status="error", + title=title, + author=author, + format=fmt, + filesize="", + filepath="", + message=message, + ) + if not proxy_mgr.rotate(): return DownloadResult( status="error", @@ -1157,64 +1386,14 @@ def _download_one( format=fmt, filesize="", filepath="", - message="所有代理已用完,且当前代理下载次数已达上限", + message=f"{message};且所有代理已用完", ) - proxy_url = proxy_mgr.get_current_proxy_url() - proxy_name = proxy_mgr.get_current_proxy_name() - - if verbose: - print( - f" ⬇️ 下载: {filename} (代理: {proxy_name})", file=sys.stderr - ) - - # 完全创建新 Session,废弃之前的旧 Session 以获取全新的 CDN 下载授权 - session = ZLibSession(proxy_url=proxy_url, verbose=verbose) - success, message = session.download_file(dl_path, page_url, output_path) - - if success: - proxy_mgr.record_download() - filesize = os.path.getsize(output_path) - size_str = format_size(filesize) - return DownloadResult( - status="success", - title=title, - author=author, - format=fmt, - filesize=size_str, - filepath=output_path, - message=f"下载成功 ({size_str})", - proxy_used=proxy_name, - ) - else: - # 如果是限制错误(达到配额)或遇到 503/403 等代理被屏蔽导致的无限验证循环,尝试切换代理重试 - msg_lower = message.lower() - should_retry = ( - "限" in message or "limit" in msg_lower or - "503" in message or "403" in message or "502" in message or - "timeout" in msg_lower or "请求失败" in message or "html" in msg_lower - ) - - if should_retry: - if proxy_mgr.rotate(): - if verbose: - print( - f" 🔄 下载受阻 ({message}),切换代理重试: {proxy_mgr.get_current_proxy_name()}", - file=sys.stderr, - ) - return _download_one( - dl_path, page_url, title, author, fmt, output_dir, proxy_mgr, verbose - ) - - return DownloadResult( - status="error", - title=title, - author=author, - format=fmt, - filesize="", - filepath="", - message=message, - ) + if verbose: + print( + f" 🔄 下载受阻 ({message}),切换代理重试: {proxy_mgr.get_current_proxy_name()}", + file=sys.stderr, + ) def _format_size(size_bytes: int) -> str: """格式化文件大小"""