跳转至

开源阅读网络请求说明

预计阅读时长 : 14 分钟

请求流程

Legado 通过书源配置,构建了一个连贯的网络请求链条,链条中的每一步都会将提取的 url 以 baseUrl 字符串传递给下一步,同时通过 result 给下一步传递 url 返回的响应内容以供内容提取

graph LR
    A[基本 - 源域名+请求头] --> B(搜索地址) --> C(搜索 - 详情地址)
    A --> D(发现地址) --> E(发现 - 详情地址)
    C --> F[详情 - 目录地址]
    E --> F
    F --> G(目录 - 章节地址 + 翻页规则)
    G --> H(章节 - 章节规则 + 翻页规则)

流程图说明

  • 为了更加清晰的展示网络请求的流程,这里将描述的文字进行了部分调整,未与配置界面一一对应。
  • 实际上,搜索地址搜索 Tab 中,发现地址发现 Tab 中,从理解的角度上其实放入 基本 Tab 中更为合适。
  • 章节 实际对应的是 正文 Tab,章节规则 对应的是 正文规则

针对不同类型的网络请求,也可以通过给 url 配置对应的变量以及 JSON 化的请求参数,精细化的调整请求类型和请求方法。

常用变量

网络请求中的最常用的两大变量分别是 baseUrlresult,它们分别代表了请求的链接地址和请求的响应结果。

  • baseUrl : 上一步生成的当前步骤的原始 url
    • 详情 Tab 的 baseUrl 就是由 搜索 - 详情地址 或者 发现 - 详情地址 中的规则提取的。
  • result : 上一步网络请求的原始响应结果
    • 独立使用代表当前步骤 baseUrl 的响应内容。
    • 在组合规则中使用代表上一步规则的结果,例如 @css:a@href@js:result+','+JSON.stringify({"webView": true}); 中的 result 就是上一步 @css:a@href 抓取到的 href 链接的值。
    • 底层逻辑是通过 Promise 异步请求的 .then 处理程序(handler)链进行传递 result

其他的常用变量值还包括:

  • java : 当前类
  • cookie : cookie 操作类
  • cache : 缓存操作类
  • source : 书源类
  • book : 书籍类
  • src : 内容源码
  • title : 当前标题
  • chapter : 当前目录类
  • nextChapterUrl : 下一章节 url

请求方式

普通请求

一般情况下,网络请求使用最普通的 GET 方法即可,只需要在对应的属性值中直接填入 URL 即可。

https://www.baidu.com

如果部分页面对于使用的客户端有限制,例如只允许在手机上进行访问,那么可以尝试在 基础 - 请求头 中使用 JSON 格式增加客户端标识。

1
2
3
{
  "User-Agent": "Mozilla/5.0 (Linux; Android 10; PACM00 Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.79 Mobile Safari/537.36"
}

模拟浏览器请求

上述格式的 URL 使用的是后面提到的 AnalyzeUrl 库。在某些网站的反扒机制中,此类网络需求无法获取到期望的内容。

这种情况下,我们可以使用 BackstageWebView 库,即通过在链接后的 ,{} 中添加参数 "webView": true 来调用 webView 模拟浏览器行为,以规避反扒机制。

https://www.baidu.com,{"webView": true}

如果是其他的步骤中的网络请求,例如 目录-章节地址 中提取出来的 url,则可以通过 JS 拼接出带有参数的 url,来实现 webView 方式访问。

添加 webView 参数示例
@css:a@href@js:result+','+JSON.stringify({"webView": true});

请求参数

这里的 ,{} 中填写的就是 URL 参数,完整的 URL 参数格式如下:

data class UrlOption(
    val webView: Any?,
    val charset: String?,
    val headers: Any?,
    val method: String?,
    val body: Any?,
    val type: String?,
    val js: String?,
    val retry: Int = 0  //重试次数
)

刚刚我们看了 webView 参数的作用,下面我们来看看如何通过其他参数配置,来实现更为复杂的网络调用。

配置 charset

charset 为 utf-8 时可省略,一般获取的内容为乱码时,可以考虑配置 charset 为 gbk

https://www.baidu.com,{"charset": "GBK"}

简单 header 配置可以直接通过字符串在参数中进行赋值,如果内容比较多或者比较复杂,也可以使用 JSON.stringify 来进行拼接。

1
2
3
4
5
6
@js:
let ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36";
let headers = {
  "headers": {"User-Agent": ua}
};
result = "https://www.baidu.com," + JSON.stringify(headers)

通过 POST 方法请求

对于需要填写表单后才能获取到目标页面,或者是直接调用 API 的网络请求,则一般会使用 POST 方法。

使用 POST 方法时,需要额外配置 methodbody 参数,type 参数默认设置为 "application/json",可以省略。

POST 完整请求示例
1
2
3
4
https://www.baidu.com,{
    "method": "POST",
    "body": "bid=10086",
}

复杂情况同样可使用 JavaScript 先生成请求头,然后再拼接到 URL 之后:

POST 复杂请求示例
let headers = {
    "User-Agent":"Mozilla/5.0 (Linux; Android 10; PACM00 Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.79 Mobile Safari/537.36",
};
let body = "bid=" + "10086";
let option = {
    "charset": "gbk",
    "method": "POST",
    "body": String(body),
    "headers": headers,
    "webView": true,
};
result = "https://www.baidu.com," + JSON.stringify(option);

通过 JS 处理请求

通过 js 参数,可以在解析 url 时进行更复杂的处理。

