novel-downloader 1.2.1__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/PKG-INFO +14 -17
  2. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/README.md +8 -8
  3. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/__init__.py +1 -2
  4. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/cli/__init__.py +0 -1
  5. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/cli/clean.py +2 -10
  6. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/cli/download.py +18 -22
  7. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/cli/interactive.py +0 -1
  8. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/cli/main.py +1 -3
  9. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/cli/settings.py +8 -8
  10. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/config/__init__.py +0 -1
  11. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/config/adapter.py +48 -18
  12. novel_downloader-1.3.0/novel_downloader/config/loader.py +185 -0
  13. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/config/models.py +41 -32
  14. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/config/site_rules.py +2 -4
  15. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/__init__.py +0 -1
  16. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/downloaders/__init__.py +4 -4
  17. novel_downloader-1.3.0/novel_downloader/core/downloaders/base/__init__.py +14 -0
  18. novel_downloader-1.2.1/novel_downloader/core/downloaders/base_async_downloader.py → novel_downloader-1.3.0/novel_downloader/core/downloaders/base/base_async.py +49 -53
  19. novel_downloader-1.2.1/novel_downloader/core/downloaders/base_downloader.py → novel_downloader-1.3.0/novel_downloader/core/downloaders/base/base_sync.py +64 -43
  20. novel_downloader-1.3.0/novel_downloader/core/downloaders/biquge/__init__.py +12 -0
  21. novel_downloader-1.3.0/novel_downloader/core/downloaders/biquge/biquge_sync.py +25 -0
  22. novel_downloader-1.3.0/novel_downloader/core/downloaders/common/__init__.py +14 -0
  23. novel_downloader-1.2.1/novel_downloader/core/downloaders/common_asynb_downloader.py → novel_downloader-1.3.0/novel_downloader/core/downloaders/common/common_async.py +42 -33
  24. novel_downloader-1.2.1/novel_downloader/core/downloaders/common_downloader.py → novel_downloader-1.3.0/novel_downloader/core/downloaders/common/common_sync.py +34 -23
  25. novel_downloader-1.3.0/novel_downloader/core/downloaders/qidian/__init__.py +10 -0
  26. novel_downloader-1.2.1/novel_downloader/core/downloaders/qidian_downloader.py → novel_downloader-1.3.0/novel_downloader/core/downloaders/qidian/qidian_sync.py +80 -64
  27. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/factory/__init__.py +4 -5
  28. novel_downloader-1.2.1/novel_downloader/core/factory/downloader_factory.py → novel_downloader-1.3.0/novel_downloader/core/factory/downloader.py +36 -35
  29. novel_downloader-1.2.1/novel_downloader/core/factory/parser_factory.py → novel_downloader-1.3.0/novel_downloader/core/factory/parser.py +12 -14
  30. novel_downloader-1.2.1/novel_downloader/core/factory/requester_factory.py → novel_downloader-1.3.0/novel_downloader/core/factory/requester.py +29 -16
  31. novel_downloader-1.2.1/novel_downloader/core/factory/saver_factory.py → novel_downloader-1.3.0/novel_downloader/core/factory/saver.py +4 -9
  32. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/interfaces/__init__.py +8 -9
  33. novel_downloader-1.2.1/novel_downloader/core/interfaces/async_downloader_protocol.py → novel_downloader-1.3.0/novel_downloader/core/interfaces/async_downloader.py +4 -5
  34. novel_downloader-1.2.1/novel_downloader/core/interfaces/async_requester_protocol.py → novel_downloader-1.3.0/novel_downloader/core/interfaces/async_requester.py +26 -12
  35. novel_downloader-1.2.1/novel_downloader/core/interfaces/parser_protocol.py → novel_downloader-1.3.0/novel_downloader/core/interfaces/parser.py +11 -6
  36. novel_downloader-1.2.1/novel_downloader/core/interfaces/saver_protocol.py → novel_downloader-1.3.0/novel_downloader/core/interfaces/saver.py +2 -3
  37. novel_downloader-1.2.1/novel_downloader/core/interfaces/downloader_protocol.py → novel_downloader-1.3.0/novel_downloader/core/interfaces/sync_downloader.py +6 -7
  38. novel_downloader-1.2.1/novel_downloader/core/interfaces/requester_protocol.py → novel_downloader-1.3.0/novel_downloader/core/interfaces/sync_requester.py +34 -17
  39. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/parsers/__init__.py +5 -4
  40. novel_downloader-1.2.1/novel_downloader/core/parsers/base_parser.py → novel_downloader-1.3.0/novel_downloader/core/parsers/base.py +20 -11
  41. novel_downloader-1.3.0/novel_downloader/core/parsers/biquge/__init__.py +10 -0
  42. novel_downloader-1.3.0/novel_downloader/core/parsers/biquge/main_parser.py +126 -0
  43. {novel_downloader-1.2.1/novel_downloader/core/parsers/common_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/common}/__init__.py +2 -3
  44. {novel_downloader-1.2.1/novel_downloader/core/parsers/common_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/common}/helper.py +20 -18
  45. {novel_downloader-1.2.1/novel_downloader/core/parsers/common_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/common}/main_parser.py +15 -9
  46. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/__init__.py +2 -3
  47. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/browser/__init__.py +2 -3
  48. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/browser/chapter_encrypted.py +41 -49
  49. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/browser/chapter_normal.py +17 -21
  50. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/browser/chapter_router.py +10 -9
  51. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/browser/main_parser.py +16 -12
  52. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/session/__init__.py +2 -3
  53. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/session/chapter_encrypted.py +37 -45
  54. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/session/chapter_normal.py +19 -23
  55. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/session/chapter_router.py +10 -9
  56. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/session/main_parser.py +16 -12
  57. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/session/node_decryptor.py +7 -10
  58. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/shared/__init__.py +2 -3
  59. novel_downloader-1.3.0/novel_downloader/core/parsers/qidian/shared/book_info_parser.py +150 -0
  60. {novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser → novel_downloader-1.3.0/novel_downloader/core/parsers/qidian}/shared/helpers.py +9 -10
  61. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/requesters/__init__.py +9 -5
  62. novel_downloader-1.3.0/novel_downloader/core/requesters/base/__init__.py +16 -0
  63. novel_downloader-1.2.1/novel_downloader/core/requesters/base_async_session.py → novel_downloader-1.3.0/novel_downloader/core/requesters/base/async_session.py +180 -73
  64. novel_downloader-1.3.0/novel_downloader/core/requesters/base/browser.py +340 -0
  65. novel_downloader-1.3.0/novel_downloader/core/requesters/base/session.py +364 -0
  66. novel_downloader-1.3.0/novel_downloader/core/requesters/biquge/__init__.py +12 -0
  67. novel_downloader-1.3.0/novel_downloader/core/requesters/biquge/session.py +90 -0
  68. {novel_downloader-1.2.1/novel_downloader/core/requesters/common_requester → novel_downloader-1.3.0/novel_downloader/core/requesters/common}/__init__.py +4 -5
  69. novel_downloader-1.3.0/novel_downloader/core/requesters/common/async_session.py +96 -0
  70. novel_downloader-1.3.0/novel_downloader/core/requesters/common/session.py +113 -0
  71. novel_downloader-1.3.0/novel_downloader/core/requesters/qidian/__init__.py +21 -0
  72. novel_downloader-1.3.0/novel_downloader/core/requesters/qidian/broswer.py +306 -0
  73. novel_downloader-1.3.0/novel_downloader/core/requesters/qidian/session.py +287 -0
  74. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/savers/__init__.py +5 -3
  75. novel_downloader-1.2.1/novel_downloader/core/savers/base_saver.py → novel_downloader-1.3.0/novel_downloader/core/savers/base.py +12 -13
  76. novel_downloader-1.3.0/novel_downloader/core/savers/biquge.py +25 -0
  77. {novel_downloader-1.2.1/novel_downloader/core/savers/common_saver → novel_downloader-1.3.0/novel_downloader/core/savers/common}/__init__.py +2 -3
  78. novel_downloader-1.2.1/novel_downloader/core/savers/common_saver/common_epub.py → novel_downloader-1.3.0/novel_downloader/core/savers/common/epub.py +24 -52
  79. {novel_downloader-1.2.1/novel_downloader/core/savers/common_saver → novel_downloader-1.3.0/novel_downloader/core/savers/common}/main_saver.py +43 -9
  80. novel_downloader-1.2.1/novel_downloader/core/savers/common_saver/common_txt.py → novel_downloader-1.3.0/novel_downloader/core/savers/common/txt.py +16 -46
  81. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/savers/epub_utils/__init__.py +0 -1
  82. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/savers/epub_utils/css_builder.py +13 -7
  83. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/savers/epub_utils/initializer.py +4 -5
  84. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/savers/epub_utils/text_to_html.py +2 -3
  85. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/core/savers/epub_utils/volume_intro.py +1 -3
  86. novel_downloader-1.2.1/novel_downloader/core/savers/qidian_saver.py → novel_downloader-1.3.0/novel_downloader/core/savers/qidian.py +12 -6
  87. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/locales/en.json +12 -4
  88. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/locales/zh.json +9 -1
  89. novel_downloader-1.3.0/novel_downloader/resources/config/settings.toml +88 -0
  90. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/cache.py +2 -2
  91. novel_downloader-1.3.0/novel_downloader/utils/chapter_storage.py +340 -0
  92. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/constants.py +8 -5
  93. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/crypto_utils.py +3 -3
  94. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/file_utils/__init__.py +0 -1
  95. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/file_utils/io.py +12 -17
  96. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/file_utils/normalize.py +1 -3
  97. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/file_utils/sanitize.py +2 -9
  98. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/fontocr/__init__.py +0 -1
  99. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/fontocr/ocr_v1.py +19 -22
  100. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/fontocr/ocr_v2.py +147 -60
  101. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/hash_store.py +19 -20
  102. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/hash_utils.py +0 -1
  103. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/i18n.py +3 -4
  104. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/logger.py +5 -6
  105. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/model_loader.py +5 -8
  106. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/network.py +9 -10
  107. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/state.py +6 -7
  108. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/text_utils/__init__.py +0 -1
  109. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/text_utils/chapter_formatting.py +2 -7
  110. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/text_utils/diff_display.py +0 -1
  111. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/text_utils/font_mapping.py +1 -4
  112. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/text_utils/text_cleaning.py +0 -1
  113. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/time_utils/__init__.py +0 -1
  114. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/time_utils/datetime_utils.py +9 -11
  115. novel_downloader-1.3.0/novel_downloader/utils/time_utils/sleep_utils.py +63 -0
  116. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader.egg-info/PKG-INFO +14 -17
  117. novel_downloader-1.3.0/novel_downloader.egg-info/SOURCES.txt +130 -0
  118. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader.egg-info/requires.txt +3 -4
  119. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/pyproject.toml +20 -14
  120. novel_downloader-1.2.1/novel_downloader/config/loader.py +0 -177
  121. novel_downloader-1.2.1/novel_downloader/core/parsers/qidian_parser/shared/book_info_parser.py +0 -95
  122. novel_downloader-1.2.1/novel_downloader/core/requesters/base_browser.py +0 -210
  123. novel_downloader-1.2.1/novel_downloader/core/requesters/base_session.py +0 -243
  124. novel_downloader-1.2.1/novel_downloader/core/requesters/common_requester/common_async_session.py +0 -98
  125. novel_downloader-1.2.1/novel_downloader/core/requesters/common_requester/common_session.py +0 -126
  126. novel_downloader-1.2.1/novel_downloader/core/requesters/qidian_requester/__init__.py +0 -22
  127. novel_downloader-1.2.1/novel_downloader/core/requesters/qidian_requester/qidian_broswer.py +0 -377
  128. novel_downloader-1.2.1/novel_downloader/core/requesters/qidian_requester/qidian_session.py +0 -202
  129. novel_downloader-1.2.1/novel_downloader/resources/config/settings.yaml +0 -76
  130. novel_downloader-1.2.1/novel_downloader/utils/time_utils/sleep_utils.py +0 -49
  131. novel_downloader-1.2.1/novel_downloader.egg-info/SOURCES.txt +0 -118
  132. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/LICENSE +0 -0
  133. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/resources/config/rules.toml +0 -0
  134. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/resources/css_styles/main.css +0 -0
  135. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/resources/css_styles/volume-intro.css +0 -0
  136. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/resources/images/volume_border.png +0 -0
  137. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/resources/js_scripts/qidian_decrypt_node.js +0 -0
  138. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/resources/json/replace_word_map.json +0 -0
  139. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/resources/text/blacklist.txt +0 -0
  140. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader/utils/__init__.py +0 -0
  141. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader.egg-info/dependency_links.txt +0 -0
  142. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader.egg-info/entry_points.txt +0 -0
  143. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/novel_downloader.egg-info/top_level.txt +0 -0
  144. {novel_downloader-1.2.1 → novel_downloader-1.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: novel-downloader
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Summary: A command-line tool for downloading Chinese web novels from Qidian and similar platforms.
5
5
  Author-email: Saudade Z <saudadez217@gmail.com>
6
6
  License: MIT License
@@ -34,18 +34,16 @@ Classifier: License :: OSI Approved :: MIT License
34
34
  Classifier: Natural Language :: Chinese (Simplified)
35
35
  Classifier: Topic :: Utilities
36
36
  Classifier: Programming Language :: Python :: 3
37
- Classifier: Programming Language :: Python :: 3.8
38
- Classifier: Programming Language :: Python :: 3.9
39
- Classifier: Programming Language :: Python :: 3.10
40
- Classifier: Programming Language :: Python :: 3.11
41
37
  Classifier: Programming Language :: Python :: 3.12
42
- Requires-Python: >=3.8
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Requires-Python: >=3.12
43
40
  Description-Content-Type: text/markdown
44
41
  License-File: LICENSE
45
42
  Requires-Dist: requests
43
+ Requires-Dist: aiohttp
46
44
  Requires-Dist: beautifulsoup4
47
45
  Requires-Dist: DrissionPage
48
- Requires-Dist: pyyaml
46
+ Requires-Dist: opencv-python
49
47
  Requires-Dist: lxml
50
48
  Requires-Dist: platformdirs
51
49
  Requires-Dist: click
@@ -64,10 +62,9 @@ Requires-Dist: scipy; extra == "font-recovery"
64
62
  Requires-Dist: numpy; extra == "font-recovery"
65
63
  Requires-Dist: tinycss2; extra == "font-recovery"
66
64
  Requires-Dist: fonttools; extra == "font-recovery"
65
+ Requires-Dist: brotli; extra == "font-recovery"
67
66
  Requires-Dist: pillow; extra == "font-recovery"
68
67
  Requires-Dist: huggingface_hub; extra == "font-recovery"
69
- Provides-Extra: async
70
- Requires-Dist: aiohttp; extra == "async"
71
68
  Dynamic: license-file
72
69
 
73
70
  # novel-downloader
@@ -94,13 +91,10 @@ pip install novel-downloader
94
91
  # 如需支持字体解密功能 (decode_font), 请使用:
95
92
  # pip install novel-downloader[font-recovery]
96
93
 
97
- # 如需启用异步抓取模式 (mode=async), 请使用:
98
- # pip install novel-downloader[async]
99
-
100
- # 初始化默认配置 (生成 settings.yaml)
94
+ # 初始化默认配置 (生成 settings.toml)
101
95
  novel-cli settings init
102
96
 
103
- # 编辑 ./settings.yaml 完成 site/book_ids 等
97
+ # 编辑 ./settings.toml 完成 site/book_ids 等
104
98
  # 可查看 docs/4-settings-schema.md
105
99
 
106
100
  # 运行下载
@@ -117,7 +111,6 @@ cd novel-downloader
117
111
  pip install .
118
112
  # 或安装带可选功能:
119
113
  # pip install .[font-recovery]
120
- # pip install .[async]
121
114
  ```
122
115
 
123
116
  更多使用方法, 查看 [使用示例](https://github.com/BowenZ217/novel-downloader/blob/main/docs/5-usage-examples.md)
@@ -127,7 +120,10 @@ pip install .
127
120
  ## 功能特性
128
121
 
129
122
  - 爬取起点中文网的小说章节内容 (支持免费与已订阅章节)
130
- - 自动整合所有章节并输出为完整的 TXT 文件
123
+ - 断点续爬
124
+ - 自动整合所有章节并导出为
125
+ - TXT
126
+ - EPUB
131
127
  - 支持活动广告过滤:
132
128
  - [x] 章节标题
133
129
  - [ ] 章节正文
@@ -141,8 +137,9 @@ pip install .
141
137
  - [安装](https://github.com/BowenZ217/novel-downloader/blob/main/docs/1-installation.md)
142
138
  - [环境准备](https://github.com/BowenZ217/novel-downloader/blob/main/docs/2-environment-setup.md)
143
139
  - [配置](https://github.com/BowenZ217/novel-downloader/blob/main/docs/3-configuration.md)
144
- - [settings.yaml 配置说明](https://github.com/BowenZ217/novel-downloader/blob/main/docs/4-settings-schema.md)
140
+ - [settings.toml 配置说明](https://github.com/BowenZ217/novel-downloader/blob/main/docs/4-settings-schema.md)
145
141
  - [使用示例](https://github.com/BowenZ217/novel-downloader/blob/main/docs/5-usage-examples.md)
142
+ - [支持站点列表](https://github.com/BowenZ217/novel-downloader/blob/main/docs/6-supported-sites.md)
146
143
  - [文件保存](https://github.com/BowenZ217/novel-downloader/blob/main/docs/file-saving.md)
147
144
  - [TODO](https://github.com/BowenZ217/novel-downloader/blob/main/docs/todo.md)
148
145
  - [开发](https://github.com/BowenZ217/novel-downloader/blob/main/docs/develop.md)
@@ -22,13 +22,10 @@ pip install novel-downloader
22
22
  # 如需支持字体解密功能 (decode_font), 请使用:
23
23
  # pip install novel-downloader[font-recovery]
24
24
 
25
- # 如需启用异步抓取模式 (mode=async), 请使用:
26
- # pip install novel-downloader[async]
27
-
28
- # 初始化默认配置 (生成 settings.yaml)
25
+ # 初始化默认配置 (生成 settings.toml)
29
26
  novel-cli settings init
30
27
 
31
- # 编辑 ./settings.yaml 完成 site/book_ids 等
28
+ # 编辑 ./settings.toml 完成 site/book_ids 等
32
29
  # 可查看 docs/4-settings-schema.md
33
30
 
34
31
  # 运行下载
@@ -45,7 +42,6 @@ cd novel-downloader
45
42
  pip install .
46
43
  # 或安装带可选功能:
47
44
  # pip install .[font-recovery]
48
- # pip install .[async]
49
45
  ```
50
46
 
51
47
  更多使用方法, 查看 [使用示例](https://github.com/BowenZ217/novel-downloader/blob/main/docs/5-usage-examples.md)
@@ -55,7 +51,10 @@ pip install .
55
51
  ## 功能特性
56
52
 
57
53
  - 爬取起点中文网的小说章节内容 (支持免费与已订阅章节)
58
- - 自动整合所有章节并输出为完整的 TXT 文件
54
+ - 断点续爬
55
+ - 自动整合所有章节并导出为
56
+ - TXT
57
+ - EPUB
59
58
  - 支持活动广告过滤:
60
59
  - [x] 章节标题
61
60
  - [ ] 章节正文
@@ -69,8 +68,9 @@ pip install .
69
68
  - [安装](https://github.com/BowenZ217/novel-downloader/blob/main/docs/1-installation.md)
70
69
  - [环境准备](https://github.com/BowenZ217/novel-downloader/blob/main/docs/2-environment-setup.md)
71
70
  - [配置](https://github.com/BowenZ217/novel-downloader/blob/main/docs/3-configuration.md)
72
- - [settings.yaml 配置说明](https://github.com/BowenZ217/novel-downloader/blob/main/docs/4-settings-schema.md)
71
+ - [settings.toml 配置说明](https://github.com/BowenZ217/novel-downloader/blob/main/docs/4-settings-schema.md)
73
72
  - [使用示例](https://github.com/BowenZ217/novel-downloader/blob/main/docs/5-usage-examples.md)
73
+ - [支持站点列表](https://github.com/BowenZ217/novel-downloader/blob/main/docs/6-supported-sites.md)
74
74
  - [文件保存](https://github.com/BowenZ217/novel-downloader/blob/main/docs/file-saving.md)
75
75
  - [TODO](https://github.com/BowenZ217/novel-downloader/blob/main/docs/todo.md)
76
76
  - [开发](https://github.com/BowenZ217/novel-downloader/blob/main/docs/develop.md)
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  novel_downloader
5
4
  ----------------
@@ -7,7 +6,7 @@ novel_downloader
7
6
  Core package for the Novel Downloader project.
8
7
  """
9
8
 
10
- __version__ = "1.2.1"
9
+ __version__ = "1.3.0"
11
10
 
12
11
  __author__ = "Saudade Z"
13
12
  __email__ = "saudadez217@gmail.com"
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  novel_downloader.cli
5
4
  --------------------
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  novel_downloader.cli.clean
5
4
  -----------------------------
@@ -8,7 +7,6 @@ novel_downloader.cli.clean
8
7
 
9
8
  import shutil
10
9
  from pathlib import Path
11
- from typing import List, Optional
12
10
 
13
11
  import click
14
12
 
@@ -19,7 +17,6 @@ from novel_downloader.utils.constants import (
19
17
  LOGGER_DIR,
20
18
  MODEL_CACHE_DIR,
21
19
  REC_CHAR_MODEL_REPO,
22
- STATE_DIR,
23
20
  )
24
21
  from novel_downloader.utils.i18n import t
25
22
 
@@ -35,7 +32,7 @@ def delete_path(p: Path) -> None:
35
32
  click.echo(f"[clean] {t('clean_not_found')}: {p}")
36
33
 
37
34
 
38
- def clean_model_repo_cache(repo_id: Optional[str] = None, all: bool = False) -> bool:
35
+ def clean_model_repo_cache(repo_id: str | None = None, all: bool = False) -> bool:
39
36
  """
40
37
  Delete Hugging Face cache for a specific repo.
41
38
  """
@@ -61,7 +58,6 @@ def clean_model_repo_cache(repo_id: Optional[str] = None, all: bool = False) ->
61
58
  @click.command(name="clean", help=t("help_clean")) # type: ignore
62
59
  @click.option("--logs", is_flag=True, help=t("clean_logs")) # type: ignore
63
60
  @click.option("--cache", is_flag=True, help=t("clean_cache")) # type: ignore
64
- @click.option("--state", is_flag=True, help=t("clean_state")) # type: ignore
65
61
  @click.option("--data", is_flag=True, help=t("clean_data")) # type: ignore
66
62
  @click.option("--config", is_flag=True, help=t("clean_config")) # type: ignore
67
63
  @click.option("--models", is_flag=True, help=t("clean_models")) # type: ignore
@@ -72,7 +68,6 @@ def clean_model_repo_cache(repo_id: Optional[str] = None, all: bool = False) ->
72
68
  def clean_cli(
73
69
  logs: bool,
74
70
  cache: bool,
75
- state: bool,
76
71
  data: bool,
77
72
  config: bool,
78
73
  models: bool,
@@ -81,7 +76,7 @@ def clean_cli(
81
76
  all: bool,
82
77
  yes: bool,
83
78
  ) -> None:
84
- targets: List[Path] = []
79
+ targets: list[Path] = []
85
80
 
86
81
  if all:
87
82
  if not yes:
@@ -92,7 +87,6 @@ def clean_cli(
92
87
  targets = [
93
88
  LOGGER_DIR,
94
89
  JS_SCRIPT_DIR,
95
- STATE_DIR,
96
90
  DATA_DIR,
97
91
  CONFIG_DIR,
98
92
  MODEL_CACHE_DIR,
@@ -102,8 +96,6 @@ def clean_cli(
102
96
  targets.append(LOGGER_DIR)
103
97
  if cache:
104
98
  targets.append(JS_SCRIPT_DIR)
105
- if state:
106
- targets.append(STATE_DIR)
107
99
  if data:
108
100
  targets.append(DATA_DIR)
109
101
  if config:
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  novel_downloader.cli.download
5
4
  -----------------------------
@@ -8,7 +7,7 @@ Download full novels by book IDs
8
7
  (supports config files, site switching, and localization prompts).
9
8
  """
10
9
 
11
- from typing import List
10
+ import asyncio
12
11
 
13
12
  import click
14
13
  from click import Context
@@ -41,7 +40,7 @@ from novel_downloader.utils.logger import setup_logging
41
40
  help=t("download_option_site", default="qidian"),
42
41
  ) # type: ignore
43
42
  @click.pass_context # type: ignore
44
- def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
43
+ def download_cli(ctx: Context, book_ids: list[str], site: str) -> None:
45
44
  """Download full novels by book IDs."""
46
45
  config_path = ctx.obj.get("config_path")
47
46
 
@@ -57,6 +56,8 @@ def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
57
56
  parser_cfg = adapter.get_parser_config()
58
57
  saver_cfg = adapter.get_saver_config()
59
58
 
59
+ click.echo(t("download_site_mode", mode=downloader_cfg.mode))
60
+
60
61
  # If no book_ids provided on the command line, try to load them from config
61
62
  if not book_ids:
62
63
  try:
@@ -67,7 +68,7 @@ def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
67
68
 
68
69
  # Filter out placeholder/example IDs
69
70
  invalid_ids = {"0000000000"}
70
- valid_book_ids = [bid for bid in book_ids if bid not in invalid_ids]
71
+ valid_book_ids = set(book_ids) - invalid_ids
71
72
 
72
73
  if not book_ids:
73
74
  click.echo(t("download_no_ids"))
@@ -80,21 +81,20 @@ def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
80
81
 
81
82
  # Initialize the requester, parser, saver, and downloader components
82
83
  if downloader_cfg.mode == "async":
83
- import asyncio
84
-
85
- async_requester = get_async_requester(site, requester_cfg)
86
- async_parser = get_parser(site, parser_cfg)
87
- async_saver = get_saver(site, saver_cfg)
88
84
  setup_logging()
89
- async_downloader = get_async_downloader(
90
- requester=async_requester,
91
- parser=async_parser,
92
- saver=async_saver,
93
- site=site,
94
- config=downloader_cfg,
95
- )
96
85
 
97
86
  async def async_download_all() -> None:
87
+ async_requester = get_async_requester(site, requester_cfg)
88
+ async_parser = get_parser(site, parser_cfg)
89
+ async_saver = get_saver(site, saver_cfg)
90
+ async_downloader = get_async_downloader(
91
+ requester=async_requester,
92
+ parser=async_parser,
93
+ saver=async_saver,
94
+ site=site,
95
+ config=downloader_cfg,
96
+ )
97
+
98
98
  prepare = getattr(async_downloader, "prepare", None)
99
99
  if prepare and asyncio.iscoroutinefunction(prepare):
100
100
  await prepare()
@@ -103,9 +103,7 @@ def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
103
103
  click.echo(t("download_downloading", book_id=book_id, site=site))
104
104
  await async_downloader.download_one(book_id)
105
105
 
106
- if requester_cfg.auto_close:
107
- input(t("download_prompt_parse"))
108
- await async_requester.shutdown()
106
+ await async_requester.close()
109
107
 
110
108
  asyncio.run(async_download_all())
111
109
  else:
@@ -125,8 +123,6 @@ def download_cli(ctx: Context, book_ids: List[str], site: str) -> None:
125
123
  click.echo(t("download_downloading", book_id=book_id, site=site))
126
124
  sync_downloader.download_one(book_id)
127
125
 
128
- if requester_cfg.auto_close:
129
- input(t("download_prompt_parse"))
130
- sync_requester.shutdown()
126
+ sync_requester.close()
131
127
 
132
128
  return
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  novel_downloader.cli.interactive
5
4
  --------------------------------
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  novel_downloader.cli.main
5
4
  --------------------------
@@ -7,7 +6,6 @@ novel_downloader.cli.main
7
6
  Unified CLI entry point. Parses arguments and delegates to parser or interactive.
8
7
  """
9
8
 
10
- from typing import Optional
11
9
 
12
10
  import click
13
11
  from click import Context
@@ -24,7 +22,7 @@ from novel_downloader.utils.i18n import t
24
22
  help=t("help_config"),
25
23
  ) # type: ignore
26
24
  @click.pass_context # type: ignore
27
- def cli_main(ctx: Context, config: Optional[str]) -> None:
25
+ def cli_main(ctx: Context, config: str | None) -> None:
28
26
  """Novel Downloader CLI."""
29
27
  ctx.ensure_object(dict)
30
28
  ctx.obj["config_path"] = config
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  novel_downloader.cli.settings
5
4
  -----------------------------
@@ -10,7 +9,6 @@ Commands to configure novel downloader settings.
10
9
  import shutil
11
10
  from importlib.resources import as_file
12
11
  from pathlib import Path
13
- from typing import Optional
14
12
 
15
13
  import click
16
14
  from click import Context
@@ -61,7 +59,7 @@ def init_settings(force: bool) -> None:
61
59
  except Exception as e:
62
60
  raise click.ClickException(
63
61
  t("settings_init_error", filename=resource.name, err=e)
64
- )
62
+ ) from e
65
63
 
66
64
 
67
65
  @settings_cli.command(name="set-lang", help=t("settings_set_lang_help")) # type: ignore
@@ -81,7 +79,7 @@ def set_config(path: str) -> None:
81
79
  save_config_file(path)
82
80
  click.echo(t("settings_set_config", path=path))
83
81
  except Exception as e:
84
- raise click.ClickException(t("settings_set_config_fail", err=e))
82
+ raise click.ClickException(t("settings_set_config_fail", err=e)) from e
85
83
 
86
84
 
87
85
  @settings_cli.command(name="update-rules", help=t("settings_update_rules_help")) # type: ignore
@@ -92,7 +90,7 @@ def update_rules(path: str) -> None:
92
90
  save_rules_as_json(path)
93
91
  click.echo(t("settings_update_rules", path=path))
94
92
  except Exception as e:
95
- raise click.ClickException(t("settings_update_rules_fail", err=e))
93
+ raise click.ClickException(t("settings_update_rules_fail", err=e)) from e
96
94
 
97
95
 
98
96
  @settings_cli.command(
@@ -120,7 +118,7 @@ def set_cookies(ctx: Context, site: str, cookies: str) -> None:
120
118
  state_mgr.set_cookies(site, cookies)
121
119
  click.echo(t("settings_set_cookies_success", site=site))
122
120
  except Exception as e:
123
- raise click.ClickException(t("settings_set_cookies_fail", err=e))
121
+ raise click.ClickException(t("settings_set_cookies_fail", err=e)) from e
124
122
 
125
123
 
126
124
  @settings_cli.command(name="add-hash", help=t("settings_add_hash_help")) # type: ignore
@@ -129,7 +127,7 @@ def set_cookies(ctx: Context, site: str, cookies: str) -> None:
129
127
  type=click.Path(exists=True, dir_okay=False),
130
128
  help=t("settings_add_hash_path_help"),
131
129
  ) # type: ignore
132
- def add_image_hashes(path: Optional[str]) -> None:
130
+ def add_image_hashes(path: str | None) -> None:
133
131
  """
134
132
  Add image hashes to internal store for matching.
135
133
  Can be run in interactive mode (no --path), or with a JSON file.
@@ -142,7 +140,9 @@ def add_image_hashes(path: Optional[str]) -> None:
142
140
  img_hash_store.save()
143
141
  click.echo(t("settings_add_hash_loaded", path=path))
144
142
  except Exception as e:
145
- raise click.ClickException(t("settings_add_hash_load_fail", err=str(e)))
143
+ raise click.ClickException(
144
+ t("settings_add_hash_load_fail", err=str(e))
145
+ ) from e
146
146
  else:
147
147
  click.echo(t("settings_add_hash_prompt_tip"))
148
148
  while True:
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  novel_downloader.config
5
4
  ------------------------
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  """
4
3
  novel_downloader.config.adapter
5
4
  -------------------------------
@@ -15,7 +14,9 @@ Supported mappings:
15
14
  - sites[site] -> book_ids list
16
15
  """
17
16
 
18
- from typing import Any, Dict, List
17
+ from typing import Any
18
+
19
+ from novel_downloader.utils.constants import SUPPORTED_SITES
19
20
 
20
21
  from .models import (
21
22
  DownloaderConfig,
@@ -23,6 +24,7 @@ from .models import (
23
24
  RequesterConfig,
24
25
  SaverConfig,
25
26
  )
27
+ from .site_rules import load_site_rules
26
28
 
27
29
 
28
30
  class ConfigAdapter:
@@ -30,7 +32,7 @@ class ConfigAdapter:
30
32
  Adapter to map a raw config dict + site name into structured dataclass configs.
31
33
  """
32
34
 
33
- def __init__(self, config: Dict[str, Any], site: str):
35
+ def __init__(self, config: dict[str, Any], site: str):
34
36
  """
35
37
  :param config: 完整加载的配置 dict
36
38
  :param site: 当前站点名称 (e.g. "qidian")
@@ -38,11 +40,35 @@ class ConfigAdapter:
38
40
  self._config = config
39
41
  self._site = site
40
42
 
41
- def set_site(self, site: str) -> None:
43
+ site_rules = load_site_rules() # -> Dict[str, SiteRules]
44
+ self._supported_sites = set(site_rules.keys()) | SUPPORTED_SITES
45
+
46
+ @property
47
+ def site(self) -> str:
48
+ return self._site
49
+
50
+ @site.setter
51
+ def site(self, value: str) -> None:
52
+ self._site = value
53
+
54
+ def _get_site_cfg(self, site: str | None = None) -> dict[str, Any]:
42
55
  """
43
- 切换当前适配的站点
56
+ 获取指定站点的配置 (默认为当前适配站点)
57
+
58
+ 1. 如果有 site-specific 配置, 优先返回它
59
+ 2. 否则, 如果该站点在支持站点中, 尝试返回 'common' 配置
60
+ 3. 否则返回空 dict
44
61
  """
45
- self._site = site
62
+ site = site or self._site
63
+ sites_cfg = self._config.get("sites", {}) or {}
64
+
65
+ if site in sites_cfg:
66
+ return sites_cfg[site] or {}
67
+
68
+ if site in self._supported_sites:
69
+ return sites_cfg.get("common", {}) or {}
70
+
71
+ return {}
46
72
 
47
73
  def get_requester_config(self) -> RequesterConfig:
48
74
  """
@@ -50,12 +76,13 @@ class ConfigAdapter:
50
76
  返回 RequesterConfig 实例
51
77
  """
52
78
  req = self._config.get("requests", {})
53
- site_cfg = self._config.get("sites", {}).get(self._site, {})
79
+ site_cfg = self._get_site_cfg()
54
80
  return RequesterConfig(
55
- wait_time=req.get("wait_time", 5),
56
81
  retry_times=req.get("retry_times", 3),
57
- retry_interval=req.get("retry_interval", 5),
58
- timeout=req.get("timeout", 30),
82
+ backoff_factor=req.get("backoff_factor", 2.0),
83
+ timeout=req.get("timeout", 30.0),
84
+ max_connections=req.get("max_connections", 10),
85
+ max_rps=req.get("max_rps", None),
59
86
  headless=req.get("headless", True),
60
87
  user_data_folder=req.get("user_data_folder", "./user_data"),
61
88
  profile_name=req.get("profile_name", "Profile_1"),
@@ -63,7 +90,6 @@ class ConfigAdapter:
63
90
  disable_images=req.get("disable_images", True),
64
91
  mute_audio=req.get("mute_audio", True),
65
92
  mode=site_cfg.get("mode", "session"),
66
- max_rps=site_cfg.get("max_rps", None),
67
93
  )
68
94
 
69
95
  def get_downloader_config(self) -> DownloaderConfig:
@@ -73,11 +99,11 @@ class ConfigAdapter:
73
99
  """
74
100
  gen = self._config.get("general", {})
75
101
  debug = gen.get("debug", {})
76
- site_cfg = self._config.get("sites", {}).get(self._site, {})
102
+ site_cfg = self._get_site_cfg()
77
103
  return DownloaderConfig(
78
- request_interval=gen.get("request_interval", 5),
104
+ request_interval=gen.get("request_interval", 5.0),
79
105
  raw_data_dir=gen.get("raw_data_dir", "./raw_data"),
80
- cache_dir=gen.get("cache_dir", "./cache"),
106
+ cache_dir=gen.get("cache_dir", "./novel_cache"),
81
107
  download_workers=gen.get("download_workers", 4),
82
108
  parser_workers=gen.get("parser_workers", 4),
83
109
  use_process_pool=gen.get("use_process_pool", True),
@@ -85,6 +111,8 @@ class ConfigAdapter:
85
111
  login_required=site_cfg.get("login_required", False),
86
112
  save_html=debug.get("save_html", False),
87
113
  mode=site_cfg.get("mode", "session"),
114
+ storage_backend=gen.get("storage_backend", "json"),
115
+ storage_batch_size=gen.get("storage_batch_size", 1),
88
116
  )
89
117
 
90
118
  def get_parser_config(self) -> ParserConfig:
@@ -94,9 +122,9 @@ class ConfigAdapter:
94
122
  """
95
123
  gen = self._config.get("general", {})
96
124
  font_ocr = gen.get("font_ocr", {})
97
- site_cfg = self._config.get("sites", {}).get(self._site, {})
125
+ site_cfg = self._get_site_cfg()
98
126
  return ParserConfig(
99
- cache_dir=gen.get("cache_dir", "./cache"),
127
+ cache_dir=gen.get("cache_dir", "./novel_cache"),
100
128
  decode_font=font_ocr.get("decode_font", False),
101
129
  use_freq=font_ocr.get("use_freq", False),
102
130
  use_ocr=font_ocr.get("use_ocr", True),
@@ -124,6 +152,7 @@ class ConfigAdapter:
124
152
  return SaverConfig(
125
153
  raw_data_dir=gen.get("raw_data_dir", "./raw_data"),
126
154
  output_dir=gen.get("output_dir", "./downloads"),
155
+ storage_backend=gen.get("storage_backend", "json"),
127
156
  clean_text=out.get("clean_text", True),
128
157
  make_txt=fmt.get("make_txt", True),
129
158
  make_epub=fmt.get("make_epub", False),
@@ -133,13 +162,14 @@ class ConfigAdapter:
133
162
  filename_template=naming.get("filename_template", "{title}_{author}"),
134
163
  include_cover=epub_opts.get("include_cover", True),
135
164
  include_toc=epub_opts.get("include_toc", False),
165
+ include_picture=epub_opts.get("include_picture", False),
136
166
  )
137
167
 
138
- def get_book_ids(self) -> List[str]:
168
+ def get_book_ids(self) -> list[str]:
139
169
  """
140
170
  从 config["sites"][site]["book_ids"] 中提取目标书籍列表
141
171
  """
142
- site_cfg = self._config.get("sites", {}).get(self._site, {})
172
+ site_cfg = self._get_site_cfg()
143
173
  raw_ids = site_cfg.get("book_ids", [])
144
174
 
145
175
  if isinstance(raw_ids, str):