novel-downloader规则扩展实战指南:从零构建自定义抓取规则 novel-downloader规则扩展实战指南从零构建自定义抓取规则【免费下载链接】novel-downloader一个可扩展的通用型小说下载器。项目地址: https://gitcode.com/gh_mirrors/no/novel-downloadernovel-downloader是一个高度可扩展的通用型小说下载器其核心扩展机制基于模块化的规则系统。本文将深入解析其规则扩展架构指导开发者如何为新的小说网站构建自定义抓取规则实现从概念理解到实践部署的完整技术流程。一、规则系统架构深度解析novel-downloader采用分层架构设计将核心下载引擎与网站特定规则解耦。核心架构由BaseRuleClass基类定义统一接口各网站规则通过继承或工厂函数实现具体逻辑。1.1 核心基类设计BaseRuleClass作为所有规则的抽象基类定义了统一的生命周期方法export abstract class BaseRuleClass { public abstract bookParse(): PromiseBook; public abstract chapterParse( chapterUrl: string, chapterName: string | null, isVIP: boolean, isPaid: boolean | null, charset: string, options: Recordstring, any ): PromiseChapterParseObject; public async run(): PromiseBook | undefined { // 统一执行流程 } protected async preHook(): Promisevoid { /* 预处理钩子 */ } protected async initChapters(): PromiseChapter[] { /* 章节初始化 */ } protected postHook(): void { /* 后处理钩子 */ } }1.2 规则目录结构项目采用按网站类型分类的目录结构便于维护和扩展src/rules/ ├── onePage/ # 单页式网站规则 │ ├── template.ts # 单页规则模板 │ ├── 69shuba.ts # 实际规则示例 │ └── ... ├── twoPage/ # 双页式网站规则 ├── special/ # 特殊网站规则 │ ├── original/ # 原创网站 │ └── reprint/ # 转载网站 └── lib/ # 通用工具库二、规则开发实战单页式网站模板解析2.1 模板工厂函数模式单页式规则采用工厂函数模式通过mkRuleClass快速创建规则类// src/rules/onePage/template.ts export function mkRuleClass({ bookUrl, bookname, author, introDom, introDomPatch, coverUrl, aList, getContentFromUrl, contentPatch, // ... 其他配置参数 }: MkRuleClassOptions): PublicConstructorBaseRuleClass { return class extends BaseRuleClass { // 具体实现 }; }2.2 实际规则实现示例以69书吧为例展示完整规则实现// src/rules/onePage/69shuba.ts import { htmlTrim } from ../../lib/cleanDOM; import { rm2 } from ../../lib/dom; import { nextPageParse } from ../../lib/rule; import { mkRuleClass } from ./template; export const c69shuba () mkRuleClass({ bookUrl: location.href, bookname: document.querySelector(#info h1)!.innerText.trim(), author: document.querySelector(#info p)!.innerText .trim() .replace(/^作\s*者\s*[:]\s*/u, ), introDom: document.querySelector(#intro)!, introDomPatch: (_) _, coverUrl: document.querySelector(#fmimg img)!.src, aList: document.querySelectorAll(#list dl dt:last-of-type ~ a), async getContentFromUrl(chapterUrl, chapterName, charset) { const { contentRaw } await nextPageParse({ chapterName, chapterUrl, charset, selector: #booktxt, contentPatch(content, doc) { rm2([/本章阅读完毕/], content); htmlTrim(content); return content; }, getNextPage(doc) { const nextPageLink doc.querySelector( div.bottem1 a[relnext] ) as HTMLAnchorElement; if (nextPageLink nextPageLink.innerText.includes(下一页)) { return nextPageLink.href; } return ; }, continueCondition(content, nextLink) { return nextLink ! /_\d.html/.test(nextLink); }, }); return contentRaw; }, contentPatch: (content) content, });三、规则实现关键技术点3.1 章节列表提取策略章节列表提取需要精确分析网站DOM结构使用CSS选择器定位章节链接// 章节选择器示例 aList: document.querySelectorAll(#list dl dt:last-of-type ~ a), // 自定义章节名称提取 getAName: (aElem) { // 处理特殊格式的章节标题 return aElem.innerText.replace(/第\d章\s*/, ).trim(); },3.2 内容提取与清理内容提取需要处理多种复杂情况包括分页加载、广告过滤等// 分页内容提取 async getContentFromUrl(chapterUrl, chapterName, charset) { const { contentRaw } await nextPageParse({ chapterName, chapterUrl, charset, selector: #booktxt, contentPatch(content, doc) { // 移除广告和无关元素 rm2([/本章阅读完毕/, /请收藏本站/, /广告/], content); htmlTrim(content); return content; }, getNextPage(doc) { // 下一页链接提取逻辑 const nextLink doc.querySelector(a.next); return nextLink?.href || ; }, continueCondition(content, nextLink) { // 继续提取下一页的条件 return nextLink ! !nextLink.includes(javascript:); }, }); return contentRaw; }3.3 反爬机制处理对于采用复杂反爬措施的网站需要特殊处理// 字体加密处理示例参考jjwxcFontDecode.ts import { decodeJJWXCFont } from ../../lib/jjwxcFontDecode; const contentPatch (content) { // 检测并解码字体加密内容 const decodedContent decodeJJWXCFont(content); return decodedContent; }; // 图片验证码处理 import { OCRDecoder } from ../../lib/decoders/OCRDecoder; async function handleCaptcha(imageUrl) { const ocr new OCRDecoder(); const captchaText await ocr.decodeImage(imageUrl); return captchaText; }四、特殊网站规则实现4.1 Cloudflare防护网站处理对于采用Cloudflare防护的网站需要特殊的技术方案// src/rules/special/reprint/sbxh.ts // 处理Cloudflare Shadow DOM的复杂场景 export const sbxh () mkRuleClass({ // ... 省略其他配置 getContentFromUrl: async (chapterUrl, _name, _charset) { const result await fetchChapterViaIframe(chapterUrl); if (!result) return null; if (result.captcha) return createCaptchaMessage(); return parseViewerHtml(result.html); }, concurrencyLimit: 1, sleepTime: 1000, });4.2 多页索引网站处理对于章节分布在多个索引页的网站需要实现多页遍历// src/rules/onePageWithMultiIndexPage/template.ts export function mkMultiIndexRuleClass({ // ... 基础配置 getNextIndexPage, // 获取下一页索引页 continueIndexCondition, // 继续遍历索引页的条件 }) { return mkRuleClass({ // ... 基础配置 async bookParse() { const allChapters []; let currentIndexUrl bookUrl; do { const doc await getHtmlDOM(currentIndexUrl, charset); const chapters extractChaptersFromPage(doc); allChapters.push(...chapters); currentIndexUrl getNextIndexPage(doc); } while (continueIndexCondition(currentIndexUrl)); // ... 构建Book对象 }, }); }五、规则测试与验证5.1 本地开发测试开发新规则时建议使用本地开发环境进行测试// 测试脚本示例 import { c69shuba } from ./src/rules/onePage/69shuba; // 模拟环境测试 const rule c69shuba(); const book await rule.bookParse(); console.log(书籍名称: ${book.bookname}); console.log(章节数量: ${book.chapters.length});5.2 集成测试框架项目提供E2E测试框架确保规则质量// test/e2e-validate.ts describe(69shuba规则测试, () { test(应正确提取书籍信息, async () { const rule c69shuba(); const book await rule.bookParse(); expect(book.bookname).toBeTruthy(); expect(book.author).toBeTruthy(); expect(book.chapters.length).toBeGreaterThan(0); }); test(应正确提取章节内容, async () { const rule c69shuba(); const chapter await rule.chapterParse( https://www.69shuba.com/book/123/1.html, 第一章, false, false, utf-8, {} ); expect(chapter.contentText).toBeTruthy(); expect(chapter.contentText.length).toBeGreaterThan(100); }); });六、规则部署与贡献流程6.1 规则注册机制新规则需要在src/rules.ts中注册// src/rules.ts import { c69shuba } from ./rules/onePage/69shuba; import { exampleRule } from ./rules/onePage/example; export const rules [ // ... 现有规则 c69shuba(), exampleRule(), ];6.2 规则匹配逻辑系统通过URL正则匹配自动选择合适的规则// 规则匹配示例 const matchedRule rules.find(rule rule.url.test(currentUrl) );6.3 贡献指南提交新规则时需遵循以下规范代码质量遵循项目编码规范使用TypeScript严格类型错误处理完善的异常处理和日志记录性能优化合理设置并发限制和请求间隔文档完善提供规则说明和使用示例测试覆盖包含完整的测试用例七、高级扩展技巧7.1 自定义内容处理管道通过contentPatch函数实现自定义内容处理contentPatch: (content) { // 移除特定广告元素 const adSelectors [ .ad-container, [class*advert], [id*ad] ]; adSelectors.forEach(selector { content.querySelectorAll(selector).forEach(el el.remove()); }); // 标准化段落格式 content.querySelectorAll(p).forEach(p { p.style.margin 1em 0; }); return content; },7.2 动态配置支持支持运行时配置增强规则灵活性export const createConfigurableRule (config: RuleConfig) mkRuleClass({ bookUrl: config.bookUrl, bookname: config.booknameSelector, author: config.authorSelector, aList: config.chapterListSelector, getContentFromUrl: async (url, name, charset) { // 使用配置的选择器 const selector config.contentSelector; const doc await getHtmlDOM(url, charset); return doc.querySelector(selector); }, concurrencyLimit: config.concurrency || 10, sleepTime: config.sleepTime || 50, });八、最佳实践总结选择正确的规则模板根据网站结构选择onePage、twoPage或special模板精确的DOM选择器使用稳定的CSS选择器避免依赖易变的class名称完善的错误处理处理网络异常、DOM变化等边界情况性能优化合理设置并发限制避免对目标网站造成过大压力代码复用充分利用lib目录下的通用工具函数持续维护定期检查规则有效性及时适配网站改版通过本文的深度解析开发者可以全面掌握novel-downloader的规则扩展机制。该系统的模块化设计和丰富的工具库使得为新的小说网站添加支持变得高效而规范。无论是简单的单页网站还是复杂的Cloudflare防护站点都可以通过适当的规则实现稳定可靠的内容抓取。图novel-downloader规则扩展架构图展示核心基类、规则工厂和具体实现的关系图规则处理流程图展示从URL匹配到内容提取的完整处理流程【免费下载链接】novel-downloader一个可扩展的通用型小说下载器。项目地址: https://gitcode.com/gh_mirrors/no/novel-downloader创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考