diff --git a/app/modules/indexer/__init__.py b/app/modules/indexer/__init__.py
index 1841a4edc..8e8846b86 100644
--- a/app/modules/indexer/__init__.py
+++ b/app/modules/indexer/__init__.py
@@ -634,12 +634,14 @@ class IndexerModule(_ModuleBase):
:return: 用户数据
"""
- def __get_site_obj() -> Optional[SiteParserBase]:
+ def __get_site_obj(schema_value: Optional[str] = None) -> Optional[SiteParserBase]:
"""
获取站点解析器
+ :param schema_value: 指定 schema, 默认取站点声明的 schema
"""
+ schema_value = schema_value or site.get("schema")
for site_schema in self._site_schemas:
- if site_schema.schema and site_schema.schema.value == site.get("schema"):
+ if site_schema.schema and site_schema.schema.value == schema_value:
return site_schema(
site_name=site.get("name"),
url=site.get("url"),
@@ -651,6 +653,7 @@ class IndexerModule(_ModuleBase):
api_url=site.get("api_url"))
return None
+ # 按站点声明的 schema 获取解析器
site_obj = __get_site_obj()
if not site_obj:
if not site.get("public"):
@@ -662,6 +665,28 @@ class IndexerModule(_ModuleBase):
logger.info(f"站点 {site.get('name')} 开始以 {site.get('schema')} 模型解析数据...")
site_obj.parse()
logger.debug(f"站点 {site.get('name')} 数据解析完成")
+ # 站点声明的 schema 解析失败(userid 为空)时, 自动尝试其他解析器,
+ # 兼容资源文件 schema 标注错误/变种站点的场景
+ if not site_obj.userid and not site.get("public"):
+ tried = {site.get("schema")}
+ for site_schema in self._site_schemas:
+ if not site_schema.schema or site_schema.schema.value in tried:
+ continue
+ tried.add(site_schema.schema.value)
+ logger.info(f"站点 {site.get('name')} schema {site.get('schema')} 解析失败, "
+ f"尝试 {site_schema.schema.value} 模型...")
+ alt_obj = __get_site_obj(site_schema.schema.value)
+ if not alt_obj:
+ continue
+ try:
+ alt_obj.parse()
+ except Exception as e:
+ logger.error(f"站点 {site.get('name')} 以 {site_schema.schema.value} 解析失败: {str(e)}")
+ continue
+ if alt_obj.userid:
+ site_obj = alt_obj
+ logger.info(f"站点 {site.get('name')} 改用 {site_schema.schema.value} 模型解析成功")
+ break
return SiteUserData(
domain=site_rules.extract_domain(site.get("url")),
userid=site_obj.userid,
diff --git a/app/modules/indexer/parser/gazelle.py b/app/modules/indexer/parser/gazelle.py
index b8440002d..b6833db11 100644
--- a/app/modules/indexer/parser/gazelle.py
+++ b/app/modules/indexer/parser/gazelle.py
@@ -93,6 +93,27 @@ class GazelleSiteUserInfo(SiteParserBase):
'//div[contains(@class, "box_userinfo_stats")]//li[contains(text(), "加入时间")]/span/text()')
if join_at_text:
self.join_at = time_tools.normalize_datetime(join_at_text[0].strip())
+
+ # 兼容部分 Gazelle 站点(如 JPopsuki)以文本形式展示上传/下载:
+ #
Uploaded: 77.44 GB / Downloaded: 8.51 GB
+ if not self.upload:
+ upload_text = html.xpath(
+ '//li[starts-with(normalize-space(text()), "Uploaded:")]')
+ if upload_text:
+ size_match = re.search(
+ r"([\d.,]+\s*[GMKT]?i?B)", upload_text[0].xpath("string(.)"), re.I)
+ if size_match:
+ self.upload = size_tools.parse_size(size_match.group(1))
+ if not self.download:
+ download_text = html.xpath(
+ '//li[starts-with(normalize-space(text()), "Downloaded:")]')
+ if download_text:
+ size_match = re.search(
+ r"([\d.,]+\s*[GMKT]?i?B)", download_text[0].xpath("string(.)"), re.I)
+ if size_match:
+ self.download = size_tools.parse_size(size_match.group(1))
+ if not self.ratio and self.upload and self.download:
+ self.ratio = round(self.upload / self.download, 3)
finally:
if html is not None:
del html
diff --git a/app/modules/indexer/parser/nexus_project.py b/app/modules/indexer/parser/nexus_project.py
index 1c854d6bc..1920c3024 100644
--- a/app/modules/indexer/parser/nexus_project.py
+++ b/app/modules/indexer/parser/nexus_project.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
import re
+from lxml import etree
+
from app.modules.indexer.parser import SiteSchema
from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo
@@ -11,9 +13,45 @@ class NexusProjectSiteUserInfo(NexusPhpSiteUserInfo):
def _parse_site_page(self, html_text: str):
html_text = self._prepare_html_text(html_text)
- user_detail = re.search(r"userdetails.php\?id=(\d+)", html_text)
+ user_detail = re.search(r"userdetails\.php\?id=(\d+)", html_text)
if user_detail and user_detail.group().strip():
self._user_detail_page = user_detail.group().strip().lstrip('/')
self.userid = user_detail.group(1)
+ else:
+ # 兼容部分 NexusProject 变种站点(如 star-space.net)的
+ # p_user/user_detail.php?uid= 用户定位格式
+ user_detail = re.search(r"user_detail\.php\?uid=(\d+)", html_text)
+ if user_detail and user_detail.group().strip():
+ self._user_detail_page = user_detail.group().strip().lstrip('/')
+ self.userid = user_detail.group(1)
+ uname = re.search(
+ r"user_detail\.php\?uid=\d+[^>]*?>\s*<[^>]*>([^<]+)<", html_text)
+ if uname:
+ self.username = uname.group(1).strip()
self._torrent_seeding_page = f"viewusertorrents.php?id={self.userid}&show=seeding"
+
+ def _parse_user_traffic_info(self, html_text):
+ # 兼容部分 NexusProject 变种站点(如 star-space.net)以
+ # span#user_info / span#user_info_no_hover 文本展示流量:
+ # "上传:445.11 G" / "下载:29.61 G"(单位无 B 后缀)
+ try:
+ html = etree.HTML(html_text)
+ if html is not None:
+ body_text = " ".join(html.xpath(
+ '//span[@id="user_info"]//text() | //span[@id="user_info_no_hover"]//text()'))
+ size_match = re.search(r"上传[::]\s*([\d.,]+)\s*([GMKT]?i?B?)", body_text, re.I)
+ if size_match:
+ self.upload = self.num_filesize(
+ f"{size_match.group(1).replace(',', '')} {size_match.group(2).upper()}B")
+ size_match = re.search(r"下载[::]\s*([\d.,]+)\s*([GMKT]?i?B?)", body_text, re.I)
+ if size_match:
+ self.download = self.num_filesize(
+ f"{size_match.group(1).replace(',', '')} {size_match.group(2).upper()}B")
+ if self.upload and self.download:
+ self.ratio = round(self.upload / self.download, 3)
+ if self.upload or self.download:
+ return
+ except Exception:
+ pass
+ super()._parse_user_traffic_info(html_text)