in

使用Rust进行网页爬取的初学者指南

使用Rust进行网页爬取的初学者指南

虽然 Python 可能是网络爬虫最流行的语言,但是你可以使用任何你熟悉的语言来完成基本的网络爬虫任务。只要语言具有基本的库支持来获取 HTML 并解析其内容,你就可以开始了。

例如,Rust 是最新最流行的语言之一。虽然它主要用于后端任务,但很少有人建议使用它来进行网络爬虫。

然而,用 Rust 进行网络爬虫与用 Python 一样容易。其网络爬虫库遵循与 Python 库相同的原则,生成的代码也同样易于阅读。

在这篇文章中,你将学习如何使用 Rust 进行网页爬取。你还将发现如何使用两个 Rust 库——ReqwestScraper——来获取 Hacker News 的热门帖子列表连同其点数。


使用 Rust 爬取 Hacker News

在本教程中,你将学习如何使用 Rust 爬取 Hacker News 的热门帖子。你将创建一个程序,该程序下载 Hacker News 的首页并打印所有帖子连同其评分。

设置

为了完成本教程,你需要在机器上安装 Rust 编译器。如果你还没有安装,可以从官方网站下载。

首先,你需要在命令行中运行 cargo new web_scraper 创建一个新的 Rust 项目。然后,在代码编辑器中打开新创建的目录。

对于本项目,你需要添加三个dependencies:

  • Reqwest 用于处理 HTTP 请求
  • Scraper 用于解析页面的 HTML
  • Tokio(Rust 的异步运行时),它是使用 Reqwest 所需的。

打开 Cargo.toml 文件并更新dependencies:

[dependencies]
reqwest = "0.11"
scraper = "0.13.0"
tokio = { version = "1.22.0", features = ["full"] }

Then, open main.rs and replace the boilerplate there with the following: 

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {

// code goes here

    Ok(())
}

这是使用 Tokio 运行时所需的最少代码,Reqwest 库默认使用该运行时。一旦你完成了这些设置,你就可以开始编写“真正的代码”了。

使用 Reqwest 获取网页

Reqwest 是一个 HTTP 客户端库,允许你向服务器发送 GET、POST 和其他 HTTP 请求。它是 Rust 中的 requests 库等价物。

要使用它,你首先需要创建一个新的客户端,该客户端将作为与网站的联系点。

let client = reqwest::Client::builder().build()?;

然后,你可以使用该客户端发送 GET 请求。下面的代码将获取网站的 HTML 代码并将其存储在 response 变量中。

let response = client
        .get("https://news.ycombinator.com/")
        .send()
        .await?
        .text()
        .await?;

这就是使用 Rust 获取网页内容所需的所有操作。到目前为止,代码如下。

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::builder().build()?;

    let response = client
        .get("https://news.ycombinator.com/")
        .send()
        .await?
        .text()
        .await?;

    Ok(())
}

现在,你可以解析下载的网页来找到所需的信息。

使用 Scraper 解析页面

Scraper 是一个 Rust 库,用于解析 HTML 并使用 CSS 选择器查询它。它是 Rust 中的 BeautifulSoup 版本

要使用它,你首先需要解析响应的整个 HTML。

let document = scraper::Html::parse_document(&response);

然后,你可以使用CSS selector来查询文档中的项目,以获取所需的信息。

选择器是使用 scraper::Selector::parse 方法创建的,该方法将一个字符串作为参数,该字符串包含一个 CSS 选择器,这是一种指定 CSS 元素的细节方式。

如果你检查 Hacker News 的 HTML 代码,你将看到帖子标题被包装在一个具有 titleline 类的 span 元素中。要获取该 span 元素内部的标题,你需要选择 anchor 元素。这可以由以下 CSS 选择器涵盖:span.titleline>a。

下面是 Rust 中创建该选择器的函数。

let title_selector = scraper::Selector::parse("span.titleline>a").unwrap();

由于并不是所有字符串都是有效的 CSS 选择器,因此 parse 函数返回一个 Result 类型。在这种情况下,如果 CSS 选择器格式不正确,程序崩溃是可以接受的,所以你可以简单地调用 unwrap() 来获取选择器。

然后,你可以通过将选择器应用于 HTML 文档,然后将每个匹配元素映射到其内部 HTML(即标题文本)来获取所有帖子标题。

let titles = document.select(&title_selector).map(|x| x.inner_html());

获取每个标题的点数要困难一些。这主要是因为标题不一定需要实际拥有点数:一些赞助信息只会有发布时间。

首先,你需要确定所有帖子的共同元素:既有分数的帖子,也有没有分数的帖子。在这种情况下,这是一个具有 subtext 类的表格单元格,它包含所有信息,如提交帖子的用户、发布时间和帖子的分数。

<td class="subtext">
…
</td>

接着,你将使用之前学习的选择器语法来选择所有这些元素。

let subtext_selector = scraper::Selector::parse("td.subtext").unwrap();
    let subtexts = document.select(&subtext_selector);

下一步,你将遍历这些元素,并检查每个元素中是否包含分数。如果不包含,就使用默认值代替。

