前言
好久没更新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
            
            
              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命令,可以热重载。
