【实战】基于 Tauri 和 Rust 实现基于无头浏览器的高可用网页抓取

一、背景

在 Saga Reader 的早期版本中,存在对网页内容抓取成功率不高的问题。主要原因是先前采用的方案为后台进程通过 reqwest 直接发起 GET 请求获取网站 HTML 的方案,虽然仿真了Header内容,但仍然会被基于运行时的反爬机制(如 Browser指纹交叉验证、运行时行为识别、动态渲染等)所屏蔽。这导致我们无法稳定、可靠地获取内容,影响应用的可用性。

为了解决这一痛点,我们优化了更新机制。利用 Tauri 提供的 WebView(在此场景下作为无头浏览器使用)来模拟真实用户访问,并注入定制化的 JavaScript 脚本来精确抓取所需的 DOM 内容。这种方法能够有效对抗大多数常见的反爬虫策略,显著提升抓取成功率。

同时,我们也希望应用能够在系统启动时自动在后台执行 Feed 更新任务,而无需立即显示主窗口,从而提供更流畅的“静默更新”体验。

关于Saga Reader

基于Tauri开发的开源AI驱动的智库式阅读器(前端部分使用Web框架),能根据用户指定的主题和偏好关键词自动从互联网上检索信息。它使用云端或本地大型模型进行总结和提供指导,并包括一个AI驱动的互动阅读伴读功能,你可以与AI讨论和交换阅读内容的想法。

这个项目我5月刚放到Github上(Github - Saga Reader),欢迎大家关注分享。🧑‍💻码农🧑‍💻开源不易,各位好人路过请给个小星星💗Star💗。

核心技术栈:Rust + Tauri(跨平台)+ Svelte(前端)+ LLM(大语言模型集成),支持本地 / 云端双模式

关键词:端智能,边缘大模型;Tauri 2.0;桌面端安装包 < 5MB,内存占用 < 20MB。

运行截图
在这里插入图片描述

二、架构概览

新方案的整体架构围绕 Tauri 应用的核心组件构建,旨在实现高效、可靠的后台 Feed 更新和内容抓取。主要组件及其交互如下:

  1. 主进程 (Rust Backend):

    • 应用生命周期管理: 控制应用的启动、后台运行、窗口显隐及退出。
    • Feed 更新调度器 (feeds_update.rs): 核心的后台任务模块,负责定时唤醒、管理 Feed 更新队列、并发控制。
    • 无头 WebView 管理: 为每个 Feed 源创建和管理一个隐藏的 WebView 实例。
    • JavaScript 注入与通信: 通过 WebView API 向加载的页面注入抓取脚本,并接收脚本返回的数据。
    • 文件锁 (feeds_schedule_update.lock):确保同一时间只有一个应用实例在执行 Feed 更新调度。
    • 状态管理与前端接口: 维护后台任务状态,并通过 Tauri 的 invoke 和事件机制与前端 Svelte UI 通信。
    • 配置与环境 (env.rs, tauri.conf.json): 读取配置,判断运行模式(例如,是否后台启动)。
  2. Tauri WebView (Headless Instance):

    • 网页加载: 加载目标 Feed 源的 URL。
    • JavaScript 执行环境: 执行注入的抓取脚本,模拟用户交互(如果需要),解析 DOM。
  3. 抓取脚本 (JavaScript):

    • DOM 解析: 负责在 WebView 加载的页面中定位和提取所需的 Feed 内容(如文章列表、标题、链接、日期、正文摘要等)。
    • 数据格式化: 将提取的数据整理成结构化格式,返回给 Tauri 主进程。
  4. Svelte 前端 UI (tasks.svelte.ts, etc.):

    • 用户界面: 展示 Feed 内容、更新状态、提供用户操作入口。
    • 与后端通信: 通过 Tauri API 调用后端命令、监听后端事件,实现数据同步和交互。
  5. 数据存储 (Implicit):

    • 抓取到的 Feed 数据最终会被存储(例如,在本地数据库或文件中),供用户阅读。

交互流程简介:应用启动时,主进程根据配置判断是否进入后台更新模式。Feed 更新调度器定时触发,为每个 Feed 创建无头 WebView 实例,加载对应网页,注入抓取脚本。脚本执行后将数据返回主进程,主进程处理数据并更新前端 UI(如果可见)或存储数据。

三、核心技术实现与亮点

1. 应用主进程的后台化与主窗口延迟显示

新版本不再依赖单独的守护进程,而是让应用主进程本身支持后台运行。这主要通过以下方式实现:

  • 配置文件修改:在 中,将主窗口的 visible 属性设置为 false
    "windows": [
      {
         
        "label": "main",
        "visible": false, // 默认不显示主窗口
        ...
      }
    ]
    
  • 启动逻辑调整:在 的 app_setuprun_event_loop 函数中处理应用启动和事件循环。app_setup 负责初始化,而 run_event_loop 中的 RunEvent::Reopen 事件处理允许用户在应用已后台运行时,通过点击 Dock 图标或其他方式重新打开主窗口。
    • is_daemon_mode 函数(位于 )用于判断当前是否应以“守护进程”模式(即后台模式)启动,这可能基于命令行参数或特定环境变量。
    • 在 的 run 函数中,会根据 is_daemon_mode 的结果来决定是否立即显示窗口或执行其他后台初始化逻辑。

代码分析
修改 tauri.conf.json 是最直接的方式来控制窗口的初始可见性。Rust 代码层面,app_setup 钩子在 Tauri 应用初始化时运行,适合执行一些全局设置。run_event_loop 则处理应用运行期间的各种事件,特别是 RunEvent::Reopen,它使得即使用户关闭了所有窗口(在 macOS 上应用通常不会退出),或者应用以不可见模式启动,也能响应用户的重新打开请求,显示主窗口。

2. 基于 Tauri 无头 WebView 的智能抓取

这是解决反爬虫问题的关键。我们利用 Tauri 的 WebView 来加载和执行 JavaScript,模拟真实用户环境。

  • 创建隐藏 WebView:虽然 Tauri 的底层Tao和Wry并没有实现 headless 机制,但我们可以在应用层创建一个程序化控制的、不实际显示给用户的 WebView 窗口。这个窗口加载目标网页。
  • JavaScript 注入与执行:一旦页面加载完成(或达到某个特定状态,如 DOMContentLoaded),通过 Tauri 的 window.eval()webview.execute_script() 方法注入自定义的 JavaScript 代码。这个脚本被设计用来:
    1. 定位元素:使用 document.querySelector, document.querySelectorAll 等标准 DOM API 找到包含 Feed 条目、标题、链接、日期、摘要等的 HTML 元素。
    2. 提取数据:获取这些元素的 innerText, href 等属性。
    3. 处理动态内容:如果内容是动态加载的,脚本可能需要等待特定条件或模拟某些用户操作(如点击“加载更多”)来获取完整数据。
    4. 返回结果:将提取并结构化的数据通过 Promise 或 Tauri 的 IPC 机制返回给 Rust 后端。

代码分析
核心逻辑位于 "feeds_update.rs"

// 伪代码,示意 feeds_update.rs 中的逻辑
async fn fetch_feed_content(app_handle: &AppHandle, url: &str, script: &str
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值