-
Notifications
You must be signed in to change notification settings - Fork 1
Search Reference
ChouChiu edited this page Sep 23, 2026
·
1 revision
各平台通用的搜索结果载荷:
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>:以逗号连接专辑艺术家。
曲目匹配度等级,实现了 Ord,可直接排序比较:
| 变体 | 分值 | 判定语义 |
|---|---|---|
Perfect |
100 | 完全匹配(标题、艺术家、专辑、时长高度吻合) |
VeryHigh |
99 | 极高匹配(标题与艺术家完全一致) |
High |
95 | 高匹配 |
PrettyHigh |
90 | 较高匹配 |
Medium |
70 | 中等匹配 |
Low |
30 | 低匹配 |
VeryLow |
10 | 极低匹配 |
NoMatch |
-1 | 不匹配 |
所有平台搜索客户端统一实现的 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):请求执行失败(网络中断、超时、限流、验证码或服务端业务报错)。
搜索与歌词抓取过程中产生的错误类型:
| 变体 | 触发条件 |
|---|---|
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}"),
}pub enum Searchers {
QQMusic,
Netease,
Kugou,
Musixmatch,
SodaMusic,
AppleMusic,
Spotify,
LRCLIB,
AmllTtmlDb,
}执行两趟搜索策略(先精确、再宽松)并返回匹配等级最高的结果:
pub async fn search_for_best_result(
searcher: &dyn Searcher,
track: &TrackMetadata,
) -> Result<Option<SearchResult>, SearchError>在 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>根据元数据生成从精确到模糊的多级查询,逐层尝试搜索:
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. ...)附加信息。