使用 Rust 做异步数据采集的实践
source link: https://blog.budshome.com/budshome/shi-yong-rust-zuo-yi-bu-shu-ju-cai-ji-de-shi-jian
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
“人要学会走路,也要学会摔跤,而且只有经过摔跤,才能学会走路。” 对于一个人来说,这正是磨练。给生活添点料,经过千锤百炼的人生,更加耐人寻味。 -- 佚名
[Rust] 使用 Rust 做异步数据采集的实践
2021-04-09 12:49:57+08:00 by - budshome [blog: 芽之家]
💥 内容涉及著作权,均归属作者本人。若非作者注明,默认欢迎转载:请注明出处,及相关链接。
Summary: 使用 `Rust` 生态中的数据采集相关 `crate` 进行数据采集的实践:异步运行时 `async-std`,HTTP 客户端库 `reqwest`,数据采集库 `scraper`,以及控制台输出文字颜色标记库 `colored`。
数据采集,生态工具最完整、成熟的,笔者认为莫过于 Python
了,特别是其 Scrapy
库的强大和成熟,是很多项目和产品的必选。笔者以前在大数据项目中,数据采集部分,也是和团队同事一起使用。不管从工程中的那个视觉来说,笔者认为 scrapy
都是完全满足的。
本文是使用 Rust
生态中的数据采集相关 crate
进行数据采集的实践,是出于这样的目的:新的项目中,统一为 Rust
技术栈;想尝试下 Rust
的性能优势,是否在数据采集中也有优势。
因此,本文更多的仅是关于 Rust
生态实践而言,并非是 Rust
做数据采集相比 Python
有优势。仅就语言而言,Rust 显然具有极大的性能优势。
好的,我们从头开始进行一次数据采集的完整实践,以站点 https://this-week-in-rust.org/
为目标,采集所有的 Rust 周报
。
我们使用 cargo
,创建一个新项目。本项目我们要使用 Rust 的异步运行时 async-std
,HTTP 客户端库 reqwest
,数据采集库 scraper
,以及控制台输出文字颜色标记库 colored
。我们在创建项目后,一并使用 cargo-edit
crate 将它们加入依赖项:
关于
cargo-edit
的安装和使用,请参阅文章《构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(1)- 起步及 crate 选择》
cargo new rust-async-crawl-example
cd ./rust-async-crawl-example
cargo add async-std reqwest scraper colored
成功执行后,Cargo.toml
文件清单的 dependencies
区域将有上述 4 个 crate。但是对于 async-std
,本次实践中,我们进使用其 attributes
特性;对于 reqwest
,我们则要启用其 blocking
特性。我们修改 Cargo.toml
文件,最终为如下内容:
[package]
name = "rust-crawl-week"
version = "0.1.0"
authors = ["zzy <[email protected]>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-std = { version = "1.9.0", features = ["attributes"] }
reqwest = { version = "0.11.2", features = ["blocking"] }
scraper = "0.12.0"
colored = "2.0.0"
数据采集,我们必定不会局限于一个站点。所以,我们参考 Python 中的库 scrapy
的思路,每个具体的爬虫,对应一个站点。因此,我们组织文件结构为 main.rs
是执行入口;sites.rs
或者 sites
模块,是具体各自站点爬中位置。本例中,我们只是对站点 https://this-week-in-rust.org/
进行采集,所以将其编写在 sites.rs
文件中。实际的项目产品中,推荐使用 sites
模块,里面包含以各自站点命名的具体爬虫。
对于采集结果,我们要通过输出接口,将其输入到控制台、数据库、文档(文本、excel 等)。这些输出和写入的接口,也需要是在统一的位置,以便于后续扩展。
本实例中,我们将其打印输出到控制台。并在打印时,对于不同的站点、标题,以及 url 链接进行着色。
因此,本实践实例中,工程结构最终为:
此时,我们还未编译构建,所以没有 Cargo.lock
文件和 target
目录。您如果跟随本文实践,cargo build
后,会产生它们。下文不再说明。
main.rs
数据采集入口文件,其代码要尽可能简单和简洁。
mod sites;
#[async_std::main]
async fn main() {
// this-week-in-rust.org
match sites::this_week_in_rust_org().await {
Ok(site) => println!("{:#?}", site),
Err(_) => eprintln!("Error fetching this-week-in-rust.org data."),
}
// 其它站点
// ……
}
对于多个站点,我们逐次增加即可,这样有利于简单的后续扩展。这儿需要再次说明:本例中,我们只是对站点 https://this-week-in-rust.org/
进行采集,所以将其编写在 sites.rs
文件中。实际的项目产品中,推荐使用 sites
模块,里面包含以各自站点命名的具体爬虫。
注意:
println!("{:#?}", site)
,控制台输出时,我们已经对其采用了 Rust 中默认最美观易读的输出方式。之所以标注此代码,是因为对于第一次不够“人类工程学”的显示方式,我们后面要进行迭代。
sites.rs
第一次编码,采集数据并输出
首先,我们要定义两个结构体,分别表示站点信息,以及采集目标数据的信息(本例为标题、url 链接)。
#[derive(Debug)]
pub struct Site {
name: String,
stories: Vec<Story>,
}
#[derive(Debug)]
struct Story {
title: String,
link: Option<String>,
}
对于目标数据的采集,我们的思路很简单,三步走:
- 获取 HTML 文档;
- 萃取数据标题;
- 萃取数据 url 链接。
我们定义这三个方法,并在具体的站点爬虫 this_week_in_rust_org
中,进行调用:
use reqwest::{blocking, Error};
use scraper::{ElementRef, Html, Selector};
use std::result::Result;
pub async fn this_week_in_rust_org() -> Result<Site, Error> {
let s = Selector::parse("div.col-md-12 a").unwrap();
let body = get_html("https://this-week-in-rust.org/blog/archives/index.html").await?;
let stories = body
.select(&s)
.map(|element| Story {
title: parse_title(element),
link: parse_link(element),
})
.collect();
let site = Site {
name: "this-week-in-rust.org".to_string(),
stories,
};
Ok(site)
}
async fn get_html(uri: &str) -> Result<Html, Error> {
Ok(Html::parse_document(&blocking::get(uri)?.text()?))
}
fn parse_link(element: ElementRef) -> Option<String> {
let mut link: Option<String> = None;
if let Some(link_str) = element.value().attr("href") {
let link_str = link_str.to_owned();
link = Some(link_str);
}
link
}
fn parse_title(element: ElementRef) -> String {
element.inner_html()
}
这段代码是很易读的,代码既是最好的文档。注意获取 HTML 文档的函数 get_html
和 爬虫调用函数 this_week_in_rust_org
是异步的,而萃取链接函数 parse_link
和萃取标题函数 parse_title
则不是。因为具体的萃取,是在一个数据解析进程中执行的,异步与否笔者认为意义不大。当然,您如果有兴趣,可以改为异步函数,进行性能对比。
第一次编码完成,我们编译、运行看看部分输出结果:
安装依赖较多,如果时间较长,请配置 Cargo 国内镜像源。
这个输出数据是 json 格式的,并且文字也没有颜色区分。对于其它输入调用接口,非常合适。比如数据库和导出文档等。但是对于人眼阅读来说,则有些不够友好,我们希望输出就是标题和其链接就可以了。
第二次编码,输出数据格式优化
第一次编码中,我们使用的是 Rust 默认的 Display
trait。我们要实现自定义输出数据格式,也就是需要对 Site
和 Story
2 个结构体实现自定义 Display
trait。
use colored::Colorize;
use std::fmt;
impl fmt::Display for Site {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "{}", self.name.blue().bold())?;
for story in &self.stories {
writeln!(f, "{}", story)?;
}
Ok(())
}
}
impl fmt::Display for Story {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.link {
Some(link) => write!(f, "\t{}\n\t\t({})", self.title.green(), link),
None => write!(f, "\t{}", self.title),
}
}
}
此时,我们 main.rs
中的打印,甚至不需要指定 Display
方式的:
mod sites;
#[async_std::main]
async fn main() {
// this-week-in-rust.org
match sites::this_week_in_rust_org().await {
Ok(site) => println!("{}", site),
Err(_) => eprintln!("Error fetching this-week-in-rust.org data."),
}
// 其它站点
// ……
}
我们现在看看输出结果:
人眼阅读,这种方式合适一些,并且 url 链接,可以直接点击。
感兴趣的朋友,可以参阅 github 完整代码仓库。
谢谢您的阅读!
Recommend
-
108
关于拉勾网数据采集爬虫的文章,网上已经写烂了。这里简单记录一个之前帮助同事妹子写的小爬虫工具。某天,HR同事妹子接到一个任务,收集并分析拉勾网BAT三家公司所有招聘岗位的分类,要求,薪酬范围,人数等信息。人肉采集辛苦枯燥,随手写段代码搭救妹子。 开始...
-
90
开源纯C#工控网关+组态软件(七)数据采集与归档 一、 引子
-
63
「造数」用智能云数据采集技术降低门槛,节省的或许不止一个工程师的成本韩旭·2017-12-19 09:20数据时代的“石油钻井平台”
-
65
Python爬虫——城市公交、地铁站点和线路数据采集 本...
-
64
-
6
数据采集是数据应用的源头,指导企业在产品、运营和业务等多方面决策。本文作者王灼洲从数据采集需求出发,详细解读了如何实现高效、可用的数据采集方案。主要内容如下: · 数据采集的定义和重要性 · 业内常见的数据采集方案 · 数据采集的原则 · 数据采集案例分...
-
5
神策数据王灼洲:方法论 + 实践,全面解析数据采集方案,必看! 神策小秘书...
-
0
数据采集使用莫忘“红绿灯” 来源:经济日报2021-12-20 15:09...
-
3
使用 Async/Await 编写异步代码的五个优秀实践 作者:前端小智 2023-04-14 08:10:59 在这篇博客文章中,我们将讨论async/await - 一种在各种编程语言中编写异步代码的强大工具。
-
6
C#使用MX Component实现三菱PLC软元件数据采集的完整步骤(仿真) ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK