前言
好久没更新tauri了,笔者最近闲的无聊,搞搞爬虫,比如
spiderdemo第四题-CSDN博客
https://blog.csdn.net/qq_63401240/article/details/153880866上面博客里面,笔者使用DP这个工具
DrissionPage官网
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的教程。
正文
建立一个加载html的tauri项目
直接给出文件及其内容
cargo.toml
rust
[package]
name = "html-t"
edition = "2024"
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = "2"
src/main.rs
rust
#![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
rust
{
"$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
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__.invoke
const form = document.querySelector('#form')
const nameEl = document.querySelector('#name')
const messageEl = document.querySelector('#message')
form.addEventListener('submit', async (e) => {
e.preventDefault()
const name = nameEl.value
const newMessage = await invoke('greet', { name })
messageEl.textContent = newMessage
})
</script>
</body>
</html>
rust
fn main(){
tauri_build::build();
}
编译项目。
文件目录如下

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

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


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

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

使用的加密库都写出来了。
这就很简单了,笔者专门选这道题,主要是因为很简单。哈哈哈哈哈哈。
下面对关键代码进行分析
rust
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
直接给出代码
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 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的触发
javascript
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结构体
需要实现反序列化,
rust
#[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,
}
返回值------返回一个数字
因此,定义的通信函数如下
rust
#[tauri::command]
async fn get_result(params: Params,page:u32)->Result<u32, String>{
}
名字任取,笔者取名get_result。
前端加密
直接给出代码
javascript
<script type="module">
const invoke = window.__TAURI_INTERNALS__.invoke
async 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
直接给出代码
javascript
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又是一个列表,因此,定义一个结构体处理响应的数据
javascript
#[derive(Serialize,Deserialize,Debug)]
struct Response {
page_data:Vec<u32>
}
发送请求
来到最关键的一步,首先,确定发送get请求,需要传入页数和加密参数,考虑处理结果,考虑请求头,这里不多废话,前面已经做好铺垫,直接给出代码
javascript
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)
}
完善通信函数
注册通信函数,完善代码如下
javascript
#[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的代码如下
javascript
#![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的代码如下
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__.invoke
async 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命令,可以热重载。
