From 1f8dd65363001a2ca2c3fc89d863747e1ebf1857 Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Mon, 20 Apr 2026 09:31:30 +0800 Subject: [PATCH] feat: scaffold letu spider --- py/tests/test_乐兔.py | 35 +++++++++++++++++++++++++ py/乐兔.py | 60 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 py/tests/test_乐兔.py create mode 100644 py/乐兔.py diff --git a/py/tests/test_乐兔.py b/py/tests/test_乐兔.py new file mode 100644 index 0000000..49b229f --- /dev/null +++ b/py/tests/test_乐兔.py @@ -0,0 +1,35 @@ +import unittest +from importlib.machinery import SourceFileLoader +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +MODULE = SourceFileLoader("letu_spider", str(ROOT / "乐兔.py")).load_module() +Spider = MODULE.Spider + + +class TestLeTuSpider(unittest.TestCase): + def setUp(self): + Spider._instance = None + self.spider = Spider() + self.spider.init() + + def test_home_content_exposes_expected_categories(self): + content = self.spider.homeContent(False) + self.assertEqual( + [item["type_id"] for item in content["class"]], + ["1", "2", "3", "4", "5"], + ) + + def test_home_video_content_returns_empty_list(self): + self.assertEqual(self.spider.homeVideoContent(), {"list": []}) + + def test_encode_and_decode_detail_and_play_ids(self): + self.assertEqual(self.spider._encode_vod_id("/detail/demo.html"), "detail/demo") + self.assertEqual(self.spider._decode_vod_id("detail/demo"), "https://www.letu.me/detail/demo.html") + self.assertEqual(self.spider._encode_play_id("/play/123-1-1.html"), "play/123-1-1") + self.assertEqual(self.spider._decode_play_id("play/123-1-1"), "https://www.letu.me/play/123-1-1.html") + + +if __name__ == "__main__": + unittest.main() diff --git a/py/乐兔.py b/py/乐兔.py new file mode 100644 index 0000000..34f7135 --- /dev/null +++ b/py/乐兔.py @@ -0,0 +1,60 @@ +# coding=utf-8 +import re +import sys +from urllib.parse import urljoin + +from base.spider import Spider as BaseSpider + +sys.path.append("..") + + +class Spider(BaseSpider): + def __init__(self): + self.name = "乐兔" + self.host = "https://www.letu.me" + self.headers = { + "User-Agent": ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/144.0.0.0 Safari/537.36" + ), + "Referer": self.host + "/", + } + self.categories = [ + {"type_id": "1", "type_name": "电影"}, + {"type_id": "2", "type_name": "电视剧"}, + {"type_id": "3", "type_name": "综艺"}, + {"type_id": "4", "type_name": "动漫"}, + {"type_id": "5", "type_name": "短剧"}, + ] + + def init(self, extend=""): + return None + + def getName(self): + return self.name + + def homeContent(self, filter): + return {"class": self.categories} + + def homeVideoContent(self): + return {"list": []} + + def _build_url(self, path): + return urljoin(self.host + "/", str(path or "").strip()) + + def _encode_vod_id(self, href): + matched = re.search(r"/detail/([^/?#]+)\.html", self._build_url(href)) + return f"detail/{matched.group(1)}" if matched else "" + + def _decode_vod_id(self, vod_id): + matched = re.search(r"^detail/([^/?#]+)$", str(vod_id or "").strip()) + return self._build_url(f"/detail/{matched.group(1)}.html") if matched else "" + + def _encode_play_id(self, href): + matched = re.search(r"/play/([^/?#]+)\.html", self._build_url(href)) + return f"play/{matched.group(1)}" if matched else "" + + def _decode_play_id(self, play_id): + matched = re.search(r"^play/([^/?#]+)$", str(play_id or "").strip()) + return self._build_url(f"/play/{matched.group(1)}.html") if matched else ""