diff --git a/py/tests/test_茶杯狐.py b/py/tests/test_茶杯狐.py
index b5521ec..b95e238 100644
--- a/py/tests/test_茶杯狐.py
+++ b/py/tests/test_茶杯狐.py
@@ -48,3 +48,47 @@ class TestCupfoxSpider(unittest.TestCase):
def test_firewall_encrypt_returns_base64_text(self, _mock_randint):
encoded = self.spider._cupfox_firewall_encrypt("PX")
self.assertEqual(base64.b64decode(encoded).decode("utf-8"), "PwXh7w")
+
+ def test_request_with_firewall_retries_after_robot_verification(self):
+ calls = []
+
+ def fake_request(url, method="GET", body=None, headers=None):
+ calls.append({"url": url, "method": method, "body": body, "headers": headers or {}})
+ if len(calls) == 1:
+ return {
+ "status_code": 200,
+ "text": '
',
+ "headers": {"set-cookie": ["session=abc; Path=/"]},
+ }
+ if "robot.php" in url:
+ self.assertEqual(method, "POST")
+ self.assertIn("Cookie", headers)
+ self.assertIn("value=", body)
+ self.assertIn("token=", body)
+ return {
+ "status_code": 200,
+ "text": "ok",
+ "headers": {"set-cookie": ["shield=passed; Path=/"]},
+ }
+ return {
+ "status_code": 200,
+ "text": "ok",
+ "headers": {},
+ }
+
+ self.spider._request_text = fake_request
+ html = self.spider._request_with_firewall("https://www.cupfox.ai/search/test----------1---.html")
+
+ self.assertEqual(html, "ok")
+ self.assertEqual(len(calls), 3)
+ self.assertEqual(calls[2]["headers"]["Cookie"], "session=abc; shield=passed")
+
+ def test_extract_player_data_reads_embedded_json(self):
+ html = ''
+ data = self.spider._extract_player_data(html)
+ self.assertEqual(data["url"], "vid-1")
+ self.assertEqual(data["from"], "lineA")
+
+ def test_decode2_recovers_shifted_text(self):
+ encoded = "QXdCQ2tE"
+ self.assertEqual(self.spider._decode2(encoded), "P0")
diff --git a/py/茶杯狐.py b/py/茶杯狐.py
index beca930..da4831a 100644
--- a/py/茶杯狐.py
+++ b/py/茶杯狐.py
@@ -1,9 +1,10 @@
# coding=utf-8
import base64
+import json
import random
import re
import sys
-from urllib.parse import urljoin
+from urllib.parse import quote, urljoin
from base.spider import Spider as BaseSpider
@@ -80,3 +81,85 @@ class Spider(BaseSpider):
+ self.firewall_chars[random.randint(0, 61)]
)
return base64.b64encode(encoded.encode("utf-8")).decode("utf-8")
+
+ def _request_text(self, url, method="GET", body=None, headers=None):
+ request_headers = dict(self.headers)
+ if headers:
+ request_headers.update(headers)
+ if method == "POST":
+ response = self.post(url, data=body, headers=request_headers, timeout=15)
+ else:
+ response = self.fetch(url, headers=request_headers, timeout=15)
+ return {
+ "status_code": response.status_code,
+ "text": response.text or "",
+ "headers": dict(response.headers or {}),
+ }
+
+ def _request_with_firewall(self, url):
+ cookie_jar = {}
+ first = self._request_text(url)
+ self._merge_set_cookie(cookie_jar, first["headers"].get("set-cookie", []))
+ if not re.search(r"人机验证|verifyBox", first["text"] or ""):
+ if int(first["status_code"] or 0) != 200:
+ raise ValueError(f"HTTP {first['status_code']} @ {url}")
+ return first["text"]
+
+ token_raw = self._extract_firewall_token(first["text"])
+ if not token_raw:
+ return first["text"]
+
+ verify_body = (
+ "value="
+ + quote(self._cupfox_firewall_encrypt(url))
+ + "&token="
+ + quote(self._cupfox_firewall_encrypt(token_raw))
+ )
+ verify_headers = {
+ "Referer": url,
+ "Origin": self.host,
+ "Content-Type": "application/x-www-form-urlencoded",
+ }
+ cookie_text = self._cookie_header(cookie_jar)
+ if cookie_text:
+ verify_headers["Cookie"] = cookie_text
+ verify = self._request_text(
+ self.host + "/robot.php",
+ method="POST",
+ body=verify_body,
+ headers=verify_headers,
+ )
+ self._merge_set_cookie(cookie_jar, verify["headers"].get("set-cookie", []))
+
+ second_headers = {}
+ solved_cookie = self._cookie_header(cookie_jar)
+ if solved_cookie:
+ second_headers["Cookie"] = solved_cookie
+ second = self._request_text(url, headers=second_headers)
+ if int(second["status_code"] or 0) != 200:
+ raise ValueError(f"HTTP {second['status_code']} @ {url}")
+ return second["text"]
+
+ def _extract_player_data(self, html_text):
+ matched = re.search(r"player_aaaa\s*=\s*(\{[\s\S]*?\})\s*;?", str(html_text or ""))
+ if not matched:
+ return None
+ try:
+ return json.loads(matched.group(1))
+ except Exception:
+ return None
+
+ def _decode2(self, encoded):
+ if not encoded:
+ return ""
+ lookup = {}
+ for index, char in enumerate(self.firewall_chars):
+ lookup[char] = self.firewall_chars[(index + 59) % 62]
+ try:
+ raw = base64.b64decode(str(encoded).encode("utf-8")).decode("utf-8")
+ except Exception:
+ return ""
+ result = ""
+ for index in range(1, len(raw), 3):
+ result += lookup.get(raw[index], raw[index])
+ return result