Skip to content

404-novel-project/novel-downloader

Repository files navigation

小说下载器

一个可扩展的通用型小说下载器。

关于 404 小说文库项目

在这个 404 时代,由于种种原因,起点、晋江、刺猬猫、SF 等小说网站上的小说经常毫无征兆的消失。即使该小说已经入 V,即使你已经订阅了该小说。

这样的例子数不胜数。随便打开笔趣阁等转载网站,首发于起点,但现在起点上找不到该小说的情形比比皆是。像轻文轻小说这种整个网站都上天了的情况也不是没有。

如果小说消失时被笔趣阁等转载网站转载了,后来的读者尚且能一睹其风采,但如果小说发布的网站不是起点,小说也不够热,根本就没有转载网站转载,那后来者想一睹该小说的风采,就相当困难了。

404 小说文库项的目的是:保存这些质量上乘,但不够热门,没有被其他网站转载,彻底从互联网上消失的作品。

本脚本为 404 小说文库项目的组成部分之一,对于无登录墙的小说网站,如您同意,本脚本将会尝试将当前书籍详情页及目录页(如果存在)存档至互联网档案馆(archive.org),以备日后(被删除后)查看。

存档过程中将会搜集并上报您如下信息:IP 地址、User-Agent、Referer、当前书籍详情页 URL、当前书籍目录页 URL(如果存在)、当前小说下载器脚本版本、当前脚本管理器版本。除上述信息外,不会搜集您任何其他信息。

安装

本软件为油猴脚本,需先在浏览器安装脚本管理器(Greasemonkey、Violentmonkey、Tampermonkey),再安装本脚本。具体可参见:如何安装用户脚本

本脚本地址:

使用方法

本脚本执行下载任务时将播放无声音频,以保证脚本后台运行时不被休眠。

如果本脚本支持该小说网站,当打开小说目录页时,网页右上角会出现下载图标,点击该图标即可开始下载。

如果你要下载的小说章节较多,等待时间可能较长,此时请耐心等待。

你通过右下角进度条了解当前下载进度,或者按下 F12,打开网页控制台查看当前下载状态。

下载完成后,本脚本将会自动下载一个 TXT 文档及 EPUB 文件。

TXT 文档请使用记事本或其它阅读软件进行阅读。

EPUB 文件请使用相应阅读器阅读。

常见问题

  • Q:脚本运行出错了!

    A:在反馈之前,请保证您当前运行的脚本版本为最新版,如不是最新版,请更新脚本。

    如最新版脚本仍出现错误,请说明具体网址,有无特殊操作以及其他附加说明,并附上调试日志,协助开发者明确出错原因。调试日志为下载生成的 zip 文件中的 debug.log 文件。

    如需反馈问题,请至本项目支持页面提交 issue, 发布于 greasyfork 评论区的反馈将不会被处理。

  • Q:希望支持某某网站。

    A:请提交 issue 并附上以上信息,网站 URL,原创网站或转载网站,有无收费章节,有无如登录墙等额外限制,希望添加的理由等。开发者将视情况,酌情添加。

  • Q:请问有交流群组吗?

    A:有的。Matrix 空间:#404-novel-project:bgme.me,Telegram 群组:https://t.me/+ZCngCQiJ_xo2NDI1

目前支持小说网站(部分)

特别提醒:如欲下载支持列表中网站的付费章节,请登录相应网站帐户,并确定已购买相应付费章节。未登录网站帐户,或未购买的付费章节,下载时将直接忽略,无法进行下载。

