规则说明
通过编写规则, 可以把不同网站的内容以相同的格式呈现, 以实现聚合搜索或阅读的功能。
为了避免重复造轮子、重复制定规则规范, 目前
any-reader的规则规范是按照eso的规则实现的, 所以规则的编写方法您可以参考 eso, 目前any-reader也支持 eso 规则
规则结构
规则结构
export interface Rule {
// ===== 通用字段 =====
// 域名
// 当发起网络请求时, 如果最终的地址非 `http` 开头, 那么请求的时候会自动拼接上 `host`
// 规则里也可以使用变量 `$host` 来获取它
host: string;
id: string; // uuid, 用于区分规则的唯一性
name: string; // 规则名称
sort: number; // 规则排序, 越高越靠前
contentType: ContentType; // 规则类型
loadJs: string; // 全局JS脚本
author: string; // 规则作者
userAgent: string; // Headers JSON字符串
// ===== 解析流程 - 搜索 =====
enableSearch: boolean; // 表示搜索功能开启的状态
searchUrl: string; // 用法可参考 `URL地址规则`
// 以下规则用法可参考 `取列表规则`
// 可以使用变量: `result` 获取 `searchUrl` 的结果
searchList: string;
// 以下规则用法可参考 `取内容规则`
// 可以使用变量: `result` 获取 `searchList` 数组当前项的结果
searchCover: string; // 封面
searchName: string; // 标题
searchAuthor: string; // 作者
searchChapter: string; // 最新章节
searchDescription: string; // 描述
searchResult: string; // 结果
// ===== 解析流程 - 章节列表 =====
// 以下规则用法可参考 `URL地址规则`
// 可以使用变量: `result` 获取 `searchResult` 或者 `discoverResult` 的结果
chapterUrl: string;
// 以下规则用法可参考 `取列表规则`
// 可以使用变量: `result` 获取 `chapterUrl` 的结果
// 可以使用变量: `lastResult` 获取 `searchResult` 或者 `discoverResult` 的结果
chapterList: string; // 列表
// 以下规则用法可参考 `取内容规则`
// 可以使用变量: `result` 获取 `chapterList` 数组当前项的结果
// 可以使用变量: `lastResult` 获取 `searchResult` 或者 `discoverResult` 的结果
chapterName: string; // 标题
chapterCover: string; // 封面
chapterTime: string; // 时间
chapterNextUrl: string; // 下一页地址
chapterResult: string; // 结果
// ===== 解析流程 - 发现页 =====
enableDiscover: boolean; // 是否启用发现页
discoverUrl: string; // 用法可参考 `发现页分类规则`
// 以下规则用法可参考 `取列表规则`
// 可以使用变量: `result` 获取 `discoverUrl` 的结果
discoverList: string; // 列表
// 以下规则用法可参考 `取内容规则`
// 可以使用变量: `result` 获取 `discoverList` 数组当前项的结果
discoverName: string; // 标题
discoverCover: string; // 封面
discoverAuthor: string; // 作者
discoverDescription: string; // 描述
discoverResult: string; // 结果
// discoverItems: string
discoverTags: string;
discoverChapter: string;
discoverNextUrl: string; // 下一页地址
// ===== 解析流程 - 正文 =====
contentUrl: string;
contentItems: string; // 内容
contentNextUrl: string; // 用于一篇正文存在多个页面的场景
contentDecoder: string; // 用于正文图片需要解密的场景
}
enum ContentType {
MANGA = 0, // 漫画
NOVEL = 1, // 小说
VIDEO = 2, // 视频
AUDIO = 3, // 音频
RSS = 4,
NOVELMORE = 5
}{
"id": "xxx-xxx-xxx-xxx-xxx",
"author": "",
"name": "",
"host": "",
"icon": "",
"contentType": 1,
"sort": 0,
"userAgent": "",
"enableDiscover": false,
"discoverUrl": "",
"discoverNextUrl": "",
"discoverList": "",
"discoverTags": "",
"discoverName": "",
"discoverCover": "",
"discoverChapter": "",
"discoverDescription": "",
"discoverResult": "",
"enableSearch": false,
"searchUrl": "",
"searchAuthor": "",
"chapterCover": "",
"chapterTime": "",
"discoverAuthor": "",
"searchList": "",
"searchTags": "",
"searchName": "",
"searchCover": "",
"searchChapter": "",
"searchDescription": "",
"searchResult": "",
"enableMultiRoads": false,
"chapterRoads": "",
"chapterRoadName": "",
"chapterUrl": "",
"chapterNextUrl": "",
"chapterList": "",
"chapterName": "",
"chapterResult": "",
"contentUrl": "",
"contentNextUrl": "",
"contentItems": "",
"contentDecoder": ""
}字段太多看着很复杂?
并不是每个字段都是必填的, 按需填写既可。
以搜索为例, 实际上只用实现以下几个关键字段。
{
"host": "", // 域名
"contentType": 1, // 规则类型, 告诉软件这是小说还是漫画还是视频
"enableSearch": true, // 开启搜索功能
"searchUrl": "", // 搜索请求地址
"searchList": "", // 搜索列表
"searchName": "", // 每一个书或者视频的名称
"searchResult": "", // 用于给下一个流程使用, 一般是章节列表地址
"chapterList": "", // 章节列表
"chapterName": "", // 章节名
"chapterResult": "", // 用于给下一个流程使用, 一般是正文地址
"contentItems": "" // 正文
}格式
eso://:xxxxx是压缩后的规则, 软件也会自动识别, 也可以使用 在线规则编解码工具 还原成json
规则字段类型
URL地址规则
相关字段: searchUrl、chapterUrl, 常用来请求网络获取数据
地址规则的几种写法:
| 特性 | 示例 |
|---|---|
| URL | https://xxx.com/search?q=$keyword&pageSize=10 |
| JSON | {"url":"https://xxx.com/search","method":"post","headers":{"token":"111"},"body":{"keyword":"$keyword"}} |
| @js | @js:(() => { return {url, method, body, headers}; })(); |
例子1 - 简单的GET请求
输入:
https://xxx.com/search?q=$keyword&pageSize=10效果:
GET https://xxx.com/search?q=$keyword&pageSize=10在搜索流程里,
$keyword会被转换为搜索的关键字
例子2 - POST 请求
输入:
{"url":"https://xxx.com/search","method":"post","headers":{"token":"111"},"body":{"keyword":"$keyword"}}效果:
POST https://xxx.com/search HTTP/1.1
token: 111
Content-Type: application/json
{"keyword": "$keyword"}例子3 - 复杂点的可以用JS获取请求参数
输入:
@js:(() => {
// 处理一些事情
return { url: 'https://xxx.com/search', method: 'post', headers: { token: '111' }, body: { keyword: '$keyword' } };
})();效果:
POST https://xxx.com/search HTTP/1.1
token: 111
Content-Type: application/json
{"keyword": "$keyword"}取列表规则
相关字段: searchList、chapterList、discoverList
有了网络请求的数据, 那么下一步一般是用来获取列表。一般用于从 URL地址规则 的结果提取内容。
拿到的结果通常是一个数组
列表规则也可以通过
@js规则fetch等接口发起请求获取数据
例子1 @css 从 URL规则 结果获取
假如URL规则拿到的结果:
<html>
<!-- -->
<ul id="xxxlist">
<li>
<p class="q">
<a href="http://xxx.xxx/book/xxxx/" target="_blank">名字1</a>
</p>
<p class="a">
<a href="/xxx/xxxx.html" target="_blank">空空如也。</a>
</p>
<p class="b">张三</p>
<p class="c">99k</p>
<p class="d">连载中</p>
<p class="e">24-01-01</p>
</li>
<li>
<p class="q">
<a href="http://xxx.xxx/book/xxx1/" target="_blank">名字2</a>
</p>
<p class="a">
<a href="/xxx/xxx1.html" target="_blank">空空如也。</a>
</p>
<p class="b">李四</p>
<p class="c">99k</p>
<p class="d">连载中</p>
<p class="e">24-01-02</p>
</li>
<!-- -->
</ul>
<!-- -->
</html>规则:
#xxxlist li规则用法可以参考下面的
规则表达式这里使用的是默认的
@css,#xxxlist li效果类似于document.querySelectorAll('#xxxlist li')也可以使用
xpathjs等方式去获取,规则表达式里有介绍
例子2 @js 网络请求
不通过 URL地址规则 获取内容, 直接通过js获取
@js:(async () => {
return await fetch('https://api.github.com/gists/public').then((e) => e.text());
})()例子3 @js 不需要网络请求
不通过 URL地址规则 获取内容, 直接通过js获取
@js:[{
a: 1,
b: 2
// ...
}]取内容规则
有了列表数组的数据, 那么下一步一般是需要获取具体的字段内容, 比如书名、作者。
这类规则通常用来从列表规则的结果获取具体的某项内容,
图片字段可以使用
@headers携带请求头 (目前仅桌面端支持)比如:
https://xxx.jpg@headers{"xxx":"xxx"}
例子1
假设列表规则拿到的数组每一项内容是这样的:
<li>
<p class="q">
<a href="http://xxx.xxx/book/xxxx/" target="_blank">名字1</a>
</p>
<p class="a">
<a href="/xxx/xxxx.html" target="_blank">空空如也。</a>
</p>
<p class="b">张三</p>
<p class="c">99k</p>
<p class="d">连载中</p>
<p class="e">24-01-01</p>
</li>规则:
{
searchName: '.q a@text', // 名字1
searchAuthor: '.b@text', // 张三
searchResult: '.q a@href' // http://xxx.xxx/book/xxxx/
}结果规则
字段名通常后面有 Result, 比如 searchResult chapterResult。
这类规则一般用于从列表规则的结果获取内容供下一个流程的 URL地址规则 使用。
比如搜索时,searchResult 拿到的结果将会给获取章节列表的流程使用,获取章节列表的URL规则里可以使用 result 变量拿到 searchResult 的结果。
结果规则的结果会成为下一个解析流程URL地址规则的
result变量,成为下一个解析流程取列表规则的lastResult变量。
发现页分类规则
discoverUrl 例子:
分类名1::url1
分类名2::url2
分类名3::url3分组1::分类名1::url1
分组1::分类名2::url2
分组1::分类名3::url3
分组2::分类名1::url4
分组2::分类名2::url5
分组2::分类名3::url6@js:(() => {
return [
'分组1::分类名1::url1',
'分组1::分类名2::url2',
'分组1::分类名3::url3',
'分组2::分类名1::url4',
'分组2::分类名2::url5',
'分组2::分类名3::url6'
];
})();
discoverUrl虽然规则看起来像URL地址规则, 但是用法截然不同, 所以这里单独说明
规则表达式
| 特性 | 说明 | 示例 |
|---|---|---|
@css | 使用 css 选择器查找内容 规则测试 | @css:.box1 .box2@text |
@json | 使用 jsonpath 查找内容 规则测试 | @json:$.list[:1].title |
@xpath | 使用 xpath 查找内容 规则测试 | @xpath://*[@class="box3"]/text() |
@js | 使用 js 脚本 规则测试 | @js:1+1 |
@filter | 模拟浏览器加载地址后匹配指定链接 | @filter:(?:m3u8|mp4) |
@replace | 替换匹配到的内容为空 规则测试 | @replace:\d |
## | 正则替换 规则测试 | $.a##2##替换文本 |
{{}} | 拼接 规则测试 | http://www.aaa.com/{{$.id}} |
|| | 或, 直到规则匹配成功 规则测试 | $.a||$.b |
&& | ||
| 嵌套组合 | $.info.body@css:.box1 .box2@text |
规则可以省略开头的,@css、@xpath、@json, 因为解析器会尝试自动识别。
CSS
例子: @css:.box1 .box2@text
该类规则分为 2 部分,以 @ 符号分割.
前部分与 CSS 标准一致,可省略(表示取根节点). 后部分则是软件自定义行为, 包括 text、html、innerHtml、outerHtml 取文本,src、href、data-original、bid 等取属性。
XPath
例子: @xpath://*[@class="box3"]/text()
JSONPath
例子: @json:$.list[:1].title
JavaScript
例子: @js:1+1
可以搭配解析流程产生的变量使用, 比如
result、lastResult如果
URL地址规则拿到的结果是123, 那么在取列表规则字段中@js:result将输出123如果上一个流程
结果规则拿到的结果是456, 那么在取列表规则字段中@js:lastResult将输出456, 在URL地址规则字段中@js:result将输出456
内置方法: CryptoJS、fetch、xpath、cheerio
CryptoJS
https://github.com/brix/crypto-js
@js:CryptoJS.AES.decrypt('U2FsdGVkX1+lzrvaz1MagYswnfRUePbcwyo+fZ90+Qs=', "secret key 123").toString(CryptoJS.enc.Utf8)
// -> hello word使用变量
@js:CryptoJS.AES.decrypt(result, "secret key 123").toString(CryptoJS.enc.Utf8)fetch
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API
@js:(async() => fetch('https://api.github.com/').then(e => e.text()))()xpath
@js:(async() => {
return await xpath('<div class="box1"><div class="box2">content2</div><div class="box3">content3</div></div>', '//*[@class="box3"]/text()')
})()
// -> content3cheerio
https://github.com/cheeriojs/cheerio/wiki/Chinese-README
@js:(() => cheerio.load(result)('h2.title').text())()正则
例子: rule##match##replacement##replaceFirstFlag
其中 replacement 和 replaceFirstFlag 均可以省略。
变量
例子: aa{{rule1}}bb{{rule2}}cc
级联
例子: $.html@css:html
@ 分割规则后,第一个部分为 JSONPath, 使用 JSONPath 解析后继续用第二部分的 css 规则解析拿到最终结果