Appearance
规则语法参考
每个 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 而不是子选择器):text、html、outerhtml、 all、textnodes、owntext、href、src、data-src、alt、class、id、content、 name、value、title、type、onclick、action、style。省略 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 → JSONPath 或 JSONPath → 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 排除第一个),不需要关心编码方式。