站点 公共章节 付费章节 备注
SF 轻小说 ✅* ✅** *不支持对话小说,例:224282。 **VIP 章节仅支持图片版。
起点中文网 部分小说 VIP 章节可能出现乱码无法下载。
起点女生网
晋江文学城 晋江文学城 VIP 章节可添加 API Token 以获得更好体验,Token 添加方法参见 Token 填写一节
长佩文学 反爬较严,限制下载速度,每分钟约可下载 6 章,请耐心等待,最好不要多开页面同时下载多本长佩小说。
长佩文学为单页应用,如打开书籍详情页右上角未出现下载图标,请按下 F5 重新加载当前页面。
书耽 VIP 章节仅支持图片版。
海棠文化线上文学城
次元姬
米国度
寒武纪年原创网
哔哩哔哩漫画
息壤中文网
有毒小说网
独阅读
轻之文库轻小说 VIP 章节仅支持 APP 查看
纵横中文网
花语女生网
17K 小说网
书海小说网
塔读文学
七猫中文网 请先进入作品目录再运行脚本。
废文网 部分小说或章节需登录后查看。
pixiv 单页应用,如打开书籍详情页右上角未出现下载图标,请按下 F5 重新加载当前页面。
动漫之家 需下载大量图片,速度较慢,请耐心等待。
需占用大量内存,请保证最终生成文件 4 倍以上内存,即最终下载生成 500MB ZIP 文件,运行时请保证至少 2GB 内存空间。可使用筛选函数,分次下载。
Lofter 因本脚本会将博文中的图片也一同下载下来,对于图片特别多的博客,下载时请注意内存用量(800MB 限制),根据实际情况使用筛选函数分次下载。
部分博文内含视频内容,为节省内存使用,加快下载速度,本脚本将跳过视频内容。
如您使用广告屏蔽器,可能会影响本脚本在 Lofter 的工作。
努努书坊 格式众多,如发现不支持页面敬请反馈。
真白萌
天涯书库
爱青果
カクヨム
小説家になろう
ハーメルン
ファンタジー小説
Novel Up Plus
点击查看全部支持网站
站点 公共章节 付费章节 备注
禁忌书屋
UU 看书网
亿软网 网站性能差,降低抓取频率,请耐心等待。
书趣阁 网站性能差,降低抓取频率,请耐心等待。
星空中文
乐文小说网
266 看书
和图书
阁笔趣
书书网
八一中文网 抓取速度慢,请耐心等待。
御书阁 部分文字被图片替换,请使用 HTML 版查看。
完本神站
得间小说
轻小说文库
西方奇幻小说网
棉花糖小说网
笔趣阁
红叶书斋
哩哔轻小说 因需要基于邻近章节修复隐藏链接的章节,生成目录时间变慢,大约2分钟。
落秋中文
一笔阁
腐书网
搜小说
腐国度
书包网
恋上你看书
同人小说网
同人圈
精品小说网
256 文学
笔趣阁小说网
海棠小说网 部分文字被图片替换,请使用 HTML 版查看。
如需替换清理图片,请自行生成图片文字对照表。
笔趣阁
25 中文网
天域小说网
完本神站
燃文小说
望书阁
百合小说网
全书斋
蔷薇后花园
黑沼泽俱乐部
神凑轻小说
爱下书小说网
精彩小说网
爱下电子书
笔趣阁
言情小说
18 看书
笔下文学 333
小说屋
缤纷幻想
弟子小说网
新笔趣阁
69 书吧
笔下文学
飘天文学网
红袖招
38 看书
天天看小说
精华书阁
全职小说网 网站反爬较严,大量抓取可能导致封禁ip。
笔趣阁
新笔趣阁
全本同人小说
鬼大爷网

特殊权限说明

  • unsafeWindow:用于获取自定义筛选函数、自定义保存参数等设置。
  • GM_info/GM.info: 获取并输出脚本运行环境。
  • GM_xmlhttpRequest/GM.xmlHttpRequest:用于跨域 HTTP 请求。
  • GM_setValue/GM.setValueGM_getValue/GM.getValueGM_deleteValue/GM.deleteValue: 用于统计模块,本地统计运行次数。

Token 填写

当前部分网站(如晋江文学城)需要手动填写登录 token。app 平台一般为 Android 平台,其他平台一般不能使用(但也可以实践看看)。

抓包软件和教程

这里列举出一些常用的工具以供参考。

  1. 抓包精灵(Android)

下载抓包精灵/NetCapture(可在 Google Play、酷安搜索到,其他软件也可以)并配置好设置。

