【Tauri2】050——加载html和rust爬虫
前言
好久没更新tauri了,笔者最近闲的无聊,搞搞爬虫,比如
spiderdemo第四题-CSDN博客 https://blog.csdn.net/qq_63401240/article/details/153880866上面博客里面,笔者使用DP这个工具
https://blog.csdn.net/qq_63401240/article/details/153880866上面博客里面,笔者使用DP这个工具
DrissionPage官网 https://www.drissionpage.cn/笔者使用这个工具的目的
https://www.drissionpage.cn/笔者使用这个工具的目的
- 加载html
- 执行js
笔者突然想到,tauri好像也可以加载html,然后就有个想法,试试rust爬虫,使用tauri加载html页面,执行加密函数,获取加密参数,发送请求。
感觉没问题。
那么,就以spiderdemo的第21题为例子。
T21-摘要算法 https://www.spiderdemo.cn/authentication/hash_challenge/?challenge_type=hash_challenge编写rust爬虫,顺便作为tauri的教程。
https://www.spiderdemo.cn/authentication/hash_challenge/?challenge_type=hash_challenge编写rust爬虫,顺便作为tauri的教程。
正文
建立一个加载html的tauri项目
直接给出文件及其内容
cargo.toml
[package]
name = "html-t"
edition = "2024"[build-dependencies]
tauri-build = { version = "2", features = [] }[dependencies]
tauri = "2"
src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]#[tauri::command]
fn greet(name: &str) -> String {format!("Hello {name}, You have been greeted from Rust!")
}fn main() {tauri::Builder::default().invoke_handler(tauri::generate_handler![greet]).run(tauri::generate_context!()).expect("error while running tauri application");
}
tauri.config.json
{"$schema": "https://schema.tauri.app/config/2","productName": "start","version": "0.1.0","identifier": "com.start.app","build": {"frontendDist": ["./frontend/index.html"]},"app": {"windows": [{"label": "main","title": "start","width": 800,"height": 600,"theme": "Dark"}],"security": {"csp": null}},"bundle": {"active": true,"targets": "all"}
}
icons/icon.ico

frontend/index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Welcome to Tauri!</title></head><body><h1>Welcome to Tauri!</h1><form id="form"><input id="name" placeholder="Enter a name..." /><button>Greet</button></form><p id="message"></p><script>const invoke = window.__TAURI_INTERNALS__.invokeconst form = document.querySelector('#form')const nameEl = document.querySelector('#name')const messageEl = document.querySelector('#message')form.addEventListener('submit', async (e) => {e.preventDefault()const name = nameEl.valueconst newMessage = await invoke('greet', { name })messageEl.textContent = newMessage})</script></body>
</html>build.rs
fn main(){tauri_build::build();
}编译项目。
文件目录如下

gen目录是自动生成的,里面是和权限有关的文件。
总之,运行
cargo run
结果如下

没问题,很好。
爬虫分析
进入spiderdemo第21题的页面后,直言的说,多次点击下一页,可以发现有四个参数进行了加密


两个在请求头中,两个是请求参数。
搜索关键字——x-request-token

直接就发现了关键信息,可以发现
hmac、md5、sha256、sha3_256四种加密方式
所在的文件是hask_challengen.js,把这个文件下载下来。
而且查看页面源代码ctrl+u,可以发现

