Skip to content

规则说明

通过编写规则, 可以把不同网站的内容以相同的格式呈现, 以实现聚合搜索或阅读的功能。

为了避免重复造轮子、重复制定规则规范, 目前 any-reader 的规则规范是按照 eso 的规则实现的, 所以规则的编写方法您可以参考 eso, 目前 any-reader 也支持 eso 规则

规则结构

规则结构
ts
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
}
json5
{
  "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": ""
}
字段太多看着很复杂?

并不是每个字段都是必填的, 按需填写既可。

以搜索为例, 实际上只用实现以下几个关键字段。

json5
{
  "host": "", // 域名
  "contentType": 1, // 规则类型, 告诉软件这是小说还是漫画还是视频
  "enableSearch": true, // 开启搜索功能

  "searchUrl": "", // 搜索请求地址
  "searchList": "", // 搜索列表
  "searchName": "", // 每一个书或者视频的名称
  "searchResult": "", // 用于给下一个流程使用, 一般是章节列表地址

  "chapterList": "", // 章节列表
  "chapterName": "", // 章节名
  "chapterResult": "", // 用于给下一个流程使用, 一般是正文地址

  "contentItems": "" // 正文
}

格式 eso://:xxxxx 是压缩后的规则, 软件也会自动识别, 也可以使用 在线规则编解码工具 还原成json

规则字段类型

URL地址规则

相关字段: searchUrlchapterUrl, 常用来请求网络获取数据

地址规则的几种写法:

特性示例
URLhttps://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请求

输入:

http
https://xxx.com/search?q=$keyword&pageSize=10

效果:

http
GET https://xxx.com/search?q=$keyword&pageSize=10

在搜索流程里, $keyword 会被转换为搜索的关键字

例子2 - POST 请求

输入:

JSON
{"url":"https://xxx.com/search","method":"post","headers":{"token":"111"},"body":{"keyword":"$keyword"}}

效果:

http
POST https://xxx.com/search HTTP/1.1
token: 111
Content-Type: application/json

{"keyword": "$keyword"}
例子3 - 复杂点的可以用JS获取请求参数

输入:

javascript
@js:(() => {
  // 处理一些事情
  return { url: 'https://xxx.com/search', method: 'post', headers: { token: '111' }, body: { keyword: '$keyword' } };
})();

效果:

http
POST https://xxx.com/search HTTP/1.1
token: 111
Content-Type: application/json

{"keyword": "$keyword"}

取列表规则

相关字段: searchListchapterListdiscoverList

有了网络请求的数据, 那么下一步一般是用来获取列表。一般用于从 URL地址规则 的结果提取内容。

拿到的结果通常是一个数组

列表规则也可以通过 @js 规则 fetch 等接口发起请求获取数据

例子1 @cssURL规则 结果获取

假如URL规则拿到的结果:

html
<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>

规则:

css
#xxxlist li

规则测试

规则用法可以参考下面的 规则表达式

这里使用的是默认的 @css, #xxxlist li 效果类似于 document.querySelectorAll('#xxxlist li')

也可以使用 xpath js 等方式去获取, 规则表达式 里有介绍

例子2 @js 网络请求

不通过 URL地址规则 获取内容, 直接通过js获取

javascript
@js:(async () => {
  return await fetch('https://api.github.com/gists/public').then((e) => e.text());
})()
例子3 @js 不需要网络请求

不通过 URL地址规则 获取内容, 直接通过js获取

javascript
@js:[{
  a: 1,
  b: 2
  // ...
}]

取内容规则

有了列表数组的数据, 那么下一步一般是需要获取具体的字段内容, 比如书名、作者。

这类规则通常用来从列表规则的结果获取具体的某项内容,

图片字段可以使用 @headers 携带请求头 (目前仅桌面端支持)

比如: https://xxx.jpg@headers{"xxx":"xxx"}

例子1

假设列表规则拿到的数组每一项内容是这样的:

html
<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>

规则:

json5
{
  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
javascript
@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()

XPath 文档

XPath 测试工具

JSONPath

例子: @json:$.list[:1].title

文档

JSONPath 测试工具

JavaScript

例子: @js:1+1

可以搭配解析流程产生的变量使用, 比如 resultlastResult

如果URL地址规则拿到的结果是 123, 那么在取列表规则字段中 @js:result 将输出 123

如果上一个流程结果规则拿到的结果是 456, 那么在取列表规则字段中 @js:lastResult 将输出 456, 在URL地址规则字段中 @js:result 将输出 456

内置方法: CryptoJSfetchxpathcheerio

CryptoJS

https://github.com/brix/crypto-js

javascript
@js:CryptoJS.AES.decrypt('U2FsdGVkX1+lzrvaz1MagYswnfRUePbcwyo+fZ90+Qs=', "secret key 123").toString(CryptoJS.enc.Utf8)

// -> hello word

使用变量

javascript
@js:CryptoJS.AES.decrypt(result, "secret key 123").toString(CryptoJS.enc.Utf8)

fetch

https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API

javascript
@js:(async() => fetch('https://api.github.com/').then(e => e.text()))()

xpath

javascript
@js:(async() => {
  return await xpath('<div class="box1"><div class="box2">content2</div><div class="box3">content3</div></div>', '//*[@class="box3"]/text()')
})()

// -> content3

cheerio

https://github.com/cheeriojs/cheerio/wiki/Chinese-README

javascript
@js:(() => cheerio.load(result)('h2.title').text())()

正则

例子: rule##match##replacement##replaceFirstFlag

其中 replacementreplaceFirstFlag 均可以省略。

变量

例子: aa{‍​‍{rule1}}bb{‍​‍{rule2}}cc

级联

例子: $.html@css:html

@ 分割规则后,第一个部分为 JSONPath, 使用 JSONPath 解析后继续用第二部分的 css 规则解析拿到最终结果