可参考 @ll0yiya 的经验(https://github.com/404-novel-project/novel-downloader/issues/599#issuecomment-1866142314)。

  1. HttpCanary(Android)

可参考 https://blog.csdn.net/weixin_53891182/article/details/124739048 等资料。

在编写脚本时,作者使用的版本:https://fin.lanzoub.com/iGYv20vym1dc。

  1. eCapture(Android)

需要 Android 内核版本 5.4以上。

详细信息,建议参考 https://mp.weixin.qq.com/s/KWm5d0uuzOzReRtr9PmuWQ 等资料。

  1. Charles(Windows / MacOS / Linux)

官网: https://www.charlesproxy.com/

需要电脑,可搭配 Android 模拟器。

可参考 https://blog.csdn.net/qq_41631913/article/details/135748992。

注:本节脚本代码中的值均为编撰,仅为示意。

晋江文学城

该网站现提供 2 个方法以获取 token 。

  1. 脚本设置获取
  • 在任意晋江小说页面加载完时打开设置,在“基本设置”选项卡中会看到“获取token”按钮(仅限第一次打开可见),点击按钮并关闭设置,按页面提示输入账号和密码后点击登录,如收到验证码则填入验证码后再次点击登录,如操作无误则可以看到返回的 token 值。

  • 该方法可能需要禁用主设备验证功能。如获取失败可根据提示重试、先在晋江 app 里重新登录后再试。如一直不能获取,则参考方法 2 。


  1. 自行抓取。
  • 要抓取的数据:
    • token: 登录晋江文学城 Android app 并随意浏览章节,在形如“https://app.jjwxc.org/androidapi/chapterContent?” 等链接中找到 &token= 后的字符串(止于下一个&)
    • user_key 方法同上,数据为 &user_key= 后的字符串(止于下一个&)

注:user_key对于部分账号可能是必须的,如果你采用方法2,可以将两个一起抓取。


成功抓取 token 后,在脚本管理器中新建如下脚本(不要把该脚本代码和其他脚本代码合并,除非你完全理解脚本的意思)并保存:

// ==UserScript==
// @name         auto inject tokenOptions
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  auto inject tokenOptions
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const tokenOptions = {
    Jjwxc: "11111111_750afc84c839aaaaafccd841fffd11f1", //填入token,形如客户号+下划线'_'+字母与数字混合的字符串
  };
  window.tokenOptions = tokenOptions;
})();

如果你采用方法2,成功抓取 token 和 user_key 后,在脚本管理器中新建如下脚本(不要把该脚本代码和其他脚本代码合并,除非你完全理解脚本的意思)并保存:

// ==UserScript==
// @name         auto inject tokenOptions
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  auto inject tokenOptions
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const tokenOptions = {
    Jjwxc: {
      token:"11111111_750afc84c839aaaaafccd841fffd11f1", //填入token,形如客户号+下划线'_'+字母与数字混合的字符串
      user_key:"11ffffff-11ff-11ff-11ff-111111111fff",//填入user_key
    }
  };
  window.tokenOptions = tokenOptions;
})();

Caution

在设置中启用调试模式以后,日志可能会输出一些包含 token 的链接,这个设计的初衷是为了更快定位发生的问题。

请不要直接将该日志上传到互联网上,当且仅当从事维护的开发者需要此日志定位问题时再提供(可以通过重新登录之前抓取 token 设备上的晋江 app 以使原 token 失效)。

息壤中文网

该网站的 Android app 启动时会检测 root 和 VPN 代理, 因此可能需要一些额外的操作以越过;此外 header 数据的获取需要安装CA证书,建议具有一定相关知识的人士进行操作。

需要抓取的数据:登录息壤中文网 Android app 并随意浏览章节,在形如“https://android-api.xrzww.com/api/readWithEncrypt”的网址中转到 Request header(即请求头)页,header 中的 deviceIdentify 和 Authorization 即为需要抓取的数据(注意不要弄成 Response header(响应头))。

在脚本管理器中新建如下脚本(不要把该脚本代码和其他脚本代码合并,除非你完全理解脚本的意思)并保存:

// ==UserScript==
// @name         auto inject tokenOptions
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  auto inject tokenOptions
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const tokenOptions = {
    Xrzww: {
            deviceIdentify: "webh517657567560", //填入 header中的deviceIdentify值
            Authorization:  "Bearer 453453453e03ee546456546754756756", //填入 header中的Authorization值
        },
  };
  window.tokenOptions = tokenOptions;
})();

高阶使用技巧

启用调试功能

设置菜单中按击开启。

