\s*
![]()
]+data-original="([^"]+)"', html, re.S)
+ if cover_m:
+ cover = cover_m.group(1)
+
+ def get_info(label, use_em=True):
+ pat = rf'
{label}:([^<]+)' if use_em else rf'
{label}:([^<]+)'
+ m = re.search(pat, html)
+ return m.group(1).strip() if m else ""
+
+ vod_remarks = get_info("状态", True)
+ vod_year = get_info("年份", False)
+ vod_area = get_info("地区", False)
+ vod_type = get_info("类型", False)
+ vod_actor = get_info("主演", False)
+
+ vod_content = ""
+ desc_m = re.search(r'class="blurb"[^>]*>.*?
[^<]+(.*?)', html, re.S)
+ if desc_m:
+ vod_content = re.sub(r"<[^>]+>", "", desc_m.group(1)).strip()
+
+ play_data = self._parse_play_sources(html, raw_id)
+
+ return {
+ "vod_id": raw_id,
+ "vod_name": title,
+ "vod_pic": cover,
+ "vod_year": vod_year,
+ "vod_area": vod_area,
+ "vod_type": vod_type,
+ "vod_actor": vod_actor,
+ "vod_remarks": vod_remarks,
+ "vod_content": vod_content,
+ "vod_play_from": play_data["vod_play_from"],
+ "vod_play_url": play_data["vod_play_url"],
+ }
+
+ def _parse_play_sources(self, html, raw_id):
+ root = self.html(html)
+ if root is None:
+ return {"vod_play_from": "default", "vod_play_url": ""}
+ source_names = ["高清", "ikun", "非凡", "量子"]
+ # collect tab names in order
+ tabs = []
+ for idx, tab in enumerate(root.xpath("//*[contains(@class,'module-tab-item') or contains(@class,'tab-item')]")):
+ name = self._s(tab.xpath("string(.)")).strip()
+ if not name:
+ name = source_names[idx] if idx < len(source_names) else f"线路{idx+1}"
+ tabs.append(name)
+ # group episodes by source index from URL: /play/{vid}-{sourceIdx}-{epIdx}/
+ grouped = {}
+ all_links = root.xpath("//*[contains(@class,'module-play-list')]//a[@href] | //*[contains(@class,'playlist')]//a[@href]")
+ # fallback: try all links under .scroll-content or .module-list
+ if not all_links:
+ all_links = root.xpath("//*[contains(@class,'scroll-content') or contains(@class,'module-list')]//a[@href]")
+ for a in all_links:
+ ep_name = self._s(a.xpath("string(.)")).strip()
+ ep_url = self._s(a.get("href", ""))
+ if not ep_name or not ep_url or ep_url.startswith(("javascript:", "#")):
+ continue
+ m = re.search(r"/play/\d+-(\d+)-\d+/", ep_url)
+ if m:
+ src_idx = int(m.group(1)) - 1
+ else:
+ src_idx = 0
+ if src_idx not in grouped:
+ grouped[src_idx] = []
+ grouped[src_idx].append(f"{ep_name}${ep_url}")
+ if not grouped:
+ return {"vod_play_from": "default", "vod_play_url": ""}
+ # build output ordered by source index
+ play_from = []
+ play_url = []
+ for src_idx in sorted(grouped.keys()):
+ name = tabs[src_idx] if src_idx < len(tabs) else (source_names[src_idx] if src_idx < len(source_names) else f"线路{src_idx+1}")
+ play_from.append(name)
+ play_url.append("#".join(grouped[src_idx]))
+ return {
+ "vod_play_from": "$$$".join(play_from),
+ "vod_play_url": "$$$".join(play_url),
+ }
+
+ def _extract_play_url(self, html, fallback_url):
+ m = re.search(r"url:\s*'(https?://[^']+)'", html)
+ if m:
+ return m.group(1)
+ m = re.search(r"(https?://[^\s'\"<>]+\.m3u8(?:\?[^\s'\"<>]*)?)", html)
+ if m:
+ return m.group(1)
+ return fallback_url