mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-07-12 16:02:35 +08:00
feat: accelerate RSS parsing with Rust
This commit is contained in:
@@ -9,6 +9,7 @@ from lxml import etree
|
||||
from app.core.config import settings
|
||||
from app.helper.browser import PlaywrightHelper
|
||||
from app.log import logger
|
||||
from app.utils import rust_accel
|
||||
from app.utils.http import RequestUtils
|
||||
from app.utils.string import StringUtils
|
||||
|
||||
@@ -227,6 +228,32 @@ class RssHelper:
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def __format_rust_items(items: List[dict]) -> List[dict]:
|
||||
"""
|
||||
将 Rust RSS 解析结果转换为原 Python XPath 解析返回结构。
|
||||
"""
|
||||
ret_array = []
|
||||
for item in items:
|
||||
pubdate = ""
|
||||
pubdate_raw = item.get("pubdate_raw")
|
||||
if pubdate_raw:
|
||||
pubdate = StringUtils.get_time(pubdate_raw)
|
||||
if pubdate is not None:
|
||||
pubdate = pubdate.astimezone(tz=None)
|
||||
tmp_dict = {
|
||||
'title': item.get("title") or "",
|
||||
'enclosure': item.get("enclosure") or "",
|
||||
'size': item.get("size") or 0,
|
||||
'description': item.get("description") or "",
|
||||
'link': item.get("link") or "",
|
||||
'pubdate': pubdate
|
||||
}
|
||||
if item.get("nickname"):
|
||||
tmp_dict['nickname'] = item.get("nickname")
|
||||
ret_array.append(tmp_dict)
|
||||
return ret_array
|
||||
|
||||
def parse(self, url, proxy: bool = False,
|
||||
timeout: Optional[int] = 15, headers: dict = None, ua: str = None) -> Union[List[dict], None, bool]:
|
||||
"""
|
||||
@@ -298,6 +325,12 @@ class RssHelper:
|
||||
logger.error("RSS内容不是有效的XML格式")
|
||||
return False
|
||||
|
||||
rust_items = rust_accel.parse_rss_items(ret_xml, self.MAX_RSS_ITEMS)
|
||||
if rust_items is not None:
|
||||
if len(rust_items) >= self.MAX_RSS_ITEMS:
|
||||
logger.warning(f"RSS条目过多,仅处理前{self.MAX_RSS_ITEMS}个")
|
||||
return self.__format_rust_items(rust_items)
|
||||
|
||||
# 使用lxml.etree解析XML
|
||||
parser = None
|
||||
try:
|
||||
|
||||
@@ -11,7 +11,6 @@ from requests import Session
|
||||
from app.core.config import settings
|
||||
from app.helper.cloudflare import under_challenge
|
||||
from app.log import logger
|
||||
from app.utils import rust_accel
|
||||
from app.utils.http import RequestUtils
|
||||
from app.utils.site import SiteUtils
|
||||
from app.utils.string import StringUtils
|
||||
@@ -159,11 +158,8 @@ class SiteParserBase(metaclass=ABCMeta):
|
||||
@staticmethod
|
||||
def num_filesize(text) -> int:
|
||||
"""
|
||||
将站点页面中的文件大小文本转换为字节,优先使用 Rust 快路径。
|
||||
将站点页面中的文件大小文本转换为字节。
|
||||
"""
|
||||
rust_value = rust_accel.parse_filesize(text)
|
||||
if rust_value is not None:
|
||||
return rust_value
|
||||
return StringUtils.num_filesize(text)
|
||||
|
||||
def parse(self):
|
||||
|
||||
@@ -672,9 +672,6 @@ class SiteSpider:
|
||||
"""
|
||||
if not text or not filters or not isinstance(filters, list):
|
||||
return text
|
||||
rust_text = rust_accel.apply_indexer_text_filters(text, filters)
|
||||
if rust_text is not None:
|
||||
return rust_text
|
||||
if not isinstance(text, str):
|
||||
text = str(text)
|
||||
for filter_item in filters:
|
||||
|
||||
@@ -117,34 +117,6 @@ def filter_torrents(
|
||||
return None
|
||||
|
||||
|
||||
def apply_indexer_text_filters(text: Any, filters: Optional[List[dict]]) -> Optional[str]:
|
||||
"""
|
||||
使用 Rust 执行 indexer 文本过滤器,不可用或遇到不支持过滤器时返回 None。
|
||||
"""
|
||||
if not _moviepilot_rust or not filters or not isinstance(filters, list):
|
||||
return None
|
||||
try:
|
||||
return _moviepilot_rust.apply_indexer_text_filters_fast(None if text is None else str(text), filters)
|
||||
except BaseException as err:
|
||||
_raise_non_rust_panic(err)
|
||||
logger.debug(f"Rust 站点文本过滤失败,回退 Python:{err}")
|
||||
return None
|
||||
|
||||
|
||||
def parse_filesize(text: Any) -> Optional[int]:
|
||||
"""
|
||||
使用 Rust 将文件大小文本转换为字节,不可用时返回 None。
|
||||
"""
|
||||
if not _moviepilot_rust:
|
||||
return None
|
||||
try:
|
||||
return int(_moviepilot_rust.parse_filesize_fast(text))
|
||||
except BaseException as err:
|
||||
_raise_non_rust_panic(err)
|
||||
logger.debug(f"Rust 文件大小解析失败,回退 Python:{err}")
|
||||
return None
|
||||
|
||||
|
||||
def build_indexer_search_url(config: dict) -> Optional[str]:
|
||||
"""
|
||||
使用 Rust 根据普通 indexer 配置生成搜索 URL,不可用时返回 None。
|
||||
@@ -187,6 +159,20 @@ def parse_indexer_torrents(
|
||||
return None
|
||||
|
||||
|
||||
def parse_rss_items(xml_text: str, max_items: int) -> Optional[List[dict]]:
|
||||
"""
|
||||
使用 Rust 批量解析 RSS/Atom 条目,不可用或解析失败时返回 None。
|
||||
"""
|
||||
if not _moviepilot_rust:
|
||||
return None
|
||||
try:
|
||||
return _moviepilot_rust.parse_rss_items_fast(xml_text or "", int(max_items or 0))
|
||||
except BaseException as err:
|
||||
_raise_non_rust_panic(err)
|
||||
logger.debug(f"Rust RSS 条目解析失败,回退 Python:{err}")
|
||||
return None
|
||||
|
||||
|
||||
def _coerce_media_type(value: Optional[str]) -> Optional[MediaType]:
|
||||
"""
|
||||
将 Rust 返回的媒体类型字符串转换为系统 MediaType。
|
||||
|
||||
Reference in New Issue
Block a user