网络请求对象定义
1
2
3
java.post(url: String, body: String, headerMap: Map<String, String>): Connection.Response
java.get(url: String, headerMap: Map<String, String>): Connection.Response
java.head(url: String, headerMap: Map<String, String>): Connection.Response
通过 JS 处理请求示例
https://www.baidu.com,{"js":"java.headerMap.put('xxx', 'yyy')"}
https://www.baidu.com,{"js":"java.url=java.url+'yyyy'"}

除了 基础 - 源域名 之外涉及到网络请求的位置,也需要通过 @js: 来调用 JavaScript 方法来拼接生成完整的 url。例如,搜索 - 搜索地址 中的 url 可以通过以下的代码进行拼接:

POST 通过 JS 处理请求示例
@js:
let url=source.key+"/aoyuges.php,";
let body=`type=articlename&s=${key}&submit=`;
let option=JSON.stringify({
    "method": "POST"
    "body": String(body),
    "charset": "GBK",
});
// 设置变量键值对,并返回变量值
java.put("url",String(url+option))

底层库资料

Legado 在进行网络请求时,涉及到的底层 JavaScript 方法分为四种,具体定义可查看源代码 ⧉

AnalyzeUrl 库

AnalyzeUrl ⧉ 是一个 Legado 中自定义的网络请求库,默认情况下网络请求使用的就是这个库及其封装的方法。

/* 使用 AnalyzeUrl 库进行网络请求
* @param urlStr/urlList 封装好的 url,可以带有请求头和请求体
* @return 返回请求结果
*/

// 直接返回 String
java.ajax(urlStr): String
java.ajaxAll(urlList: Array<String>): Array<StrResponse?>

// 返回 Response<String>,可调用 body() code() message() header() raw() toString() 方法
java.connect(urlStr): StrResponse
(()=>{
  if(page==1){
    let url='https://www.dmxsw.org/e/search/index.php,'+JSON.stringify({
      "method":"POST",
      "body":"show=title&classid=0&keyboard="+key
    });
    // 通过链式调用获取最终的重定向地址
    return java.put('surl',String(java.connect(url).raw().request().url()));
  } else {
    return java.get('surl')+'&page='+(page-1)
  }
})()

BackstageWebView 库

BackstageWebView ⧉ 是一个基于 Android WebView 的封装库,使用无头浏览器进行网络请求,可以通过在 url 中拼接 {"webView": true} 请求头参数进行调用。

/* 使用 webView 访问网络,模拟浏览器行为获取内容
@param html 直接用 webView 载入的 html, 如果 html 为空直接访问url
@param url html 内如果有相对路径的资源不传入 url 访问不了
@param js 用来取返回值的 js 语句, 没有就返回整个源代码
@return 返回 js 获取的内容
*/

// 使用 webView 获取内容
java.webView(html: String?, url: String?, js: String?): String?

// 使用 webView 获取跳转 url
java.webViewGetOverrideUrl(html: String?, url: String?, js: String?, overrideUrlRegex: String): String?

// 使用 webView 获取资源 url
java.webViewGetSource(html: String?, url: String?, js: String?, sourceRegex: String): String?
1
2
3
4
@css:a@href
@js:
let wv={"webView": true};
result+','+JSON.stringify(mv);

JSoup 库

JSoup ⧉ 是一个用于解析 HTML 和 XML 文档的 Java 库。Legado 中通过 JSoup 库进行网络请求时,主要用于实现搜索后返回链接的重定向拦截。

/* 使用 JSoup 库进行网络请求,主要用于实现重定向拦截
* @param url 请求地址
* @param headerMap 请求头
* @param body 请求体
* @return 返回请求结果
*/

java.head(url: String, headerMap: Map<String, String>): Connection.Response
java.get(url: String, headerMap: Map<String, String>): Connection.Response
java.post(url: String, body: String, headerMap: Map<String, String>): Connection.Response
(()=>{
  let base='https://www.dmxsw.org/e/search/';
  if(page==1){
    let url=base+'index.php';
    let body='show=title&classid=0&keyboard='+key;
    return base+java.put('surl',java.post(url,body,{}).header("Location").replace('?','<?,index.php?page={{page-1}}&>');
  } else {
    return base+java.get('surl')+'&page='+(page-1);
  }
})()

SourceVerificationHelp 库

SourceVerificationHelp ⧉ 是一个用于处理验证码验证的库,主要用于唤起内置浏览器,处理需要手动进行验证的场景。

/* 使用内置浏览器打开链接,手动进行验证码验证操作,可用于网站防爬等场景
* @param url 要打开的链接
* @param title 浏览器的标题
* @param imageUrl 验证码图片链接
* @return 返回验证码验证结果
*/

// 使用内置浏览器打开链接
java.startBrowser(url: String, title: String)

// 使用内置浏览器打开链接,并异步等待网页结果,可通过 .body() 获取网页内容
java.startBrowserAwait(url: String, title: String): StrResponse

// 打开图片验证码对话框,等待返回验证结果
java.getVerificationCode(imageUrl: String): String
if(/验证码/.test(result)){
    var so=java.get("url"),
    su=source.key;
    c=java.head(baseUrl,{}).cookies();
    source.putLoginHeader(c);
    url=su+"/get_btwaf_captcha_base64?captcha="+Date.now();
    body=java.get(url,{}).body();
    img="data:image/png;base64,"+JSON.parse(body).msg;
    captcha=java.getVerificationCode(img);
    $=java.ajax(`${su}/Verification_auth_btwaf?captcha=${captcha}`);
    java.toast(JSON.parse($).msg);
    result=java.ajax(so);
    }
result;