Rust中quick-xml反序列化YouTube XML时media_group字段为空排查
YouTube XML数据反序列化后media_group字段为None的问题排查与解决
问题描述
使用Rust的quick-xml库反序列化YouTube频道XML数据接口返回的内容时,尽管原始XML中包含media:group相关数据,但Entry结构体中的media_group字段始终返回None。相关代码及依赖如下:
核心代码(youtube_data.rs)
// youtube_data.rs use hyper::{Body, Client, Uri}; use hyper_tls::HttpsConnector; use serde::Deserialize; use quick_xml::de::{from_str, DeError}; use crate::internals::singularity::WidgetError; impl From<DeError> for WidgetError { fn from(err: DeError) -> Self { WidgetError::XmlParse(err.to_string()) } } #[derive(Debug, Deserialize)] #[serde(rename = "feed")] pub struct Feed { id: String, #[serde(rename = "channelId", default)] yt_channel_id: Option<String>, title: String, published: String, #[serde(default)] #[serde(rename = "entry")] entries: Vec<Entry>, } #[derive(Debug, Deserialize)] struct Entry { id: String, title: String, published: String, #[serde(rename = "mediaGroup")] media_group: Option<MediaGroup>, } #[derive(Debug, Deserialize)] struct MediaGroup { #[serde(rename = "mediaTitle")] title: Option<String>, #[serde(rename = "mediaContent")] content: Option<MediaContent>, #[serde(rename = "mediaThumbnail")] thumbnail: Option<MediaThumbnail>, #[serde(rename = "mediaCommunity")] community: Option<MediaCommunity>, } #[derive(Debug, Deserialize)] struct MediaContent { #[serde(rename = "@url")] url: String, #[serde(rename = "@width")] width: u32, #[serde(rename = "@height")] height: u32, } #[derive(Debug, Deserialize)] struct MediaThumbnail { #[serde(rename = "@url")] url: String, #[serde(rename = "@width")] width: u32, #[serde(rename = "@height")] height: u32, } #[derive(Debug, Deserialize)] struct MediaCommunity { #[serde(rename = "mediaStarRating")] star_rating: Option<StarRating>, #[serde(rename = "mediaStatistics")] statistics: Option<Statistics>, } #[derive(Debug, Deserialize)] struct StarRating { #[serde(rename = "@count")] count: u64, } #[derive(Debug, Deserialize)] struct Statistics { #[serde(rename = "@views")] views: u64, } pub async fn get_youtube_vids_for_a_channel(channel_id: String) -> Result<Feed, WidgetError> { let url = format!( "https://www.youtube.com/feeds/videos.xml?channel_id={}", channel_id ); let uri: Uri = url.parse().unwrap(); let https = HttpsConnector::new(); let client = Client::builder().build::<_, Body>(https); let res = client.get(uri).await?; let body = hyper::body::to_bytes(res.into_body()).await?; let response_text = String::from_utf8(body.to_vec()) .map_err(|e| WidgetError::Utf8Error(e.to_string()))?; let feed: Feed = from_str(&response_text)?; Ok(feed) }
错误定义(singularity.rs)
// singularity.rs #[derive(Debug, Error)] pub enum WidgetError { #[error("Hyper error: {0}")] Hyper(#[from] hyper::Error), #[error("No geocoding data found")] NoGeocodingData, #[error("Error in reading HTML file")] NoHtmlToString, #[error("Serde JSON error: {0}")] SerdeJson(#[from] serde_json::Error), #[error("No timezone found")] NoTimeZone, #[error("XML parsing error: {0}")] XmlParse(String), #[error("UTF-8 conversion error: {0}")] Utf8Error(String), }
依赖配置(Cargo.toml)
[dependencies] hyper = { version = "0.14", features = ["full"] } hyper-tls = "0.5" regex = "1.10.5" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0.63" quick-xml = { version = "0.37", features = ["serialize"] }
问题原因
YouTube返回的XML中,media相关元素带有命名空间前缀,例如实际元素是<media:group>而非代码中期望的mediaGroup。quick-xml在反序列化时默认会严格匹配元素的完整名称(包括命名空间URI),而代码中没有处理命名空间,导致无法匹配到对应元素,最终media_group字段被反序列化为None。
查看YouTube XML的根元素可以看到命名空间声明:
<feed xmlns:media="http://search.yahoo.com/mrss/" ...>
其中media前缀对应的URI是http://search.yahoo.com/mrss/,所有media相关元素都属于这个命名空间。
解决方法
在serde的rename属性中,使用带命名空间URI的完整元素名称来匹配XML中的元素。格式为"{命名空间URI}元素名"。
修改后的核心结构体代码
#[derive(Debug, Deserialize)] struct Entry { id: String, title: String, published: String, // 匹配<media:group>元素,使用命名空间URI #[serde(rename = "{http://search.yahoo.com/mrss/}group")] media_group: Option<MediaGroup>, } #[derive(Debug, Deserialize)] struct MediaGroup { // 匹配<media:title> #[serde(rename = "{http://search.yahoo.com/mrss/}title")] title: Option<String>, // 匹配<media:content> #[serde(rename = "{http://search.yahoo.com/mrss/}content")] content: Option<MediaContent>, // 匹配<media:thumbnail> #[serde(rename = "{http://search.yahoo.com/mrss/}thumbnail")] thumbnail: Option<MediaThumbnail>, // 匹配<media:community> #[serde(rename = "{http://search.yahoo.com/mrss/}community")] community: Option<MediaCommunity>, } #[derive(Debug, Deserialize)] struct MediaCommunity { // 匹配<media:starRating> #[serde(rename = "{http://search.yahoo.com/mrss/}starRating")] star_rating: Option<StarRating>, // 匹配<media:statistics> #[serde(rename = "{http://search.yahoo.com/mrss/}statistics")] statistics: Option<Statistics>, }
补充说明
- 确保所有media相关元素的
rename属性都使用正确的命名空间URI,否则对应字段仍会返回None。 - 如果XML中还有其他命名空间的元素(比如YouTube自己的
yt:前缀),也需要用同样的方式处理,例如yt:channelId对应"{http://www.youtube.com/xml/schemas/2015}channelId"。
内容的提问来源于stack exchange,提问作者ams_132
相关产品推荐
相关产品推荐

