Skip to content

规则语法参考

每个 ruleXxx 字段里的字符串都会先经过 RuleParser.parse 解析成一棵 RuleType 语法树, 再由 RuleEvaluator 对当前内容求值。解析顺序是固定的(先 JS,再显式引擎前缀,再自动识别, 最后默认 CSS),了解这个顺序能帮你判断一条规则到底会被当成哪种语法。

默认 CSS 选择器(JSOUP 兼容)

不写任何前缀时,规则按 CSS 选择器解析,支持四种写法:

text
经典三段式  selector@attr@index
  li.chapter a@href@0      取第 1 个 <a> 的 href
  li.chapter a@href@-1     取最后一个
  li.chapter a@href@!0     排除第 1 个,取其余全部

@ 链式子选择器  A@B@...@attr
  tbody@tr!0               等价 CSS "tbody tr",排除表头行(index 0)
  .box@ul@li                等价 CSS ".box ul li"
  div.title@a@text          等价 CSS "div.title a",取文本

.N 索引记法(N 为纯数字)
  a.1@text                  第 2 个 <a>(索引从 0 开始),取文本
  a.0@href                  第 1 个 <a>,取 href

id.X 书源 ID 选择器(X 非纯数字)
  id.list_art_2013           等价 CSS "#list_art_2013"
  id.soft_info_para@h1        等价 CSS "#soft_info_para h1"

可用属性名(作为规则末段出现时会被当成 attr 而不是子选择器):texthtmlouterhtmlalltextnodesowntexthrefsrcdata-srcaltclassidcontentnamevaluetitletypeonclickactionstyle。省略 attr 时默认取 outerhtml(保留标签本身)。

裸属性简写:规则整体就是一个已知属性名时,等价于 *@属性

text
href    等价于   *@href     (取当前节点集合每一项的 href)

@@ 前缀:从文档根节点重新选择,等价于去掉 @@ 后按普通规则处理,常用于跳出当前 上下文重新定位一次:@@.side-panel .hot-list li

显式引擎前缀

text
@css:selector@attr@index    强制走 CSS 引擎(默认本来就是 CSS,多数情况可省略)
@xpath:expression           XPath;不写前缀但规则以 // 开头时也会自动识别为 XPath
@json:$.path.to.field       JSONPath;不写前缀但规则以 $. / $[ / $.. 开头时会自动识别

XPath 示例://div[@class="chapter"]/a/@href 取所有 <a href>//div[@id="content"]//text() 取文本节点。

JSONPath 示例:$.data.list[*].title$.data.bookList[0].name$..chapterUrl.. 表示递归查找)。JSON API 返回体里裸字段路径(不带 $ 前缀,如 data.datas.list[0].name)也会被自动识别为 JSONPath,不会被误当成 CSS 选择器处理。

正则

text
/pattern/          等价 :pattern:0,取第 0 个捕获组(即整个匹配)
/pattern/(1)       取第 1 个捕获组
:pattern:          同上,AllInOne 风格写法
:pattern:1         取第 1 个捕获组

## 替换链(内联)

格式:baseRule##pattern1##replace1##pattern2##replace2...——先按 baseRule 取值, 再对结果依次应用正则替换。

text
.title@text##^【.*?】     去掉标题里的【标签】前缀
.author@text##作者[::]?  去掉“作者:”“作者:”前缀
#content@html##<[^>]+>    去掉所有 HTML 标签,只保留纯文本

只在默认(不带 @css:/@xpath:/@json: 显式前缀)的规则上使用 ## 解析器按 前缀优先分支匹配:一旦写了显式引擎前缀,## 之后的内容会被整体并入选择器/路径字符串, 不会被识别成替换链。默认 CSS 写法(上面例子)以及规则末尾没有更早前缀命中时才会走到 ## 判定分支。需要对同一段内容做多条、跨行替换,改用字段级 replaceRegex(见下)。

字段级替换:replaceRegex

ruleContent.replaceRegex 这类独立字段按拆分,每行一条 pattern##replacement, 按顺序依次替换,作用于整个正文,而不是某个子规则的输出:

text
<script[\s\S]*?</script>##
广告:.*?\n##
\u3000{4,}##\u3000\u3000

三行分别是:删除内联脚本、删除“广告:”开头的整行、把连续 4 个以上全角空格压缩成 2 个。 每行独立解析,不依赖上一行的替换结果之外的状态。

组合:&&||

text
ruleA && ruleB && ruleC   串联:ruleA 的输出作为 ruleB 的输入,以此类推
ruleA || ruleB            兜底:依次尝试,返回第一个非空结果

&&/|| 只在“顶层”切分——出现在 [](){}、引号或 <js>...</js> 内部的 同名字符不会被当成分隔符,所以可以放心在 JS 片段里写字符串比较 a && b 而不会被规则解析器误切。|| 的优先级低于 &&(先按 || 分组,每组内部再按 && 拆分)。

典型用法:ruleExplore|| 依次尝试两个可能的分类容器:

text
.category-list li || .cat-nav a

变量:@put: / @get:

text
@put:{varName}                  把当前结果存入变量 varName(值就是当前结果本身)
@put:{varName:someRule}         先对 someRule 求值,把结果存入 varName
@put:{"t":"@@.thumb@a@text","a":".author@text"}   一次性存多个变量(JSON 对象格式)
@get:{varName}                  读取变量
{{varName}}                     等价于 @get:{varName},也是最常见的模板占位写法

变量在同一次规则求值的上下文里共享,典型用途是先在搜索/详情阶段 @put: 缓存一个 token 或分页游标,后续请求用 拼进 URL。

JS 混排

text
<js>...</js>                          纯 JS 规则,返回值作为结果
someRule<js>result.replace(...)</js>  先按 someRule 取值,再交给 JS 处理(result 是上一步结果)
@js:...                               等价 <js>...</js> 的简写前缀
$.token@js:java.aesBase64DecodeToString(result, key, iv)
                                       先取 JSONPath 字段,再用 java.* 解密函数处理

JS 片段里可以继续拼接 @json:xxx 之类的尾部规则,解析器会自动识别成组合链 (JS → JSONPathJSONPath → JS → JSONPath)。JS 执行环境里能用到的全局对象和 java.* 方法见JS 执行上下文

URL 请求选项

searchUrl/exploreUrl/tocUrl/分页地址等都支持在 URL 后追加 ,{...} 选项对象:

text
https://example.com/search?kw={{key}}&p={{page}},{
  "method": "POST",
  "body": "keyword={{key}}&page={{page}}",
  "charset": "gbk",
  "headers": {"X-Requested-With": "XMLHttpRequest"},
  "webView": true,
  "webViewJS": "document.readyState === 'complete'",
  "retry": 2
}

选项对象接受标准 JSON,也兼容书源里常见的单引号写法({'method':'POST'})。字段含义: method(默认 GET)、body(POST 请求体,支持 模板)、charset(响应字符集, 如 gbk)、headers(附加请求头)、webView(改用 WKWebView 渲染后再取 DOM,见 浏览器探测实战)、webViewJS(判断页面加载完成的 JS)、 retry(重试次数)、js(请求前执行的 JS,可用来现算签名参数)。

索引与排除的内部编码

正索引直接是元素下标(从 0 开始);负索引按 Swift 数组的反向下标处理(-1 是最后一个); 排除模式内部用 -(1000 + n) 编码第 n 个要排除的元素,这只是实现细节,书源作者只需要写 !n(如 !0 排除第一个),不需要关心编码方式。

Readori 使用与开发文档