From c87f96d417203943a4738e03dcb4ee6a249c9065 Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Thu, 23 Apr 2026 21:08:58 +0800 Subject: [PATCH] feat: add tengxun category and batch helper --- py/tests/test_腾讯视频.py | 49 ++++++++++++++++++++++++++ py/腾讯视频.py | 72 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/py/tests/test_腾讯视频.py b/py/tests/test_腾讯视频.py index bbc7a2e..9d9b85c 100644 --- a/py/tests/test_腾讯视频.py +++ b/py/tests/test_腾讯视频.py @@ -21,6 +21,13 @@ HOME_HTML = """ """ +CATEGORY_HTML = """ +
+ 三体 + 更新至30集 +
+""" + class TestTencentSpider(unittest.TestCase): def setUp(self): @@ -71,6 +78,48 @@ class TestTencentSpider(unittest.TestCase): }, ) + @patch.object(Spider, "fetch") + def test_category_content_maps_filters_and_prefixes_channel(self, mock_fetch): + mock_fetch.return_value = SimpleNamespace(text=CATEGORY_HTML) + result = self.spider.categoryContent( + "tv", + "2", + False, + { + "sort": "18", + "iyear": "2024", + "year": "2024", + "type": "17", + "feature": "4", + "area": "2", + "itrailer": "1", + "sex": "1", + }, + ) + request_url = mock_fetch.call_args.args[0] + self.assertIn("channel=tv", request_url) + self.assertIn("offset=21", request_url) + self.assertIn("sort=18", request_url) + self.assertIn("iyear=2024", request_url) + self.assertIn("year=2024", request_url) + self.assertIn("itype=17", request_url) + self.assertIn("ifeature=4", request_url) + self.assertIn("iarea=2", request_url) + self.assertIn("itrailer=1", request_url) + self.assertIn("gender=1", request_url) + self.assertEqual(result["list"][0]["vod_id"], "tv$/x/cover/cid001/vid001.html") + self.assertEqual(result["page"], 2) + self.assertEqual(result["limit"], 21) + self.assertEqual(result["pagecount"], 9999) + + @patch.object(Spider, "fetch") + def test_get_batch_video_info_parses_qzoutputjson_payload(self, mock_fetch): + mock_fetch.return_value = SimpleNamespace( + text='QZOutputJson={"results":[{"fields":{"vid":"vid001","title":"第1集","category_map":["0","正片"]}}]};' + ) + result = self.spider._get_batch_video_info(["vid001"]) + self.assertEqual(result, [{"vid": "vid001", "title": "第1集", "type": "正片"}]) + if __name__ == "__main__": unittest.main() diff --git a/py/腾讯视频.py b/py/腾讯视频.py index b6b56d2..b0a981d 100644 --- a/py/腾讯视频.py +++ b/py/腾讯视频.py @@ -1,4 +1,5 @@ # coding=utf-8 +import json import re import sys @@ -74,3 +75,74 @@ class Spider(BaseSpider): def playerContent(self, flag, id, vipFlags): return {"parse": 1, "jx": 1, "url": id, "header": self._headers()} + + def _safe_json(self, text, strip_prefix="", strip_suffix=""): + raw = str(text or "") + if strip_prefix and raw.startswith(strip_prefix): + raw = raw[len(strip_prefix):] + if strip_suffix and raw.endswith(strip_suffix): + raw = raw[: -len(strip_suffix)] + return json.loads(raw) if raw else {} + + def _get_batch_video_info(self, vids): + results = [] + for start in range(0, len(vids or []), 30): + batch = vids[start:start + 30] + if not batch: + continue + url = ( + "https://union.video.qq.com/fcgi-bin/data?otype=json&tid=1804&appid=20001238" + "&appkey=6c03bbe9658448a4&union_platform=1&idlist=" + ",".join(batch) + ) + response = self.fetch(url, headers=self._headers()) + payload = self._safe_json(getattr(response, "text", ""), strip_prefix="QZOutputJson=", strip_suffix=";") + for item in payload.get("results", []): + fields = item.get("fields", {}) + category_map = fields.get("category_map", []) + results.append( + { + "vid": fields.get("vid", ""), + "title": fields.get("title", ""), + "type": category_map[1] if len(category_map) > 1 else "", + } + ) + return results + + def categoryContent(self, tid, pg, filter, extend): + page = int(pg) + offset = (page - 1) * 21 + params = [ + "_all=1", + "append=1", + f"channel={tid}", + "listpage=1", + f"offset={offset}", + "pagesize=21", + "iarea=-1", + ] + options = dict(extend or {}) + if options.get("sort"): + params.append(f"sort={options['sort']}") + if options.get("iyear"): + params.append(f"iyear={options['iyear']}") + if options.get("year"): + params.append(f"year={options['year']}") + if options.get("type"): + params.append(f"itype={options['type']}") + if options.get("feature"): + params.append(f"ifeature={options['feature']}") + if options.get("area"): + params.append(f"iarea={options['area']}") + if options.get("itrailer"): + params.append(f"itrailer={options['itrailer']}") + if options.get("sex"): + params.append(f"gender={options['sex']}") + url = f"{self.base_host}/x/bu/pagesheet/list?" + "&".join(params) + response = self.fetch(url, headers=self._headers()) + return { + "list": self._parse_list_items(getattr(response, "text", ""), with_channel=True, channel_id=str(tid)), + "page": page, + "pagecount": 9999, + "limit": 21, + "total": 999999, + }