From b1a55b76e58134a9ad4b33bbc9a6c1af44788bc3 Mon Sep 17 00:00:00 2001 From: SayItDitto <123747090+SayItDitto@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:13:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=99=A8=E8=87=AA=E5=8A=A8=E6=8E=A2=E6=B5=8B?= =?UTF-8?q?=20+=20Gazelle/NexusProject=20=E5=8F=98=E7=A7=8D=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=20(#6403)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/modules/indexer/__init__.py | 29 +++++++++++++-- app/modules/indexer/parser/gazelle.py | 21 +++++++++++ app/modules/indexer/parser/nexus_project.py | 40 ++++++++++++++++++++- 3 files changed, 87 insertions(+), 3 deletions(-) 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)