告别手写规则!novel-plus 支持第三方规则导入,新增 CSS、XPath、JSONPath 解析


novel-plus 是一套功能完善的原创文学 CMS 系统,支持 PC 网站、WAP、H5、Android、iOS 和微信小程序等多个终端,提供一站式小说平台搭建解决方案。

由前台门户系统、作家后台管理系统、平台后台管理系统和爬虫管理系统等多个子系统构成,包括小说推荐、作品检索、小说排行、小说阅读、小说书评、段落评论、小说书单、会员中心、作家专区等功能。

支持自定义多模版、可拓展的多种小说内容存储方式(内置数据库分表存储和 TXT 文本存储)、阅读主题切换、自动采集和更新测试数据、AI写作、会员充值、订阅模式、新闻发布和实时统计报表。

以前,novel-plus 的爬虫规则主要依赖正则表达式,对不熟悉正则的用户来说,上手门槛相对较高。

现在,采集正式支持 CSS、XPath、正则表达式、JSONPath 四种解析方式,并在此基础上尝试实现了 第三方规则导入 功能,希望降低规则编写和迁移的成本。


目前,第三方规则导入功能 仅支持阅读 3.0 格式。

为了验证方案是否可行,我从网上公开的阅读 3.0 书源中随机挑选了一批规则,尝试导入了十几条。测试结果并不算理想,绝大部分规则无法直接使用,问题主要集中在以下几个方面:

  • 域名失效:原站点无法正常访问。
  • 地域限制:部分站点对访问来源有限制。
  • 访问限流:请求频率受到限制,无法正常采集。
  • 反爬机制:站点增加了额外的访问校验。
  • 规则不兼容:部分规则依赖 @js: 动态脚本,或者使用了暂未适配的多行组合规则。

针对地域限制、访问限流以及部分反爬问题,可以参考这篇文章:一站式解决爬虫采集难题:提速、稳定访问与 IP 限制

不过,也有少数规则成功导入,并且能够正常采集内容。这说明,第三方规则导入这条路是可行的,后续仍有继续完善的空间。


相关代码目前已经提交到 6.4.x 分支,计划在后续版本中正式发布。现阶段,这项功能还处于持续完善的过程中,暂时不能保证所有第三方规则都能直接导入使用。后续会继续完善规则兼容和导入逻辑,尽量减少手动修改规则的工作量。

CSS、XPath 和 JSONPath 虽然使用不同的表达式,但在 novel-plus 中,它们共用一套统一的取值后缀。掌握这套语法,就能应对大部分常见的采集需求。

写法 含义 示例
表达式 默认提取文本内容 #bookName
表达式::attr(名称) 提取指定属性 a.author::attr(href)
表达式::html 提取 HTML,保留标签 #content::html
表达式::ownText 仅提取元素自身的直接文本,不包含子孙元素的文本 div.intro::ownText
表达式|后端正则 提取结果后,再用正则表达式进行二次处理,优先取第 1 个捕获组 dl dd a::attr(href)|/(\d+)\.html
表达式##正则##替换 对提取结果进行正则替换;省略替换内容时,默认删除匹配部分 .content::html##[((]本章完[))]

这里特别推荐 |后端正则 这种写法。

例如,某个章节链接是 https://example.com/book/12345.html,而我们只需要其中的章节 ID 12345,就可以直接写:

1
a::attr(href)|/(\d+)\.html

先提取链接的 href 属性,再通过正则表达式提取章节 ID,无须额外编写处理逻辑。

另一种常用写法是 ##正则##替换,可以在提取内容后进一步清理文本。例如,删除正文末尾的“本章完”等标记。该语法也兼容阅读 3.0 中的 ## 过滤写法。

除了取值后缀,还有一种 规则级解析前缀,可以为单条规则单独指定解析方式,优先级高于整源的默认解析设置。

目前支持以下前缀:

  • css::使用 CSS 选择器
  • xpath::使用 XPath
  • json::使用 JSONPath
  • regex::使用正则表达式

例如,整本书源默认使用 CSS,但某个字段更适合通过 XPath 提取,就可以这样写:

1
xpath://meta[@property='og:novel:author']/@content

这样,同一个书源中就可以混合使用不同的解析方式,不必为了某个特殊字段改变整源的配置。

CSS 选择器基于 jsoup 实现,语法与浏览器中的 querySelector 等选择器比较接近。如果平时经常使用浏览器开发者工具查看网页结构,通常可以比较快地上手。

1
2
3
4
#bookName                         取书名文本
a.author::attr(href) 取作者链接
#content::html 取正文 HTML
dl dd a::attr(href)|/(\d+)\.html 提取链接中的章节 ID

这里有一个需要注意的地方是 novel-plus 对 :eq(n) 做了特殊处理。

在 novel-plus 中,:eq(n) 表示选取第 n 个匹配元素;而 jsoup 原生的 :eq(n) 语义是选取父节点下索引为 n 的子元素,两者的含义并不相同。

例如:

1
#list:eq(0) a

表示先选取第一个匹配 #list 的元素,再提取其中的 a 元素。

不包含 :eq( 的选择器仍按 jsoup 原生规则解析。

XPath 同样基于 jsoup 的 selectXpath 实现。对于已经熟悉 XPath 的用户,可以直接使用常见的路径表达式,不必重新学习 CSS 选择器。

1
2
3
4
5
6
//h1[@class='bookname']                    取书名文本
//a[@id='author'] 取作者节点
//a[@id='author']/text() 取作者文本节点
//div[@id='content']::html 取正文 HTML
//div[@id='list']//dd/a::attr(href)|/(\d+)\.html
提取章节 ID

XPath 表达式既可以返回元素,也可以返回文本节点或属性值。例如,text() 和 @attr 这类表达式都可以正常处理,无须为了适配取值逻辑而额外修改规则。

正则表达式是 novel-plus 原有的解析方式,本次升级并没有取消它,而是在保留原有能力的基础上增加其他选项。

其提取规则是 有捕获组时,优先取第 1 个捕获组;没有捕获组时,取整个匹配结果。

例如:

1
2
<h1 class="title">(.*?)</h1>           取书名
<div class="author">作者:(.*?)</div> 取作者

对于历史上没有配置解析方式字段的爬虫规则,系统仍默认按照正则表达式处理,尽可能保证旧书源继续兼容,不需要为了升级而批量修改现有规则。

有些站点的数据并不是直接写在 HTML 页面中,而是通过接口返回 JSON。对于这类站点,JSONPath 往往比 CSS 或 XPath 更直接。

novel-plus 的 JSONPath 解析基于 Jayway JsonPath 实现,适合从接口响应中提取书名、作者、章节列表、正文等数据。

1
2
3
4
5
$.data.bookName                  取书名
$.data.chapters[*].name 提取全部章节名
$.data.chapters[*].url|/(\d+)\.html
提取章节链接中的 ID
$.data.bookDesc 提取简介字段

其中,$.data.chapters[*].name 中的 [*] 表示匹配数组中的所有元素,可以一次提取多个章节名。

对于返回对象或数组的表达式,系统会将结果序列化为 JSON 文本返回,方便后续处理。

需要注意的是,JSONPath 更适合处理已经获取到的 JSON 响应。如果站点需要执行 JavaScript、完成特定请求签名或通过其他方式才能获取数据,仅仅更换解析表达式并不能解决这些问题。