【Tauri2】050——加载html和rust爬虫

前言

好久没更新tauri了,笔者最近闲的无聊,搞搞爬虫,比如

spiderdemo第四题-CSDN博客https://blog.csdn.net/qq_63401240/article/details/153880866上面博客里面,笔者使用DP这个工具

DrissionPage官网https://www.drissionpage.cn/笔者使用这个工具的目的

  1. 加载html
  2. 执行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>

build.rs


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,但是无法回去调用后的返回值。

如果硬要获取,感觉很麻烦,笔者没尝试。

最后笔者还是决定使用这个过程

  1. 前端执行加密函数,获取参数
  2. 把参数通过通信函数传入到rust
  3. rust中根据参数编写爬虫
  4. 爬虫结果返回到前端

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(&params.x_request_token).unwrap());
    headers.insert("X-Verify-Code",HeaderValue::from_str(&params.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", &params.t),
            ("code", &params.code),
            ("sign",&params.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(&params,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(&params.x_request_token).unwrap());
    headers.insert("X-Verify-Code",HeaderValue::from_str(&params.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", &params.t),
            ("code", &params.code),
            ("sign",&params.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(&params,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命令,可以热重载。

相关推荐
兰雪簪轩5 小时前
所有权与解构:一次把“拆”与“留”写进类型系统的旅程 ——从语法糖到零拷贝 AST
rust
字节逆旅5 小时前
介绍一个小工具-pake
rust
Zhangzy@6 小时前
仓颉的空安全基石:Option类型的设计与实践
java·开发语言·安全
oioihoii6 小时前
Rust中WebSocket支持的实现
开发语言·websocket·rust
陪我一起学编程7 小时前
Rust 不可变借用:从规则约束到内存安全的深度思考
后端·rust·编程语言
明道源码8 小时前
Kotlin Multiplatform 跨平台方案解析以及热门框架对比
开发语言·kotlin·cocoa
fie88898 小时前
C#实现连续语音转文字
开发语言·c#
一念&10 小时前
每日一个C语言知识:C 头文件
c语言·开发语言·算法
DARLING Zero two♡11 小时前
仓颉GC调优参数:垃圾回收的精密控制艺术
开发语言·仓颉