使用的加密库都写出来了。
这就很简单了,笔者专门选这道题,主要是因为很简单。哈哈哈哈哈哈。
下面对关键代码进行分析
  const t = e.url.match(/\/page\/(\d+)\//);if (t) {const n = parseInt(t[1]),s = new URLSearchParams(e.url.split("?")[1] || "").get("challenge_type") || "hash_challenge",a = Date.now(), o = r(n, s, a);e.headers["X-Request-Token"] = o.hmac, e.headers["X-Verify-Code"] = o.md5;const c = e.url.includes("?") ? "&" : "?";e.url += `${c}sign=${o.sha256}&code=${o.sha3_256}&t=${a}`}首先,t是什么?可以看出显示t是页数page。但是t是一个字符串
然后解析成数字,n也是页数,n是数字。
s说白就是hash_challenge这个字符串,
a是13位的时间戳,
o是r函数返回,r是什么???

r说白了,就是调用加密函数,很好。
既然如此,笔者可以把r这个函数,变成window对象,即
window.r=r

编写html
经过对js的分析,很容易得到
X-Request-Token使用hmac,X-Verify-Code使用md5,sign使用sha256,code使用sha3_256
直接给出代码
<!doctype html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>spiderdemo t21</title></head><script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/js-sha3/0.8.0/sha3.min.js"></script><script src="hash_challenge.js"></script><script>function handleClicked(){let page=1;let s = "hash_challenge";let a = Date.now();let o = window.r(page, s, a);let result={"X-Request-Token":o.hmac,"X-Verify-Code":o.md5,"sign":o.sha256,"code":o.sha3_256}console.log(result)}</script><body><button onclick="handleClicked()">确定</button></body>
</html>可以先直接运行html文件,结果如下

可以发现得到了参数。
最后,修改获取参数的方法,通过tauri的触发
    function get_encrypt_result(page, timestamp) {let s = "hash_challenge";let o = window.r(page, s, timestamp);return {"X-Request-Token": o.hmac,"X-Verify-Code": o.md5,"sign": o.sha256,"code": o.sha3_256,"t":timestamp.toString()}}需要传入两个参数,一个page,另一个timestamp
Rust爬虫
想简单点,必然需要调用get_encrypt_result方法
其实,笔者在这里考虑了许久,笔者希望tauir调用js,虽然可以调用js,但是无法回去调用后的返回值。
如果硬要获取,感觉很麻烦,笔者没尝试。
最后笔者还是决定使用这个过程
- 前端执行加密函数,获取参数
- 把参数通过通信函数传入到rust
- rust中根据参数编写爬虫
- 爬虫结果返回到前端
rust要编写爬虫,不得不使用reqwest 库
即
reqwest = {version = "0.12.24",features = ["json", "cookies"] }
写通信函数
考虑通信函数的参数,第一个参数是前面的一个对象,里面有5个参数,然后把页数也作为参数
因此,首先,先定义一个Params结构体
需要实现反序列化,
#[derive(Debug, Deserialize)]
pub struct Params {#[serde(rename = "X-Request-Token")]pub x_request_token:String,#[serde(rename = "X-Verify-Code")]pub x_verify_code: String,pub sign: String,pub code: String,pub t:String,
}返回值——返回一个数字
因此,定义的通信函数如下
#[tauri::command]
async fn get_result(params: Params,page:u32)->Result<u32, String>{
}名字任取,笔者取名get_result。
前端加密
直接给出代码
<script type="module">const invoke = window.__TAURI_INTERNALS__.invokeasync function get_params() {let total=0;for (let page = 1; page < 2; page++) {let params = get_encrypt_result(page, Date.now())let one_page_result=await invoke("get_result",{params,page})total+=one_page_result}return total}get_params().then(data=>alert(data))
</script>定义了一个get_params函数,循环100页,笔者这里只是获取第一页,这不重要,后面改一下页数即可。
调用加密,调用通信函数,返回一页的求和结果,加到最后的结果上。
可以在通信函数里面打印一下params
println!("{params:#?}");
如下

没问题
设置请求头
需要使用reqwest里面的headermap
直接给出代码
use reqwest::header::{HeaderMap,HeaderValue};
fn get_headers(params: &Params) -> HeaderMap {let mut headers = HeaderMap::new();headers.insert("Cookie",HeaderValue::from_static("sessionid=你的sesssionid"));headers.insert("X-Request-Token",HeaderValue::from_str(¶ms.x_request_token).unwrap());headers.insert("X-Verify-Code",HeaderValue::from_str(¶ms.x_verify_code).unwrap());headers.insert("User-Agent",HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0"));headers
}出来响应
爬虫获取结果之后,怎么处理数据

可以发现,需要page_data里面的数据,而这个page_data又是一个列表,因此,定义一个结构体处理响应的数据
#[derive(Serialize,Deserialize,Debug)]
struct Response {page_data:Vec<u32>
}发送请求
来到最关键的一步,首先,确定发送get请求,需要传入页数和加密参数,考虑处理结果,考虑请求头,这里不多废话,前面已经做好铺垫,直接给出代码
async fn get_data(params: &Params,page:u32) -> Result<u32, Box<dyn std::error::Error>> {let url = format!("https://www.spiderdemo.cn/authentication/api/hash_challenge/page/{}/",page);let client = Client::new();let resp = client.get(url).headers(get_headers(params)).query(&[("challenge_type", "hash_challenge"),("t", ¶ms.t),("code", ¶ms.code),("sign",¶ms.sign)]).send().await?.json::<Response>().await?;let total: u32 = resp.page_data.iter().sum();println!("total: {}", total);Ok(total)
}完善通信函数
注册通信函数,完善代码如下
#[tauri::command]
async fn get_result(params: Params,page:u32)->Result<u32, String>{println!("{params:#?}");let total=get_data(¶ms,page).await.expect("Error getting data");Ok(total)
}运行
结果如下

没问题,那么,获取100页的数据,提交。
 没问题。
没问题。
代码
main.rs的代码如下
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use serde::{Deserialize, Serialize};
use reqwest::Client;
use reqwest::header::{HeaderMap,HeaderValue};#[derive(Debug, Deserialize)]
pub struct Params {#[serde(rename = "X-Request-Token")]pub x_request_token:String,#[serde(rename = "X-Verify-Code")]pub x_verify_code: String,pub sign: String,pub code: String,pub t:String,
}
#[derive(Serialize,Deserialize,Debug)]
struct Response {page_data:Vec<u32>
}
fn get_headers(params: &Params) -> HeaderMap {let mut headers = HeaderMap::new();headers.insert("Cookie",HeaderValue::from_static("sessionid=ub6zyoe3zq0wjgeeyf7ucr0dlso5zgpu"));headers.insert("X-Request-Token",HeaderValue::from_str(¶ms.x_request_token).unwrap());headers.insert("X-Verify-Code",HeaderValue::from_str(¶ms.x_verify_code).unwrap());headers.insert("User-Agent",HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0"));headers
}
async fn get_data(params: &Params,page:u32) -> Result<u32, Box<dyn std::error::Error>> {let url = format!("https://www.spiderdemo.cn/authentication/api/hash_challenge/page/{}/",page);let client = Client::new();let resp = client.get(url).headers(get_headers(params)).query(&[("challenge_type", "hash_challenge"),("t", ¶ms.t),("code", ¶ms.code),("sign",¶ms.sign)]).send().await?.json::<Response>().await?;let total: u32 = resp.page_data.iter().sum();Ok(total)
}#[tauri::command]
async fn get_result(params: Params,page:u32)->Result<u32, String>{let total=get_data(¶ms,page).await.expect("Error getting data");Ok(total)
}fn main() {tauri::Builder::default().invoke_handler(tauri::generate_handler![get_result]).run(tauri::generate_context!()).expect("error");
}index.html的代码如下
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>spiderdemo t21</title>
</head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-sha3/0.8.0/sha3.min.js"></script>
<script src="hash_challenge.js"></script>
<script>function get_encrypt_result(page, timestamp) {let s = "hash_challenge";let o = window.r(page, s, timestamp);return {"X-Request-Token": o.hmac,"X-Verify-Code": o.md5,"sign": o.sha256,"code": o.sha3_256,"t":timestamp.toString()}}
</script><script type="module">const invoke = window.__TAURI_INTERNALS__.invokeasync function get_params() {let total=0;for (let page = 1; page < 101; page++) {let params = get_encrypt_result(page, Date.now())let one_page_result=await invoke("get_result",{params,page})total+=one_page_result}return total}get_params().then(data=>alert(data))
</script>
<body>
</body>
</html>总结
感觉搞复杂了,在爬虫层面,没有使用python简单,但是使用tauri作为作为执行js并且能交互rust的工具,还是没问题。
最后,推荐下载tauri-cli这个依赖库
cargo install tauri-cli

然后可以执行cargo tauri dev命令,可以热重载。

