Skip to content

Search Reference

ChouChiu edited this page Sep 23, 2026 · 1 revision

搜索类型与高级函数

SearchResult

各平台通用的搜索结果载荷:

pub struct SearchResult {
    pub searcher_type: Searchers,           // 来源平台
    pub title: String,                      // 歌曲标题
    pub artists: Vec<String>,               // 艺术家列表
    pub album: String,                      // 专辑名称
    pub album_artists: Option<Vec<String>>, // 专辑艺术家列表
    pub duration_ms: Option<i32>,           // 歌曲时长(毫秒,AMLL TTML DB 固定为 None)
    pub match_type: Option<MatchType>,      // 经过 compare / rank 计算后的匹配度
    pub id: String,                         // 平台歌曲唯一 ID 或文件名
    pub numeric_id: Option<i64>,            // 数字 ID(网易云、QQ 音乐等提供)
}

辅助方法:

  • artist(&self) -> String:以逗号连接全部艺术家。
  • album_artist(&self) -> Option<String>:以逗号连接专辑艺术家。

MatchType

曲目匹配度等级,实现了 Ord,可直接排序比较:

变体 分值 判定语义
Perfect 100 完全匹配(标题、艺术家、专辑、时长高度吻合)
VeryHigh 99 极高匹配(标题与艺术家完全一致)
High 95 高匹配
PrettyHigh 90 较高匹配
Medium 70 中等匹配
Low 30 低匹配
VeryLow 10 极低匹配
NoMatch -1 不匹配

Searcher Trait

所有平台搜索客户端统一实现的 trait:

#[async_trait]
pub trait Searcher: Sync {
    fn name(&self) -> &str;
    fn display_name(&self) -> &str;
    fn searcher_type(&self) -> Searchers;

    async fn search_for_results_str(
        &self,
        search_string: &str,
    ) -> Result<Vec<SearchResult>, SearchError>;

    async fn search_for_results(
        &self,
        track: &TrackMetadata,
    ) -> Result<Vec<SearchResult>, SearchError> {
        // 默认按 "{title} {artist} {album}" 拼接后调用 search_for_results_str
    }
}

错误契约:

  • Ok(vec![]):请求成功,但平台未返回匹配条目。
  • Err(SearchError):请求执行失败(网络中断、超时、限流、验证码或服务端业务报错)。

SearchError

搜索与歌词抓取过程中产生的错误类型:

变体 触发条件
Http(reqwest::Error) 网络连接中断、超时,或响应流在按目标类型解码时失败(0.5 起为 reqwest 0.13 类型)
Json(serde_json::Error) 平台响应的内容不是合法 JSON 文本
Status(u16) 服务端返回 4xx/5xx 等非 2xx HTTP 状态码(如 429 Too Many Requests)
Api(String) HTTP 请求成功,但平台返回了业务级错误码或失败说明
Captcha 命中反爬风控或人机验证(如 Musixmatch 401 Captcha)
Payload(String) 响应体数据结构破损,例如 Base64 或 UTF-8 解码失败
InvalidConfig(String) 传入的客户端参数非法(如镜像 URL 不是合法 HTTP/HTTPS 地址)
use lyrics_helper::models::TrackMetadata;
use lyrics_helper::searchers::netease::NeteaseSearcher;
use lyrics_helper::searchers::search_for_best_result;
use lyrics_helper::SearchError;

let mut track = TrackMetadata::new();
track.title = Some("晴天".to_string());
track.artist = Some("周杰伦".to_string());
track.ensure_artists();

match search_for_best_result(&NeteaseSearcher, &track).await {
    Ok(Some(best)) => println!("{}", best.title),
    Ok(None) => println!("无结果"),
    Err(SearchError::Status(429)) => eprintln!("被限流"),
    Err(SearchError::Captcha) => eprintln!("触发验证码"),
    Err(err) => eprintln!("搜索异常: {err}"),
}

Searchers 枚举

pub enum Searchers {
    QQMusic,
    Netease,
    Kugou,
    Musixmatch,
    SodaMusic,
    AppleMusic,
    Spotify,
    LRCLIB,
    AmllTtmlDb,
}

匹配打分与渐进式搜索

search_for_best_result

执行两趟搜索策略(先精确、再宽松)并返回匹配等级最高的结果:

pub async fn search_for_best_result(
    searcher: &dyn Searcher,
    track: &TrackMetadata,
) -> Result<Option<SearchResult>, SearchError>

search_for_best_result_with_match

在 search_for_best_result 基础上增加最低匹配度过滤,低于指定等级的结果返回 Ok(None):

pub async fn search_for_best_result_with_match(
    searcher: &dyn Searcher,
    track: &TrackMetadata,
    minimum_match: MatchType,
) -> Result<Option<SearchResult>, SearchError>

search_with_refinement

根据元数据生成从精确到模糊的多级查询,逐层尝试搜索:

pub async fn search_with_refinement(
    searcher: &dyn Searcher,
    track: &TrackMetadata,
    full_search: bool,
) -> Result<Vec<SearchResult>, SearchError>
  • full_search = true:执行所有层级的查询并汇总全部去重结果。
  • full_search = false:一旦某层查询返回有效条目即刻返回,不再执行后续更模糊的查询。

打分与排序工具

  • compare_track(track, title, artists, album, album_artists, duration_ms) -> MatchType:基于编辑距离与最长公共子序列计算单条结果的匹配度分值。
  • rank_by_match(results: &mut [SearchResult], track: &TrackMetadata):计算列表中每条结果的 match_type 并按匹配度从高到低就地排序。
  • strip_feat(title: &str) -> String:剔除歌曲标题中的 (feat. ...) 附加信息。

Clone this wiki locally