Files
tvbox_collect/tvbox/py/fqdm.py
T
2026-07-31 02:57:44 +00:00

194 lines
7.3 KiB
Python

# -*- coding: utf-8 -*-
# TVBox Python Spider for https://www.fqdm.cc
# 适用于 FongMi 版本
import requests
import re
import json
from urllib.parse import urljoin, quote
class Spider:
def __init__(self):
self.siteUrl = "https://www.fqdm.cc"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": self.siteUrl
}
# ---------- 必需接口 ----------
def getDependence(self):
return ""
def init(self, ext=""):
pass
def homeContent(self, filter=None):
categories = self._getCategories()
html = self._fetch(self.siteUrl)
videos = self._parseVideoList(html)
return json.dumps({"class": categories, "list": videos}, ensure_ascii=False)
def homeVideoContent(self, filter=None):
html = self._fetch(self.siteUrl)
videos = self._parseVideoList(html)
return json.dumps({"list": videos}, ensure_ascii=False)
def categoryContent(self, tid, pg, filter=False, extend=""):
url = f"{self.siteUrl}/vodshow/{tid}--------{pg}---/"
html = self._fetch(url)
videos = self._parseVideoList(html)
pagecount = self._getPageCount(html) or 999
return json.dumps({
"page": pg,
"pagecount": pagecount,
"limit": 24,
"total": pagecount * 24,
"list": videos
}, ensure_ascii=False)
def detailContent(self, ids):
vid = ids[0]
html = self._fetch(f"{self.siteUrl}/voddetail/{vid}.html")
name = self._extract(html, r'<h2[^>]*>([^<]+)</h2>')
pic = self._extract(html, r'data-original="([^"]*)"')
if not pic:
pic = self._extract(html, r'<img[^>]*src="([^"]*)"')
if pic:
pic = urljoin(self.siteUrl, pic)
desc = self._extract(html, r'class="content"[^>]*>([\s\S]*?)</div>')
if not desc:
desc = self._extract(html, r'class="info"[^>]*>([\s\S]*?)</div>')
play_url = self._parsePlayList(html)
vod = {
"vod_id": vid,
"vod_name": name.strip() if name else "",
"vod_pic": pic or "",
"vod_content": desc.strip() if desc else "",
"vod_play_from": "fqdm",
"vod_play_url": play_url
}
return json.dumps({"list": [vod]}, ensure_ascii=False)
def searchContent(self, key, quick=False):
url = f"{self.siteUrl}/vodsearch/-------------.html?wd={quote(key)}"
html = self._fetch(url)
videos = self._parseVideoList(html)
return json.dumps({"list": videos}, ensure_ascii=False)
def playerContent(self, flag, ids, vipFlags=None):
if not ids.startswith("http"):
playUrl = urljoin(self.siteUrl, ids)
else:
playUrl = ids
html = self._fetch(playUrl)
real_url = ""
# 尝试匹配苹果 CMS 播放器对象
player_json = self._extract(html, r'player_\w+\s*=\s*({[\s\S]*?});')
if player_json:
try:
data = json.loads(player_json)
real_url = data.get("url") or data.get("url_next", "")
except:
pass
if not real_url:
real_url = self._extract(html, r'<video[^>]*src="([^"]*)"')
if not real_url:
iframe_src = self._extract(html, r'<iframe[^>]*src="([^"]*)"')
if iframe_src:
iframe_url = iframe_src if iframe_src.startswith("http") else urljoin(self.siteUrl, iframe_src)
return self.playerContent(flag, iframe_url)
if real_url and not real_url.startswith("http"):
real_url = urljoin(self.siteUrl, real_url)
return json.dumps({"url": real_url}, ensure_ascii=False)
# ---------- 辅助方法 ----------
def _fetch(self, url):
resp = requests.get(url, headers=self.headers, timeout=15)
resp.encoding = "utf-8"
return resp.text
def _getCategories(self):
html = self._fetch(self.siteUrl)
categories = []
seen = set()
# 标准格式:<a href="/vodshow/1--------/...">动漫</a>
for cid, name in re.findall(r'<a[^>]*href="/vodshow/(\d+)[^"]*"[^>]*>([^<]+)</a>', html):
if cid not in seen:
seen.add(cid)
categories.append({"type_id": cid, "type_name": name.strip()})
if not categories:
# 松散匹配
for cid, name in re.findall(r'href="/vodshow/(\d+)[^"]*"[^>]*>([^<]+)<', html):
if cid not in seen:
seen.add(cid)
categories.append({"type_id": cid, "type_name": name.strip()})
if not categories:
categories = [{"type_id": "1", "type_name": "动漫"}]
return categories
def _parseVideoList(self, html):
# 找到所有包含 voddetail/数字.html 的 <a> 标签块
a_blocks = re.findall(r'<a[^>]*href="/voddetail/(\d+)\.html"[^>]*>([\s\S]*?)</a>', html)
if not a_blocks:
# 备用:可能结构较乱,只匹配至下一个 <a 或换行
a_blocks = re.findall(r'<a[^>]*href="/voddetail/(\d+)\.html"[^>]*>(.*?)</a>', html, re.DOTALL)
videos = []
for vid, content in a_blocks:
# 标题
title = ""
title_match = re.search(r'title="([^"]*)"', content)
if title_match:
title = title_match.group(1).strip()
else:
# 去除 HTML 标签取纯文本
text = re.sub(r'<[^>]+>', '', content).strip()
if text:
title = text
# 图片
pic_url = ""
pic_match = re.search(r'data-original="([^"]*)"', content)
if not pic_match:
pic_match = re.search(r'<img[^>]*src="([^"]*)"', content)
if pic_match:
pic_url = pic_match.group(1)
if not pic_url.startswith(("http://", "https://")):
pic_url = urljoin(self.siteUrl, pic_url)
if pic_url:
videos.append({
"vod_id": vid,
"vod_name": title,
"vod_pic": pic_url,
"vod_remarks": ""
})
else:
# 即使没有图片也加入,避免漏掉
videos.append({
"vod_id": vid,
"vod_name": title,
"vod_pic": "",
"vod_remarks": ""
})
return videos
def _parsePlayList(self, html):
items = re.findall(r'<a[^>]*href="(/vodplay/[^"]+)"[^>]*>([^<]+)</a>', html)
if not items:
items = re.findall(r'href="(/vodplay/[^"]+)"[^>]*>([^<]+)<', html)
episodes = []
for path, name in items:
full_url = urljoin(self.siteUrl, path)
episodes.append(f"{name.strip()}${full_url}")
return "#".join(episodes)
def _getPageCount(self, html):
import math
total_match = re.search(r'共(\d+)条', html)
if total_match:
total = int(total_match.group(1))
return math.ceil(total / 24)
return None
@staticmethod
def _extract(html, pattern):
m = re.search(pattern, html, re.DOTALL)
return m.group(1).strip() if m else ""