自定义筛选函数

如欲只下载部分章节,请在点击运行按钮前,按下 F12 打开开发者工具,在 window 下创建 chapterFilter 函数,具体格式如下:

declare enum Status {
  pending = 0,
  downloading = 1,
  failed = 2,
  finished = 3,
  aborted = 4,
  saved = 5,
}
interface ChapterAdditionalMetadate {
  lastModified?: number;
}
declare class Chapter {
  bookUrl: string;
  bookname: string;
  chapterUrl: string;
  chapterNumber: number;
  chapterName: string | null;
  isVIP: boolean;
  isPaid: boolean | null;
  sectionName: string | null;
  sectionNumber: number | null;
  sectionChapterNumber: number | null;
  chapterParse: BaseRuleClass["chapterParse"];
  charset: string;
  options: object;
  status: Status;
  retryTime: number;
  contentRaw: HTMLElement | null;
  contentText: string | null;
  contentHTML: HTMLElement | null;
  contentImages: attachmentClass[] | null;
  additionalMetadate: ChapterAdditionalMetadate | null;
  chapterHtmlFileName: string | number;
  constructor(
    bookUrl: string,
    bookname: string,
    chapterUrl: string,
    chapterNumber: number,
    chapterName: string | null,
    isVIP: boolean,
    isPaid: boolean | null,
    sectionName: string | null,
    sectionNumber: number | null,
    sectionChapterNumber: number | null,
    chapterParse: BaseRuleClass["chapterParse"],
    charset: string,
    options: object
  );
  init(): Promise<this>;
  private parse;
}
declare class attachmentClass {
  url: string;
  name: string;
  mode: "naive" | "TM";
  headers?: {
    [index: string]: string;
  };
  private defaultHeader;
  status: Status;
  retryTime: number;
  imageBlob: Blob | null | void;
  constructor(imageUrl: string, name: string, mode: "naive" | "TM");
  init(): Promise<Blob | null>;
  private downloadImage;
  private tmDownloadImage;
}

interface chapterFilter {
  (chapter: Chapter): boolean;
}

自定义筛选函数示例:

只下载该本小说前 100 章内容:

function chapterFilter(chapter) {
  return chapter.chapterNumber <= 100;
}

只下载第一卷内容:

function chapterFilter(chapter) {
  return chapter.sectionNumber === 1;
}

只下载章节名称中含有“武器”的章节:

function chapterFilter(chapter) {
  return chapter.chapterName.includes("武器");
}

自定义保存参数

自定义保存参数允许您修改保存文件的样式,章节标题等内容。

使用方法大致同自定义筛选函数,即在 window 下创建 saveOptions 对象,具体格式如下:

interface SaveOptions {
  mainStyleText?: string;
  tocStyleText?: string;
  getchapterName?: Options["getchapterName"];
  //函数定义为:getchapterName(chapter: Chapter): string;
  genSectionText?: Options["genSectionText"];
  //函数定义为:genSectionText(sectionName: string): string;
  genChapterText?: Options["genChapterText"];
  //函数定义为:genChapterText(chapterName: string, contentText: string): string;
  genChapterEpub?: Options["genChapterEpub"];
  //函数定义为:genChapterEpub(contentXHTML: string):string;
  chapterSort?: Options["chapterSort"];
  //函数定义为:chapterSort(a: Chapter, b: Chapter): 0 | 1 | -1;
}

自定义保存参数示例:

将章节名称格式修改为 第xx章 xxxx

const saveOptions = {
  getchapterName: (chapter) => {
    if (chapter.chapterName) {
      return `第${chapter.chapterNumber.toString()}${chapter.chapterName}`;
    } else {
      return `第${chapter.chapterNumber.toString()}章`;
    }
  },
};
window.saveOptions = saveOptions;

更改 ZIP 文档中章节 HTML 文件样式:

const saveOptions = {
  mainStyleText: `p {
  text-indent: 4em;
  display: block;
  line-height: 1.3em;
  margin-top: 0.4em;
  margin-bottom: 0.4em;
}`,
};
window.saveOptions = saveOptions;

txt 文档每个自然段前加两个空格

