mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-02 21:34:32 +08:00
fix(indexer): 修复 NexusPHP 用户流量详情表格兜底解析 (#7008)
This commit is contained in:
@@ -98,9 +98,9 @@ class SiteParserBase(metaclass=ABCMeta):
|
||||
self.join_at = None
|
||||
self.bonus = 0.0
|
||||
|
||||
# 流量信息
|
||||
self.upload = 0.0
|
||||
self.download = 0.0
|
||||
# 上传/下载量按整数字节计,分享率为浮点数
|
||||
self.upload = 0
|
||||
self.download = 0
|
||||
self.ratio = 0.0
|
||||
|
||||
# 做种信息
|
||||
|
||||
@@ -650,9 +650,42 @@ class NexusPhpSiteUserInfo(SiteParserBase):
|
||||
return message_head_text, message_date_text, message_content_text
|
||||
|
||||
def _fixup_traffic_info(self, html):
|
||||
"""从详情页补充首页缺失的魔力值。"""
|
||||
"""从详情表格补读为零的流量、分享率和魔力值,非零值保持不变。"""
|
||||
if not self.upload:
|
||||
upload_text = self._traffic_table_value(html, r"上[传傳]量?")
|
||||
if upload_text is not None:
|
||||
self.upload = self.num_filesize(upload_text)
|
||||
if not self.download:
|
||||
download_text = self._traffic_table_value(html, r"下[载載]量?")
|
||||
if download_text is not None:
|
||||
self.download = self.num_filesize(download_text)
|
||||
if not self.ratio:
|
||||
ratio_text = self._traffic_table_value(html, "分享率")
|
||||
if ratio_text is not None:
|
||||
ratio = text_tools.parse_float(ratio_text)
|
||||
else:
|
||||
ratio = round(self.upload / self.download, 3) if self.download else 0.0
|
||||
self.ratio = ratio
|
||||
|
||||
# fixup bonus
|
||||
if not self.bonus:
|
||||
bonus_text = html.xpath('//tr/td[text()="魔力值" or text()="猫粮"]/following-sibling::td[1]/text()')
|
||||
if bonus_text:
|
||||
self.bonus = text_tools.parse_float(bonus_text[0].strip())
|
||||
|
||||
@staticmethod
|
||||
def _traffic_table_value(html: Any, field: str) -> Optional[str]:
|
||||
"""按单元格字段名读取数值,支持同格或相邻列布局,排除“实际”流量。"""
|
||||
for cell in html.xpath('//tr/td[not(.//td)]'):
|
||||
cell_text = " ".join(cell.xpath("string(.)").split())
|
||||
field_match = re.fullmatch(rf"{field}\s*[::]?\s*(.*)", cell_text)
|
||||
if not field_match:
|
||||
continue
|
||||
value = field_match.group(1).strip()
|
||||
if not value:
|
||||
siblings = cell.xpath('following-sibling::td[1]')
|
||||
value = " ".join(siblings[0].xpath("string(.)").split()) if siblings else ""
|
||||
value_match = re.fullmatch(r"([\d,.]+(?:\s*[KMGTPI]i?B)?)(?:\s*[((].*)?", value, re.I)
|
||||
if value_match:
|
||||
return value_match.group(1)
|
||||
return None
|
||||
|
||||
47
tests/fixtures/depthstudio_userdetails.html
vendored
Normal file
47
tests/fixtures/depthstudio_userdetails.html
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<!-- 从用户提供的详情页摘取流量结构,省略身份、链接和其他个人信息。 -->
|
||||
<div class="info-card-section info-card-stats info-card-stats-count-6">
|
||||
<span class="info-stat info-stat-primary info-stat-ratio">
|
||||
<span class="info-stat-copy">
|
||||
<span class="info-stat-label color_ratio">分享率</span>
|
||||
<span class="info-stat-value">4.393</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="info-stat info-stat-primary info-stat-uploaded">
|
||||
<span class="info-stat-copy">
|
||||
<span class="info-stat-label color_uploaded">上传量</span>
|
||||
<span class="info-stat-value">
|
||||
323.04 <span class="info-stat-unit">GB</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="info-stat info-stat-primary info-stat-downloaded">
|
||||
<span class="info-stat-copy">
|
||||
<span class="info-stat-label color_downloaded">下载量</span>
|
||||
<span class="info-stat-value">
|
||||
73.54 <span class="info-stat-unit">GB</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td class="embedded">
|
||||
<strong>分享率</strong>: <font color="">4.392</font>
|
||||
(<strong>实际分享率</strong>:0.045)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="embedded"><strong>上传量</strong>: 323.04 GB</td>
|
||||
<td class="embedded"> <strong>下载量</strong>: 73.54 GB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="embedded"><strong>实际上传量</strong>: 317.04 GB</td>
|
||||
<td class="embedded"> <strong>实际下载量</strong>: 6.838 TB</td>
|
||||
<td class="embedded text-muted">实际上传/下载量 (仅用于记录, 不参与分享率计算)</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -19,10 +19,13 @@ def parser() -> Iterator[DiscuzUserInfo]:
|
||||
instance.close()
|
||||
|
||||
|
||||
def test_discuz_traffic_defaults_are_floats(parser: DiscuzUserInfo) -> None:
|
||||
for value in (parser.upload, parser.download, parser.ratio):
|
||||
assert value == 0.0
|
||||
assert isinstance(value, float)
|
||||
def test_discuz_traffic_defaults_use_integer_bytes(parser: DiscuzUserInfo) -> None:
|
||||
"""上传/下载量使用整数字节,分享率保持浮点数。"""
|
||||
for value in (parser.upload, parser.download):
|
||||
assert value == 0
|
||||
assert type(value) is int
|
||||
assert parser.ratio == 0.0
|
||||
assert isinstance(parser.ratio, float)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("include_menu", [True, False])
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from pathlib import Path
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
|
||||
import pytest
|
||||
from lxml import etree
|
||||
|
||||
from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo
|
||||
@@ -19,6 +21,121 @@ def _build_parser() -> NexusPhpSiteUserInfo:
|
||||
)
|
||||
|
||||
|
||||
def test_nexus_php_depthstudio_traffic_falls_back_to_details():
|
||||
"""首页解析失败后从详情表格补齐流量,不依赖卡片类名或实际流量。"""
|
||||
html_text = (Path(__file__).parent / "fixtures" / "depthstudio_userdetails.html").read_text(encoding="utf-8")
|
||||
html = etree.HTML(html_text)
|
||||
homepage = etree.tostring(html.xpath("//div")[0], encoding="unicode")
|
||||
homepage = homepage.replace("info-stat", "other-style")
|
||||
parser = _build_parser()
|
||||
|
||||
parser._parse_user_base_info(homepage)
|
||||
|
||||
assert parser.upload == 0
|
||||
assert parser.download == 0
|
||||
|
||||
parser._parse_user_detail_info(html_text)
|
||||
|
||||
assert parser.upload == round(323.04 * 1024 ** 3)
|
||||
assert parser.download == round(73.54 * 1024 ** 3)
|
||||
assert parser.ratio == 4.393
|
||||
|
||||
|
||||
def test_nexus_php_detail_traffic_preserves_nonzero_values():
|
||||
"""详情兜底不能覆盖已有的非零流量和分享率。"""
|
||||
parser = _build_parser()
|
||||
parser._parse_user_base_info(
|
||||
"<html><body>上传量: 1 GB 下载量: 2 GB 分享率: 0.5</body></html>"
|
||||
)
|
||||
original = parser.upload, parser.download, parser.ratio
|
||||
|
||||
parser._parse_user_detail_info(
|
||||
'<table><tr><td><strong>上传量</strong>: 323.04 GB</td>'
|
||||
'<td><strong>下载量</strong>: 73.54 GB</td></tr>'
|
||||
'<tr><td><strong>分享率</strong>: 4.392</td></tr></table>'
|
||||
)
|
||||
|
||||
assert (parser.upload, parser.download, parser.ratio) == original
|
||||
|
||||
|
||||
@pytest.mark.parametrize("homepage, expected_upload, expected_download, expected_ratio", [
|
||||
("上传量: 1 GB", 1024 ** 3, round(73.54 * 1024 ** 3), 4.392),
|
||||
("下载量: 0 B 分享率: 0", round(323.04 * 1024 ** 3), round(73.54 * 1024 ** 3), 4.392),
|
||||
("上传量: 0 B 下载量: 0 B 分享率: 0", round(323.04 * 1024 ** 3), round(73.54 * 1024 ** 3), 4.392),
|
||||
("上传量: 1 GB 下载量: 2 GB 分享率: 0", 1024 ** 3, 2 * 1024 ** 3, 4.392),
|
||||
])
|
||||
def test_nexus_php_detail_traffic_fills_zero_fields(
|
||||
homepage, expected_upload, expected_download, expected_ratio
|
||||
):
|
||||
"""零值字段允许从详情补读,分享率优先取详情表格的显示值。"""
|
||||
parser = _build_parser()
|
||||
parser._parse_user_base_info(f"<html><body>{homepage}</body></html>")
|
||||
detail = (Path(__file__).parent / "fixtures" / "depthstudio_userdetails.html").read_text(encoding="utf-8")
|
||||
|
||||
parser._parse_user_detail_info(detail)
|
||||
|
||||
assert parser.upload == expected_upload
|
||||
assert parser.download == expected_download
|
||||
assert parser.ratio == expected_ratio
|
||||
|
||||
|
||||
def test_nexus_php_zero_ratio_uses_final_traffic_without_displayed_ratio():
|
||||
"""详情未显示分享率时,按补读后的上传/下载量重新计算零分享率。"""
|
||||
parser = _build_parser()
|
||||
parser._parse_user_base_info("<html><body>上传量: 0 B 下载量: 0 B 分享率: 0</body></html>")
|
||||
|
||||
parser._parse_user_detail_info(
|
||||
'<table><tr><td><strong>上传量</strong>: 8 GB</td>'
|
||||
'<td><strong>下载量</strong>: 2 GB</td></tr></table>'
|
||||
)
|
||||
|
||||
assert parser.upload == 8 * 1024 ** 3
|
||||
assert parser.download == 2 * 1024 ** 3
|
||||
assert parser.ratio == 4.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("table, expected_upload, expected_download, expected_ratio", [
|
||||
('<tr><td><strong>上传量</strong>: 1.5 <span>TB</span></td>'
|
||||
'<td><strong>下载量</strong>: 512 <span>GB</span></td></tr>',
|
||||
round(1.5 * 1024 ** 4), 512 * 1024 ** 3, 3.0),
|
||||
('<tr><td>上傳量</td><td>1,024 GB</td></tr><tr><td>下載量</td><td>512 GB</td></tr>'
|
||||
'<tr><td>分享率</td><td>2.001</td></tr>',
|
||||
1024 ** 4, 512 * 1024 ** 3, 2.001),
|
||||
('<tr><td><strong>实际上传量</strong>: 1 TB</td>'
|
||||
'<td><strong>实际下载量</strong>: 1 TB</td></tr>', 0, 0, 0.0),
|
||||
('<tr><td>上传量</td><td>0 B</td></tr><tr><td>下载量</td><td>0 B</td></tr>', 0, 0, 0.0),
|
||||
('<tr><td>上传量</td><td>未知</td></tr><tr><td>下载量</td><td>未知</td></tr>', 0, 0, 0.0),
|
||||
])
|
||||
def test_nexus_php_detail_traffic_table(table, expected_upload, expected_download, expected_ratio):
|
||||
"""按表格字段识别流量,支持嵌套单位和分列布局,并排除实际流量。"""
|
||||
parser = _build_parser()
|
||||
parser._parse_user_base_info("<html><body></body></html>")
|
||||
|
||||
parser._parse_user_detail_info(f"<html><body><table>{table}</table></body></html>")
|
||||
|
||||
assert parser.upload == expected_upload
|
||||
assert parser.download == expected_download
|
||||
assert parser.ratio == expected_ratio
|
||||
|
||||
|
||||
@pytest.mark.parametrize("download, ratio_text, expected_ratio", [
|
||||
("512 GB", "分享率: 3.001", 3.001),
|
||||
("512 GB", "", 3.0),
|
||||
("0 B", "", 0.0),
|
||||
])
|
||||
def test_nexus_php_legacy_traffic(download, ratio_text, expected_ratio):
|
||||
"""保留传统页面解析、分享率计算及零下载量行为。"""
|
||||
parser = _build_parser()
|
||||
|
||||
parser._parse_user_traffic_info(
|
||||
f"<html><body>上传量: 1.5 TB 下载量: {download} {ratio_text}</body></html>"
|
||||
)
|
||||
|
||||
assert parser.upload == round(1.5 * 1024 ** 4)
|
||||
assert parser.download == (512 * 1024 ** 3 if download == "512 GB" else 0)
|
||||
assert parser.ratio == expected_ratio
|
||||
|
||||
|
||||
def test_nexus_php_seeding_next_page_stops_when_userid_missing():
|
||||
"""
|
||||
userid 未识别且下一页也缺少 userid 时应停止翻页而不是抛出异常。
|
||||
|
||||
Reference in New Issue
Block a user