Skip to content
On this page

获取网页内容

获取网页内容是一个非常常见的需求,这里我们使用简单的案例演示如何使用 MB 获取网页内容

概述

获取网页内容一般有两种思路,一是使用接口 mbGetSource 获取网页的 HTML 字符串,再用 C++ 对这个 HTML 进行解析和处理,二是使用 JS 获取页面内容,再传回 C++。

除非需求特别简单,建议使用第二种,原因如下:

  1. JS 拥有丰富的 DOM 接口可以对页面进行操作,而 C++ 等需要引入 HTML 解析库或手写字符串解析进行获取,另外无法在 HTML 中直接体现的属性则无力获取,更不用说后续可能还要操作页面。
  2. 对于异步或者一些单页面网页,通过接口很难判断需要的内容是否加载完成。而 JS 却有多种手段,如循环检查,注册事件,甚至可以重写内置方法实现 hook。

使用第二种方式,首先需要在页面上执行自己的 JS,然后在 JS 获得数据之后与 C++ 通信。

如何在页面上执行自己的 JS

在之前的快速开始中,我们使用了 mbRunJs(webView, frameId, "alert(1);", true, nullptr, nullptr, nullptr); 直接执行 JS 代码片段 alert(1);,但是直接使用 JS 字符串比较麻烦,比如需要处理一些转义。

假如我们有一个 JS 文件,想引入到一个页面,如何操作呢?利用一个简单的 JS 技巧就可以完成:

cpp
mbRunJs(webView, frameId, "var script = document.createElement('script');script.src = 'file:///C:/xx_dir/test.js';document.getElementsByTagName('body')[0].appendChild(script);", true, nullptr, nullptr, nullptr);

里面的 JS 代码格式化之后就是:

javascript
var script = document.createElement('script');
script.src = 'file:///C:/xx_dir/test.js'; 
document.getElementsByTagName('body')[0].appendChild(script);

其中 src 可以修改为一个合法的本地路径,网络路径也可。

当然执行自己的 JS 还有其它办法,如修改网络请求等,此处不再赘述。

JS 获得数据之后如何与 C++ 通信

mbRunJs 执行代码片段获得 JS 数据

首先,mbRunJs 可以配置参数 mbRunJsCallback 设置回调,案例如下:

调用 mbRunJs

cpp
mbRunJs(webView, frameId, "return 999 + 1;", true, onRunJsCallback, nullptr, nullptr);

onRunJsCallback 如下:

cpp
void MB_CALL_TYPE onRunJsCallback(mbWebView webView, void* param, mbJsExecState es, mbJsValue v)
{
    double value = mbJsToDouble(es, v);
}

此时 value 值为 1000.0,注意需要调用 mbJsToDoublembJsToBooleanmbJsToString 等函数从 JS 获取值,如果不确定是什么类型,可以使用 mbGetJsValueType

JS 在任意时刻与 C++ 通信

对于异步或者一些单页面网页,执行完 JS 片段不能立即返回所需结果,或者我们使用了插入 script 标签的办法执行 JS,这要求 JS 能在任意时刻与 C++ 通信。

使用 mbOnJsQuery 注册 JS 通知的回调

cpp
mbOnJsQuery(mbView, onJsQueryCallback, nullptr);

onJsQueryCallback 为:

cpp
void MB_CALL_TYPE onJsQueryCallback(mbWebView webView, void* param, mbJsExecState es, int64_t queryId, int customMsg, const utf8* request)
{
    constexpr int postForSomething = 0;

    if (customMsg == postForSomething)
    {
        std::string requestStr(request);
        // do someting
        mbResponseQuery(webView, queryId, postForSomething, "responseData"); 
    }
}

利用上一节的办法加载一个 JS 文件进入网页,内容为:

javascript
var POST_FOR_SOMETHING = 0;

setTimeout(function() {
    window.mbQuery(POST_FOR_SOMETHING, "requestData", function(customMsg, response) {
        // do something
    });
}, 2000);

这里模拟了一个异步操作,2秒之后 JS 使用 window.mbQuery 调用 C++ 。注意,window.mbQuery 为 MB 注入进页面的函数,直接调用即可。

此时 onJsQueryCallback 接收到参数,其中 customMsg 为 0,request 为字符串 "requestData"。如果要传结构体,可以使用 JSON,两边各自解析处理即可。

类似的,mbResponseQuery 也可以向 JS 的回调返回内容。

如果想深入了解如何实现 C++ 与 JS 绑定,可以参考 Javascript 和 C++ 绑定如何实现(WIP)

获取页面内容

了解前面两节的内容之后,如何获取页面内容的问题实际变成了如何通过 JS 获取页面内容的问题,深入讨论 JS 超出了本文的范围,这里只举一个简单案例。

举例:获取 MB 接口文档 中所有的接口函数名。

javascript
// 简单案例演示,不考虑错误路径

function collectionToArr(collection) {
    return [].slice.call(collection);
}

// "void mbInit(const mbSettings* settings)" => "mbInit"
function getFunctionName(str) {
    var match = str.match(/(mb[A-Za-z0-9]*)\(/);
    return match[1] ? match[1] : "";
}

function calcMBFunctionNameArr() {
    var main = document.getElementById("content");
    var h4Arr = main.getElementsByTagName("h4");
    return collectionToArr(h4Arr).map(function(ele){return getFunctionName(ele.innerText)});
}

var nameArr = calcMBFunctionNameArr();
var requestData = JSON.stringify({
    code: 0,
    data: nameArr
});

// post requestData to C++

现在 requestData 是 JSON 字符串,可以通过上一节介绍的方法传入 C++ 中。

如果不太擅长使用 JS 语法,建议查阅 MDN