const saveOptions = {
  genChapterText: (chapterName, contentText) => {
    contentText = contentText
      .split("\n")
      .map((line) => {
        if (line.trim() === "") {
          return line;
        } else {
          return line.replace(/^/, "    ");
        }
      })
      .join("\n");
    return `## ${chapterName}\n\n${contentText}\n\n`;
  },
};
window.saveOptions = saveOptions;

epub 文档删除章节空行

const saveOptions = {
  genChapterEpub: (contentXHTML) => {
    return contentXHTML.replaceAll("<p><br /></p>", "")
      .replaceAll("<p><br/></p>", "");
  },
};
window.saveOptions = saveOptions;

保存章节时倒序排列

const saveOptions = {
  chapterSort: (a, b) => {
    if (a.chapterNumber > b.chapterNumber) {
      return -1;
    }
    if (a.chapterNumber === b.chapterNumber) {
      return 0;
    }
    if (a.chapterNumber < b.chapterNumber) {
      return 1;
    }
    return 0;
  },
};
window.saveOptions = saveOptions;

使用用户脚本自动注入自定义保存参数:

如您总是想使用某一自定义保存参数,你可以使用如下用户脚本(根据实际需要修改相应数值),自动向页面注入自定义保存参数。

// ==UserScript==
// @name         auto inject saveOptions
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  auto inject saveOptions
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const saveOptions = {
    getchapterName: (chapter) => {
      if (chapter.chapterName) {
        return `第${chapter.chapterNumber.toString()}${chapter.chapterName}`;
      } else {
        return `第${chapter.chapterNumber.toString()}章`;
      }
    },
  };
  window.saveOptions = saveOptions;
})();

自定义筛选函数同理也可使用用户脚本自动注入。

自定义完成回调函数

interface customFinishCallback {
  (): void;
}

自定义完成回调函数将在下载完成并生成 ZIP 文件后自动执行。

使用自定义完成回调函数可在下载完成后自动完成某些工作,例如:关闭当前窗口。

function customFinishCallback(book: Book) {
  window.close();
}
window.customFinishCallback = customFinishCallback;

附录 完整的自定义脚本

// ==UserScript==
// @name         Noveldownloader Settings
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Noveldownloader Settings
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  // token 设置开始
  const tokenOptions = {
    Jjwxc: {
      token:"11111111_750afc84c839aaaaafccd841fffd11f1", //填入token,形如客户号+下划线'_'+字母与数字混合的字符串
      user_key:"11ffffff-11ff-11ff-11ff-111111111fff",//填入user_key
    },
    Xrzww: {
            deviceIdentify: "webh517657567560", //填入 header中的deviceIdentify值
            Authorization:  "Bearer 453453453e03ee546456546754756756", //填入 header中的Authorization值
        },
  };
  
  //token 设置结束

  // 章节过滤筛选开始
  function chapterFilter(chapter) {
    return chapter.chapterNumber <= 100;
  }
  //章节过滤筛选结束

  //保存设置开始
  const saveOptions = {
    getchapterName: (chapter) => {
      if (chapter.chapterName) {
        return `第${chapter.chapterNumber.toString()}${chapter.chapterName}`;
      } else {
        return `第${chapter.chapterNumber.toString()}章`;
      } // 按 第i章 XXX 命名章节名字
    },
    genChapterEpub: (contentXHTML) => {
      return contentXHTML.replaceAll("<p><br /></p>", "")
        .replaceAll("<p><br/></p>", "");
    },
  };
  //保存设置结束

  if(saveOptions)
    window.saveOptions = saveOptions;
  if(tokenOptions)
  window.tokenOptions = tokenOptions;
  if(chapterFilter)
    window.chapterFilter = chapterFilter;
})();

开发

  1. git clone https://github.com/yingziwu/novel-downloader.git 将项目克隆至本地(访问 github 可能需要使用代理)。
  2. yarn install 安装依赖。
  3. 继承 BaseRuleClass 类,实现 bookParsechapterParse 抽象方法,在 router/download.ts 文件中添加相应选择规则,在 header.json 文件 match 字段添加相应的匹配规则。
  4. yarn run build 编译生成最终脚本文件 dist/bundle.user.js

License

AGPL-3.0

致谢

感谢 JetBrains 向本项目提供 WebStorm IDE。