You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用Rust的scraper crate获取兄弟元素,实现Hacker News标题、链接、投票数及用户名抓取?

解决HN爬虫获取标题、投票数和用户名的问题

嘿,你已经迈出了很棒的第一步!要拿到标题、投票数和用户名,核心是先找到每个新闻条目的父容器,而不是单独抓链接——HN的页面结构里,每个新闻的所有信息都和它的容器绑定在一起,这样就能一次性把所有关联的数据捞出来。

先理清楚HN的页面结构

每个完整的新闻条目由两个相邻的<tr>组成:

  1. 第一个<tr>带athing类,里面包含标题和链接;
  2. 紧接着的第二个<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(())
}

关键改动说明

  1. 用parse_document替代parse_fragment:片段解析会忽略整个页面的DOM结构,没法正确找到兄弟元素,换成文档解析才能处理完整的层级关系。
  2. 先抓根容器tr.athing:每个容器对应一条新闻,所有相关信息都能通过这个容器定位,避免数据错位。
  3. 处理兄弟元素:用next_sibling()拿到相邻的元信息行,注意要把Node转换成Element才能继续用选择器查找内容。
  4. 边界情况处理:不是所有新闻都有投票或用户名,所以用map+unwrap_or_else来兜底,避免程序因为None而崩溃。

你的Cargo.toml配置不需要改动,保持原来的依赖就行。

内容的提问来源于stack exchange,提问作者Eka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 10:47:44