首先,你需要一个选择器来选择分数。

let score_selector = scraper::Selector::parse("span.score").unwrap();

Then, you’ll need to iterate over the subtext elements and find the scores. 

    let scores = subtexts.map(|subtext| {
        subtext
            .select(&score_selector)
            .next()
            .and_then(|score| score.text().nth(0))
            .unwrap_or("0 points")
    });

如果分数不存在,上面的函数将使用 .unwrap_or() 将默认值“0 points”插入。

这就是使用 Rust 进行网页爬取所需的所有内容!现在,你可以在控制台中打印出标题和分数了。

titles.zip(scores).for_each(|pair| println! {"{:?}", pair});

下面是完整的代码:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::builder().build()?;

    let response = client
        .get("https://news.ycombinator.com/")
        .send()
        .await?
        .text()
        .await?;

    let document = scraper::Html::parse_document(&response);

    let title_selector = scraper::Selector::parse("span.titleline>a").unwrap();
    let titles = document.select(&title_selector).map(|x| x.inner_html());

    let subtext_selector = scraper::Selector::parse("td.subtext").unwrap();
    let subtexts = document.select(&subtext_selector);

    let score_selector = scraper::Selector::parse("span.score").unwrap();

    let scores = subtexts.map(|subtext| {
        subtext
            .select(&score_selector)
            .next()
            .and_then(|score| score.text().nth(0))
            .unwrap_or("0 points")
    });

    titles.zip(scores).for_each(|pair| println! {"{:?}", pair});
    Ok(())
}

你可以通过在命令行中调用cargo run来运行这个程序。它应该打印出30个标题和分数对,如下所示:

("Apple's reaction to protests: AirDrop is now limited to 10 minutes", "382 points")
("Intentionally Making Close Friends", "410 points")
("Curation and decentralization is better than millions of apps", "163 points")
("Everything I wish I knew when learning C", "470 points")
("How hospice became a for profit hustle", "117 points")
...

向Reqwest添加代理

当你爬取网页时,隐藏你的 IP 地址是一个不错的主意,以免爬取活动被网站管理员检测和中止。实现这一点的主要方法是通过代理,它们充当你和目标网站之间的中间人,屏蔽你的 IP 地址。

在本教程的这一部分,你将学习如何将代理添加到 Rust 网页爬取程序中。

本教程将使用 IPRoyal 住宅代理作为示例。它们非常适合使用 Node js 的网页爬取项目,因为它们在每个请求中都会轮换你的 IP。此外,它们来自多个不同的位置,使得任何爬取活动都很难被检测。

但是,如果你使用其他代理提供商,不要担心——过程应该非常相似。

首先,你需要代理的链接。如果你使用 IPRoyal 代理,可以在仪表盘中找到链接。

IPRoyal residential proxies dashboard

此链接需要作为HTTP和HTTPS请求的代理添加到你的客户端。你可以在程序的第一部分(定义和构建客户机)中执行此操作。

let client = reqwest::Client::builder()
        .proxy(reqwest::Proxy::http(
            "http://link-to-proxy",
        )?)
        .proxy(reqwest::Proxy::https(
            "http://link-to-proxy",
        )?)
        .build()?;

现在,使用这个客户端发送的请求将通过代理传递,不会泄露你的真实 IP 地址。


常见问题

什么是 Tokio,为什么需要它来进行网页爬取?

Tokio 是 Rust 创建异步应用程序的 runtime。这在网页爬取应用程序中非常有用,例如网页爬虫或爬取大量页面的任务。这是因为在同步应用程序中,HTTP 请求将阻塞其他代码的执行,而异步应用程序可以在等待响应时处理其他项目。

Reqwest 默认使用 Tokio,但可以使用阻塞 API 来完成简单的任务。

为什么示例代码中频繁使用 unwrap() 函数?

Rust 尽量避免 runtime panics——代码在执行过程中崩溃的 situation。这是因为某些错误。

因此,像 Python 等语言中可能引发异常的函数有时会返回 Result 类型的值,该值可以是有效结果或错误。你可以选择以某种方式处理错误,或者只是调用 unwrap(),这将在出现错误时崩溃程序。

对于无法恢复的错误,例如构造选择器错误,这是一个足够好的选项。

可以在 Scraper 中使用 XPath 选择器吗?

不幸的是,Scraper 不支持 XPath 表达式。如果你需要它们来完成爬取项目,可以使用 Thirtyfour 库,该库提供了 XPath 选择器等其他功能。请注意,它是一个 Selenium 库(即模拟 web 浏览器),这增加了爬取的复杂性。


总    结

在本文中,你学习了如何使用 Rust 编程语言完成基本的网页爬取任务,以及如何将代理添加到 Rust 编写的网页爬虫中。

如果你想爬取使用 JavaScript 的更advanced 页面,简单的 Reqwest 和 Scraper 组合可能不足。就像 Python 一样,Rust 也有一个名为 Thirtyfour 的 Selenium 绑定库。它使你能够模拟真实浏览器的行为,如点击、输入、滚动等等。

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。