Skip to content

浏览器探测实战

浏览器探测用于处理以下场景:

  • 页面依赖前端 JavaScript 渲染(SPA / 动态加载)
  • 需要用户完成登录或验证(验证码、Cloudflare 防护)
  • 登录态由 HttpOnly Cookie 维护
  • 需要执行页面内置的解密逻辑

架构概述

书源 JS (Boa)
    ↓ legado.browser.*
Rust 后端
    ↓ 管理 WebView 生命周期
探测 WebView (独立 profile)
    ↓ JS eval
网页上下文 (document, window)
  • 探测 WebView 使用 <AppDataDir>/browser_probe_profile/ 独立存储
  • 与主程序 UI WebView 完全隔离
  • 所有书源和扩展共享探测 profile(Cookie、LocalStorage 等共享)

init() 自动调用

GUI 模式下,每个书源维护一个独立的长期 Boa Context。书源加载后如果存在无参 init() 函数,引擎会自动调用一次:

js
var browserId = '';

function init() {
  // 预创建探测会话
  browserId = legado.browser.acquire('main', { visible: false });
  legado.log('[init] browserId=' + browserId);
}

function search(keyword, page) {
  // 复用 init 中创建的会话
  var url = BASE + '/search?q=' + encodeURIComponent(keyword);
  legado.browser.navigate(browserId, url, { waitUntil: 'networkidle' });
  return legado.browser.eval(browserId, '...');
}

书源文件修改后会自动重建 Context 并重新执行 init()

场景一:动态渲染页面

页面内容由 JS 动态填充,普通 HTTP GET 只能获取空壳 HTML:

js
function chapterContent(chapterUrl) {
  // 使用 run() 一次性完成
  return legado.browser.run(chapterUrl, `
    // 等待内容加载
    await new Promise(function(resolve) { setTimeout(resolve, 1000); });
    return document.querySelector('#content')?.innerText || '';
  `, { visible: false, waitUntil: 'load' });
}

部分站点需要登录才能访问:

js
function ensureLogin() {
  // 检查是否已有有效 Cookie
  var cookies = legado.browser.cookies(BASE);
  var hasToken = false;
  for (var i = 0; i < cookies.length; i++) {
    if (cookies[i].name === 'auth_token') { hasToken = true; break; }
  }

  if (!hasToken) {
    // 弹出窗口让用户手动登录
    var ok = legado.browser.open(BASE + '/login');
    if (!ok) throw new Error('登录未完成');
  }
}

场景三:多步骤会话(保持状态)

需要在多个页面间保持会话(如翻页、表单提交):

js
function chapterContent(chapterUrl) {
  if (!globalThis.contentBrowserId) {
    globalThis.contentBrowserId = legado.browser.create({ visible: false });
  }
  var id = globalThis.contentBrowserId;

  try {
    legado.browser.navigate(id, chapterUrl, { waitUntil: 'load' });
    return legado.browser.eval(id, `
      await new Promise(r => setTimeout(r, 500));
      return document.querySelector('#content')?.innerText || '';
    `);
  } catch (e) {
    // 出错时清理会话
    legado.browser.close(id);
    globalThis.contentBrowserId = '';
    throw e;
  }
}

waitUntil 选择指南

适用场景
'load'大多数情况,页面 onload 后内容已就绪
'domcontentloaded'只需 DOM 结构,不等待图片等资源
'networkidle'SPA 页面,需等待 AJAX 请求完成

调试技巧

在「设置 → 网络 → 浏览器探测」中开启:

  • 调试:强制显示隐藏窗口:即使 visible: false,窗口也会显示
  • 一次性 legado.browser.run() 的窗口会保留不关闭
  • 便于观察页面跳转、验证码、JS 执行过程

基于 MIT 许可发布