开源阅读网络请求说明
预计阅读时长 : 15 分钟
请求流程
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 化的请求参数,精细化的调整请求类型和请求方法。
常用变量
网络请求中的最常用的两大变量分别是 baseUrl 和 result,它们分别代表了请求的链接地址和请求的响应结果。
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
请求方法
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?
/*
调用方法实例
*/
@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
/*
调用方法实例
*/
<js>
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;
</js>
class.line
|
请求类型
GET
一般情况下,网络请求使用普通的 GET 方法即可,只需要在对应的位置直接填入 URL。
如果部分页面对于使用的客户端有限制,例如只允许在手机上进行访问,那么可以尝试在 基础 - 请求头
中使用 JSON 格式增加客户端标识。
首先,我们来看一下请求头的最简内容,注意必须是 JSON 格式:
| {
"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",
}
|
对于复杂的情况,可使用 JavaScript 来拼接和生成请求头:
| @js:
(()=>{
let headers = {
"referer": baseUrl,
"x-requested-with": "mark.via",
"accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"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"};
return JSON.stringify(headers);
})()
|
IIFE 立即调用函数表达式
- 定义箭头函数:() => {...}是一个箭头函数的简写形式。当箭头函数没有参数时,你需要在箭头=>之前提供一对空括号()来表示这是一个函数定义。这里的大括号{...}内是函数体,包含了函数的执行逻辑。
- 立即调用函数表达式(IIFE):在箭头函数外面的一对括号(...)用于立即调用该箭头函数。这种模式称为立即调用函数表达式(IIFE)。通过在函数定义后紧跟一对括号(),JavaScript引擎会立即执行该函数。在这个例子中,外层的括号(() => {...})()不仅定义了一个函数,还立即执行了它。
- 使用 JavaScript 方法时,请保证相关变量的类型都满足规范,是计算得到的尽量都用
String()
强转一下类型,以便 JSON.stringify()
转换字符串生成请求头成功。
- 一般形式如下,charset 为 utf-8 时可省略,如果还是获取不成功可以到 中查看例如
<meta charset="gbk">
的编码确定具体的编码
POST
对于需要填写表单后才能获取到目标页面,或者是直接调用 API 的网络请求,则一般会使用 POST 方法。
这个时候,就不能再使用 基础 - 请求头
字段,需要额外配置 method
和 body
属性,并直接在 基础 - 源域名
中进行拼接。
| https://www.baidu.com,{
"method": "POST",
"body": "bid=10086",
"headers": "{\"User-Agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36\"}",}
|
复杂情况同样可使用 JavaScript 先生成请求头,然后再拼接到 URL 之后:
| 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 = {
"method": "POST",
"headers": JSON.stringify(headers),
"body": String(body),
};
"https://www.baidu.com," + JSON.stringify(option)
|
除了 基础 - 源域名
之外,其他涉及到网络请求的位置,都需要通过 @js:
来调用 JavaScript 方法来拼接生成完整的 url。例如,搜索 - 搜索地址
中的 url 可以通过以下的代码进行拼接:
| @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))
|
webView
如果增加了客户端标识还是无法获取内容,那就需要考虑使用 webView 方式模拟浏览器进行网络请求,以规避部分网站的反扒机制,以及抓取 Ajax 异步加载的内容。
对于基础 - 源域名
,只需要在添加链接后添加额外 {"webView": true}
请求头信息就可以实现 webView 请求:
| https://www.baidu.com,{"webView": true}
|
如果是其他的步骤中的网络请求,例如 目录-章节地址
中提取出来的 url,则需要将 {"webView": true}
加入到参数中,再通过 JS 拼接出带有参数的 url,来实现 webView 方式访问。
| @css:a@href@js:result+','+JSON.stringify({"webView": true});
|