惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
宝玉的分享
宝玉的分享
小众软件
小众软件
有赞技术团队
有赞技术团队
月光博客
月光博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
MyScale Blog
MyScale Blog
Engineering at Meta
Engineering at Meta
Stack Overflow Blog
Stack Overflow Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
D
Docker
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MongoDB | Blog
MongoDB | Blog
WordPress大学
WordPress大学
J
Java Code Geeks
罗磊的独立博客
V
Visual Studio Blog
雷峰网
雷峰网
H
Help Net Security
T
The Blog of Author Tim Ferriss
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
大猫的无限游戏
大猫的无限游戏
F
Fortinet All Blogs

又耳笔记

用AI挣钱之AI建站:二维码生成器工具 用AI挣钱之AI建站:https://markdowntopdf.top Cloudflare Workers实战番外一:用Static Assets托管网站,告别传统方案 Cloudflare Workers实战(五):不止JavaScript,拥抱Python与Rust Cloudflare Workers实战(四):托管和分发静态文件 Cloudflare Workers实战(三):实现认证、重定向与缓存 Cloudflare Workers实战(二):动态修改后端响应 Cloudflare Workers实战(一):随心所欲操作客户端请求 Cloudflare workers不完全指南 使用n8n创作短篇小说 水果风波:信任的代价 用Python将PDF文件转换成图片 rust网络框架Pingora源码阅读3 rust网络框架Pingora源码阅读2 rust网络框架Pingora源码阅读1 Pingora快速入门教程1之总览 Rust模板引擎askama快速入门引擎 使用Loco快速搭建自己的后台系统 用Rust发一封图文并茂的邮件 Rust小项目: 写一个简单的恶意流量阻断器 Rust命令行库Clap快速入门教程 Rust小项目:用Rust写一个端口扫描器 Rust真全栈开发快速入门 rust声明宏快速入门教程 Rust文本处理快速入门教程 RUST web框架axum快速入门教程6之测试 白嫖免费的Rust在线运行时shuttle RUST web框架axum快速入门教程5之中间件 用Rust来做以太坊开发5之事件日志及签名 《英语语法分解大全》读后感
Rust小项目: 写一个简单的网页爬虫
About 又耳宁 关于技术与股票的一些零碎想法 · 2024-01-20 · via 又耳笔记

本文主要是对之前reqwest库的一个简单的扩展,通过写一个简单的爬虫项目来练习练习Rust, 爬虫有用也有趣,但是不要给目标网站造成过大的压力,否则可能会触犯法律,切记切记。

本文的爬虫任务是获取GitHub的Trending相关数据, 获取当日列表的仓库名,仓库Star数,今日Star数。

本文的依赖如下

[dependencies]
reqwest = "0.11.23"
scraper = "0.18.1"
tokio = { version = "1.35.1", features = ["full"] }

快速入门

爬虫的任务总结起来并不复杂,获取数据,解析数据,示例代码如下:

有反爬机制的网站获取数据会很难的,需要耗费很多精力破解相关机制的。。。

use reqwest::Result;
use scraper::{Html, Selector};


#[tokio::main]
async fn main() -> Result<()>{
    let target_url = "https://github.com/trending/rust";
	// 获取数据
    let body = reqwest::get(target_url)
    .await
    .expect("请求地址失败")
    .text()
    .await
    .expect("解析网页内容失败");
    
	// 解析数据
    let document = Html::parse_document(body.as_str());
    // 我为啥知道是Box-row, h2 a之类的路径? 因为手动获取的呀^_^
    let rows_selector = Selector::parse(".Box-row").unwrap();
    let repo_link_selector = Selector::parse("h2 a").unwrap();
    let repo_today_star_selector: Selector = Selector::parse("span.d-inline-block.float-sm-right").unwrap();
    let repo_total_star_selector = Selector::parse("a.Link.Link--muted.d-inline-block.mr-3").unwrap();

  	// 以每行作为后续的解析入口
    for row in document.select(&rows_selector) {
        if let Some(repo_link) = row.select(&repo_link_selector).nth(0) {
            if  let Some(href) = repo_link.value().attr("href") {
                print!("仓库链接: {href} ")
            }
        }

        if let Some(today_star) = row.select(&repo_today_star_selector).nth(0) {
            let texts: Vec<_> = today_star.text().collect();
            let text = texts.join("").split_whitespace().nth(0).expect("获取今日star数失败").to_string();
            let text = text.replace(",", "");
            print!("今日star数: {text} ")
        }

        if let Some(total_star) = row.select(&repo_total_star_selector).nth(0) {
            let texts: Vec<_> = total_star.text().collect();
            let text = texts.join("").split_whitespace().nth(0).expect("获取总star数失败").to_string();
            let text = text.replace(",", "");
            print!("总star数: {text}")
        }
        println!("")
    }

    Ok(())
}

