如何利用Rust的scraper crate获取兄弟元素,实现Hacker News标题、链接、投票数及用户名抓取?
解决HN爬虫获取标题、投票数和用户名的问题
嘿,你已经迈出了很棒的第一步!要拿到标题、投票数和用户名,核心是先找到每个新闻条目的父容器,而不是单独抓链接——HN的页面结构里,每个新闻的所有信息都和它的容器绑定在一起,这样就能一次性把所有关联的数据捞出来。
先理清楚HN的页面结构
每个完整的新闻条目由两个相邻的<tr>组成:
- 第一个
<tr>带athing类,里面包含标题和链接; - 紧接着的第二个
<tr>就是包含投票数、用户名、发布时间的元信息行。
修改你的代码
下面是调整后的src/main.rs,我会在代码里加注释说明关键改动:
use scraper::{Html, Selector, Node}; use reqwest; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let url = "https://news.ycombinator.com/"; let html = reqwest::get(url).await?.text().await?; // 改成解析整个文档,而不是片段,这样才能正确处理DOM的层级和兄弟关系 let document = Html::parse_document(&html); // 选择所有新闻的根容器(带athing类的tr) let story_container_selector = Selector::parse("tr.athing").unwrap(); // 标题链接的选择器 let title_link_selector = Selector::parse("a.storylink").unwrap(); // 投票数的选择器(span.score) let score_selector = Selector::parse("span.score").unwrap(); // 用户名的选择器(a.hnuser) let user_selector = Selector::parse("a.hnuser").unwrap(); for story_element in document.select(&story_container_selector) { // 从容器里取出标题和链接 let title_link = story_element.select(&title_link_selector).next().unwrap(); let title = title_link.text().collect::<Vec<_>>().join(""); let link = title_link.value().attr("href").unwrap(); // 获取当前容器的下一个兄弟tr——也就是存着投票和用户信息的那一行 // 注意next_sibling返回的是Node,需要转成Element才能用select方法 if let Some(Node::Element(meta_element)) = story_element.next_sibling() { // 处理投票数:有些新新闻还没投票,所以要处理None的情况 let votes = meta_element.select(&score_selector) .next() .map(|score_elem| score_elem.text().collect::<Vec<_>>().join("")) .unwrap_or_else(|| "0 points".to_string()); // 处理用户名:少数官方发布的内容没有用户,同样处理None let user = meta_element.select(&user_selector) .next() .map(|user_elem| user_elem.text().collect::<Vec<_>>().join("")) .unwrap_or_else(|| "Unknown".to_string()); // 打印结果,格式更清晰 println!("📝 标题: {}", title); println!("🔗 链接: {}", link); println!("👍 投票: {}", votes); println!("👤 用户: {}", user); println!("------------------------"); } } Ok(()) }
关键改动说明
- 用
parse_document替代parse_fragment:片段解析会忽略整个页面的DOM结构,没法正确找到兄弟元素,换成文档解析才能处理完整的层级关系。 - 先抓根容器
tr.athing:每个容器对应一条新闻,所有相关信息都能通过这个容器定位,避免数据错位。 - 处理兄弟元素:用
next_sibling()拿到相邻的元信息行,注意要把Node转换成Element才能继续用选择器查找内容。 - 边界情况处理:不是所有新闻都有投票或用户名,所以用
map+unwrap_or_else来兜底,避免程序因为None而崩溃。
你的Cargo.toml配置不需要改动,保持原来的依赖就行。
内容的提问来源于stack exchange,提问作者Eka
相关产品推荐
相关产品推荐