输出如下:

仓库链接: /llenotre/maestro 今日star: 232 star: 2187
仓库链接: /rustls/rustls 今日star: 20 star: 5077
仓库链接: /aptos-labs/aptos-core 今日star: 1 star: 5599
仓库链接: /microsoft/windows-rs 今日star: 15 star: 9291
....省略其他....

如果上面的代码没有输出了,可能是Github换前端样式了,爬虫运行一段时间不生效是很正常的事情。

获取数据

如果只是获取没有太复杂的反爬机制的网页还是很简单的,通过http客户端构造一个请求就能获得网页内容了,反爬机制有很多,反反爬机制也有很多,这里简单说一个,代理。

最简单的办法就是在命令行设置http_proxy或者https_proxy变量,这个变量对于大多数http库有效,包括reqwest

export http_proxy=http://127.0.0.1:18080

至于代理从何而来,可以去爬提供代理的网页呀^_^这里就不展开了,爬代理池主要注意的是定时检查代理是否还有效。

内容解析

由程序生成和阅读的数据总是是格式化的数据,所以一定有固定的格式,网页也不例外,网页一般是HTML(也有直接返回txt格式的网页)。

定位HTML页面的各个元素一般有两种语法,XPath和CSS选择器, 我比较喜欢后者,本文涉及的第三方库scraper也支持这个语法,CSS选择器的语法可参考这个链接: https://www.runoob.com/cssref/css-selectors.html

下面是官方的几个示例。

获取列表元素

use scraper::{Html, Selector};

let html = r#"
    <ul>
        <li>Foo</li>
        <li>Bar</li>
        <li>Baz</li>
    </ul>
"#;

let fragment = Html::parse_fragment(html);
let selector = Selector::parse("li").unwrap();

for element in fragment.select(&selector) {
    assert_eq!("li", element.value().name());
}

select总是返回一个迭代器,所以需要调用相关迭代器的方法访问其中的元素或者使用for循环依次遍历。

获取元素属性

常见的属性有classhref, 总的来说,除了元素名, 包裹的文本或html内容之外哪些键值对都是熟悉, 比如这里的name="foo"value="bar"

use scraper::{Html, Selector};

let fragment = Html::parse_fragment(r#"<input name="foo" value="bar">"#);
let selector = Selector::parse(r#"input[name="foo"]"#).unwrap();

let input = fragment.select(&selector).next().unwrap();
assert_eq!(Some("bar"), input.value().attr("value"));

获取元素文本

scraper会递归的获取指定元素的文本以及包含子节点的所有文本,所以返回一个列表这并不奇怪。

use scraper::{Html, Selector};

let fragment = Html::parse_fragment("<h1>Hello, <i>world!</i></h1>");
let selector = Selector::parse("h1").unwrap();

let h1 = fragment.select(&selector).next().unwrap();
let text = h1.text().collect::<Vec<_>>();

assert_eq!(vec!["Hello, ", "world!"], text);

总结

随着反爬反反爬技术的不断碰撞,大多数爬虫项目的难点在于破解数据的加密措施而非解析页面然后获取数据,由于本人对反反爬的技术不是太精通,所以这里只是简单的介绍了一下相关第三方库, reqwestscraper

参考链接