File: README.md
本项目封装了一套可用于爬取JM的Python API.
你可以通过简单的几行Python代码,实现下载JM上的本子到本地,并且是处理好的图片。
🧭 快速指路
友情提示:珍爱JM,为了减轻JM的服务器压力,请不要一次性爬取太多本子,西门🙏🙏🙏.
项目介绍
本项目的核心功能是下载本子。
基于此,设计了一套方便使用、便于扩展,能满足一些特殊下载需求的框架。
目前核心功能实现较为稳定,项目也处于维护阶段。
除了下载功能以外,也实现了其他的一些禁漫接口,按需实现。目前已有功能:
- 登录
- 搜索本子(支持所有搜索项)
- 获取本子评论(包括回评、剧透标识)
- 分类/排行榜
- 获取本子/章节详情
- 图片下载解码
- 个人收藏夹
- 接口加解密(APP的接口)
安装教程
⚠如果你没有安装过 Python,需要先前往 Python 官网下载 再执行以下步骤。
推荐使用 Python 3.12及以上版本
通过pip官方源安装(推荐,并且更新也是这个命令)
shellpip install jmcomic -U通过源代码安装
shellpip install git+https://github.com/hect0x7/JMComic-Crawler-Python
快速上手
1. 下载本子方法
只需要使用如下代码,就可以下载本子JM123的所有章节的图片:
import jmcomic # 导入此模块,需要先安装.
jmcomic.download_album('123') # 传入要下载的album的id,即可下载整个album到本地.
# 也可以使用 Async API (详见教程: https://jmcomic.readthedocs.io/zh-cn/latest/tutorial/14_async_usage/)
import asyncio
asyncio.run(jmcomic.download_album_async('123'))上面的 download_album方法还有一个参数option,可用于控制下载配置,配置包括禁漫域名、网络代理、图片格式转换、插件等等。
你可能需要这些配置项。推荐使用配置文件创建option,用option下载本子,见下章:
2. 使用option配置来下载本子
首先,创建一个配置文件,假设文件名为
option.yml该文件有特定的写法,你需要参考这个文档 → 配置文件指南
下面做一个演示,假设你需要把下载的图片转为png格式,你应该把以下内容写进
option.yml
download:
image:
suffix: .png # 该配置用于把下载的图片转为png格式- 第二步,运行下面的python代码
import jmcomic
# 创建配置对象
option = jmcomic.create_option_by_file('你的配置文件路径,例如 D:/option.yml')
# 使用option对象来下载本子
jmcomic.download_album(123, option)
# 等价写法: option.download_album(123)3. 使用命令行
[!TIP]
如果只想下载本子,使用命令行会比上述方式更加简单直接例如,在windows上,直接按下 win+R 键,输入
jmcomic xxx就可以下载本子。
示例:下载本子123的命令
jmcomic 123同时下载本子123, 章节456的命令
jmcomic 123 p456命令行模式也支持自定义option,你可以使用环境变量或者命令行参数:
a. 通过命令行 --option 参数指定option文件路径
jmcomic 123 --option="D:/a.yml"b. 命令行不改,而是配置环境变量 JM_OPTION_PATH 为option文件路径(推荐)
[!TIP]
请自行google配置环境变量的方式,或使用powershell命令:setx JM_OPTION_PATH "D:/a.yml"重启后生效
另外,jmcomic 还提供了一个美观的进度条能力,默认是开启的,但是需要你手动安装一个额外依赖 pip install rich。效果如下:
详细开启方法参考文档:启用美观的下载进度条
4. 查看本子详情(jmv 命令)
[!NOTE]
jmv命令用于快速查看本子详情,不做下载。适用场景:在某些网站上看到一串神秘车号,想快速看看具体是啥本子。此时只需copy原文本,按下 win+R,输入
jmv [粘贴内容]即可支持从任意文本中提取数字作为车号,方便直接粘贴各种格式的车号。
示例:
# 直接输入车号
jmv 350234
# 从混合文本中提取数字(提取出 350234)
jmv 350谁还没看过234
# 指定option文件(也支持环境变量,用法同上)
jmv 350234 --option="D:/a.yml"
# -y 参数:执行完毕后直接退出,无需按回车确认
jmv 350234 -y输出效果:
🔍 正在查询 禁漫车号 - [350234] 的详情...
──────────────────────────────────────────────────
📖 标题: xxx
🆔 ID: JM350234
🔗 链接: https://18comic.vip/album/350234/
✍️ 作者: Author1, Author2
──────────────────────────────────────────────────
📅 发布日期: 2022-06-15
📅 更新日期: 2023-01-01
📄 总页数: 50
👀 观看: 2M
❤️ 点赞: 77K
💬 评论: 9801
──────────────────────────────────────────────────
🏷️ 标签: 标签1, 标签2, ...
🎭 人物: 角色A, 角色B, ...
📚 作品: 作品1, 作品2, ...
──────────────────────────────────────────────────
📑 章节 (2):
第1話 上 (id: 350234)
第2話 下 (id: 350235)
──────────────────────────────────────────────────
[运行结束] 请按回车键关闭窗口... (下次运行可附加 -y 参数跳过确认)进阶使用
请查阅文档首页 → jmcomic.readthedocs.io
或者查看github仓库的文档 → github-repo-docs
(提示:jmcomic提供了很多下载配置项,大部分的下载需求你都可以尝试寻找相关配置项或插件来实现。)
项目特点
绕过Cloudflare的反爬虫
实现禁漫APP接口最新的加解密算法 (1.6.3)
支持Async和Sync两套 API
用法多样:
- GitHub
Actions:网页上直接输入本子id就能下载(教程:使用GitHub Actions下载禁漫本子) - 命令行:无需写Python代码,简单易用(教程:使用命令行下载禁漫本子)
- Python代码:最本质、最强大的使用方式,需要你有一定的python编程基础
- GitHub
支持网页端和移动端两种客户端实现,可通过配置切换(移动端不限ip兼容性好,网页端限制ip地区但效率高)
支持自动重试和域名切换机制
支持按下载任务维度结构化收集日志
可配置性强
- 不配置也能使用,十分方便
- 配置可以从配置文件生成,支持多种文件格式
- 配置点有:
请求域名客户端实现是否使用磁盘缓存同时下载的章节/图片数量图片格式转换下载路径规则请求元信息(headers,cookies,proxies)中文繁/简转换
等
可扩展性强
- 支持自定义本子/章节/图片下载前后事件的回调函数
- 支持自定义类:
Downloader(负责调度)Option(负责配置)Client(负责请求)实体类等 - 支持自定义日志、异常监听器
- 支持Plugin插件,可以方便地扩展功能,以及使用别人的插件,目前核心内置插件有:
登录插件、只下载新章插件、导出收藏夹为csv文件插件合并所有图片为pdf文件插件、合并所有图片为长图png插件压缩文件插件、自动获取浏览器cookies插件、订阅更新插件等
使用小说明
推荐使用 Python 3.12+,目前最低兼容版本为3.9。
注意:Python 3.9 及更早版本皆已于 2025 年彻底结束官方生命周期 (EOL),使用3.9及以下随时有可能遇到第三方库不兼容的问题。
个人项目,文档和示例会有不及时之处,可以Issue提问。
项目文件夹介绍
.github:GitHub Actions配置文件
assets:存放一些非代码的资源文件
- docs:项目文档
- option:存放配置文件
src:存放源代码
- jmcomic:
jmcomic模块
- jmcomic:
tests:测试目录,存放测试代码,使用unittest
usage:用法目录,存放示例/使用代码
感谢以下项目
图片分割算法代码+禁漫移动端API
used by 项目
Star History
File: assets/docs/sources/api/client.md
client
::: jmcomic.jm_client_impl
options:
members:
- JmHtmlClient
- JmApiClient
::: jmcomic.jm_client_interface
options:
members:
- AsyncJmcomicClient
::: jmcomic.jm_async_client
options:
members:
- AsyncJmApiClient
File: assets/docs/sources/api/config.md
config
::: jmcomic.jm_config
options:
members:
- JmMagicConstants
- JmModuleConfig
- default_jm_logging
File: assets/docs/sources/api/download.md
download
::: jmcomic.api
options:
members:
- download_album
- download_photo
- create_option
- create_option_by_env
- create_option_by_file
- create_option_by_str
- download_album_async
- download_photo_async
- download_batch_async
::: jmcomic.jm_async_downloader
options:
members:
- JmAsyncDownloader
File: assets/docs/sources/api/entity.md
entity
::: jmcomic.jm_entity
options:
inherited_members: true
members:
- JmAlbumDetail
- JmPhotoDetail
- JmImageDetail
- JmPageContent
- JmAlbumComment
- JmAlbumCommentPage
- JmSearchPage
File: assets/docs/sources/api/option.md
option
::: jmcomic.jm_option
options:
members:
- DirRule
- JmOption
File: assets/docs/sources/api/plugin.md
plugin
::: jmcomic.jm_plugin
options:
filters:
- (Plugin$|JmOptionPlugin|^Plugin)
File: assets/docs/sources/api/toolkit.md
toolkit
::: jmcomic.jm_toolkit
options:
inherited_members: true
members:
- JmcomicText
- PatternTool
- JmPageTool
- JmImageTool
- JmCryptoTool
File: assets/docs/sources/tutorial/0_common_usage.md
常用类和方法
下载本子/章节
from jmcomic import *
# 下载id为438696的本子 (https://18comic.vip/album/438696)
download_album(438696)
# 下载章节 (https://18comic.vip/photo/438696)
download_photo(438696)
# 同时下载多个本子
download_album([123, 456, 789])
# 查看本子/章节下载位置和耗时
result: DownloadResult = download_album(123)
album: JmAlbumDetail = result.detail # detail是实体类,download_album 返回 album,download_photo 返回 photo
print(f'本子-JM{album.id},下载保存文件夹: {album.save_path}, 下载耗时: {result.duration:.3f}秒')
# DownloadResult 里还包含大量字段,更多用法请查阅下方章节【下载返回值】使用option定制化下载本子
如果你在下载本子时有一些定制化需求,
例如指定禁漫域名,使用代理,登录禁漫,图片格式转换等等,
那么,你可以试试看jmcomic提供的option机制
from jmcomic import *
# 1. 在调用下载api前,通过创建和使用option对象,可以定制化下载行为。
# 推荐使用配置文件的方式来创建option对象,
# 你可以配置很多东西,比如代理、cookies、下载规则等等。
# 配置文件的语法参考: https://jmcomic.readthedocs.io/zh-cn/latest/option_file_syntax/
option = create_option_by_file('op.yml') # 通过配置文件来创建option对象
# 2. 调用下载api,把option作为参数传递
download_album(123, option)
# 也可以使用下面这种面向对象的方式,是一样的
option.download_album(123)获取本子/章节/图片的实体类,下载图片/封面图
from jmcomic import *
# 客户端
client = JmOption.default().new_jm_client()
# 本子实体类
album: JmAlbumDetail = client.get_album_detail('427413')
# 下载本子封面图,保存为 cover.png (图片后缀可指定为jpg、webp等)
client.download_album_cover('427413', './cover.png')
def fetch(photo: JmPhotoDetail):
# 章节实体类
photo = client.get_photo_detail(photo.photo_id, False)
print(f'章节id: {photo.photo_id}')
# 图片实体类
image: JmImageDetail
for image in photo:
print(f'图片url: {image.img_url}')
# 下载单个图片
client.download_by_image_detail(image, './a.jpg')
# 如果是已知未混淆的图片,也可以直接使用url来下载
random_image_domain = JmModuleConfig.DOMAIN_IMAGE_LIST[0]
client.download_image(f'https://{random_image_domain}/media/albums/416130.jpg', './a.jpg')
# 多线程发起请求
multi_thread_launcher(
iter_objs=album,
apply_each_obj_func=fetch
)jmcomic异常处理示例
from jmcomic import *
# 客户端
client = JmOption.default().new_jm_client()
# 捕获获取本子/章节详情时可能出现的异常
try:
# 请求本子实体类
album: JmAlbumDetail = client.get_album_detail('427413')
except MissingAlbumPhotoException as e:
print(f'id={e.error_jmid}的本子不存在')
except JsonResolveFailException as e:
print(f'解析json失败')
# 响应对象
resp = e.resp
print(f'resp.text: {resp.text}, resp.status_code: {resp.status_code}')
except RequestRetryAllFailException as e:
print(f'请求失败,重试次数耗尽')
except JmcomicException as e:
# 捕获所有异常,用作兜底
print(f'jmcomic遇到异常: {e}')
# 多线程下载时,可能出现非当前线程下载失败,抛出异常,
# 而JmDownloader有对应字段记录了这些线程发生的异常
# 使用check_exception=True参数可以使downloader主动检查是否存在下载异常
# 如果有,则当前线程会主动上抛一个PartialDownloadFailedException异常
# 该参数主要用于主动检查部分下载失败的情况,(仅对单个本子/章节 ID 生效,传入多个 ID 时不生效。多个 ID 的场景见下)
# 因为非当前线程抛出的异常(比如下载章节的线程和下载图片的线程),这些线程如果抛出异常,
# 当前线程是感知不到的,try-catch下载方法download_album不能捕获到其他线程发生的异常。
try:
album, downloader = download_album(123, check_exception=True)
except PartialDownloadFailedException as e:
downloader: JmDownloader = e.downloader
print(f'下载出现部分失败, 下载失败的章节: {downloader.download_failed_photo}, 下载失败的图片: {downloader.download_failed_image}')
# 多 ID 下载不会因为某一项失败而中断,请检查 BatchResult.failed。
# 如果需要在批量失败时抛异常、重试或拿到更详细信息,建议自行封装一个批量下载方法。
result = download_album([123, 456, 789])
for album_id, error in result.failed.items():
print(f'本子 {album_id} 下载失败: {error}')搜索本子
from jmcomic import *
client = JmOption.default().new_jm_client()
# 分页查询,search_site就是禁漫网页上的【站内搜索】
page: JmSearchPage = client.search_site(search_query='+MANA +无修正', page=1)
print(f'结果总数: {page.total}, 分页大小: {page.page_size},页数: {page.page_count}')
# page默认的迭代方式是page.iter_id_title(),每次迭代返回 albun_id, title
for album_id, title in page:
print(f'[{album_id}]: {title}')
# 直接搜索禁漫车号
page = client.search_site(search_query='427413')
album: JmAlbumDetail = page.single_album
print(album.tags)搜索并下载本子
from jmcomic import *
option = JmOption.default()
client = option.new_jm_client()
tag = '無修正'
# 搜索标签,可以使用search_tag。
# 搜索第一页。
page: JmSearchPage = client.search_tag(tag, page=1)
aid_list = []
for aid, atitle, tag_list in page.iter_id_title_tag(): # 使用page的iter_id_title_tag迭代器
if tag in tag_list:
print(f'[标签/{tag}] 发现目标: [{aid}]: [{atitle}]')
aid_list.append(aid)
download_album(aid_list, option)获取评论
目前支持获取两种评论:
| 评论类型 | 说明 | 获取一页的方法 | 自动翻页的方法 |
|---|---|---|---|
| 本子评论 | 获取指定本子下的评论和回评 | album_pagination |
album_pagination_gen |
| 全站评论 | 获取全站最新发布的评论,可通过 comment.album_id 知道评论来自哪个本子 |
forum_pagination |
forum_pagination_gen |
评论页可以直接遍历,评论对象也可以直接打印,格式为 [评论ID] 用户(剧透): 内容。
获取本子评论
album_pagination 获取指定本子的一页评论。
from jmcomic import JmOption, JmAlbumComment
client = JmOption.default().new_jm_client(impl='api')
page = client.album_pagination('123456')
print(f'第 {page.page_number}/{page.page_count} 页,当前页一共 {len(page)} 条主评论,整本一共 {page.total} 条主评论')
for comment in page:
comment: JmAlbumComment # 评论是实体类
print(
f'评论ID: {comment.comment_id} | 用户ID: {comment.user_id} | 用户: {comment.nickname or comment.username} | '
f'是否剧透: {comment.is_spoiler} | 点赞数: {comment.likes} | 发布时间: {comment.created_at}\n内容: {comment.content}'
)
for reply in comment.replies:
reply: JmAlbumComment # 回评也是相同的实体类
print(' └─', reply)
# 需要连续获取分页时,使用生成器:
for page in client.album_pagination_gen('123456'):
print(f'\n第 {page.page_number} 页')
for comment in page:
print(comment)获取全站评论
forum_pagination 获取一页全站评论。
[!NOTE]
html 端不提供total(全部分页的主评论总数)和page_count(总页数),这两个字段都为None,api端则有值。
page = client.forum_pagination(page=1)
print(f'第 {page.page_number}/{page.page_count} 页,当前页一共 {len(page)} 条主评论,全站一共 {page.total} 条主评论')
for comment in page:
print(f'本子 {comment.album_id} | {comment}')
# 全站评论同样支持生成器
for page in client.forum_pagination_gen(page=1):
print(f'\n第 {page.page_number} 页')
for comment in page:
print(f'本子 {comment.album_id} | {comment}')获取收藏夹
可参考discussions: https://github.com/hect0x7/JMComic-Crawler-Python/discussions/235
一键导出全部收藏夹
下面的代码不会下载图片,只会把帐号中的全部收藏夹导出为 CSV,并生成 favorites.zip:
from jmcomic import JmOption, FavoriteFolderExportPlugin
USERNAME = '你的禁漫帐号'
PASSWORD = '你的禁漫密码'
option = JmOption.default()
option.build_jm_client().login(USERNAME, PASSWORD)
FavoriteFolderExportPlugin(option).invoke(
save_dir='./',
zip_enable=True,
zip_filepath='./favorites.zip',
)获取并遍历收藏夹
from jmcomic import *
option = JmOption.default()
client = option.new_jm_client()
client.login('用户名', '密码') # 也可以使用login插件/配置cookies
# 遍历全部收藏的所有页
for page in client.favorite_folder_gen(): # 如果你只想获取特定收藏夹,需要添加folder_id参数
# 遍历每页结果
for aid, atitle in page.iter_id_title():
# aid: 本子的album_id
# atitle: 本子的名称
print(aid)
# 打印当前帐号的所有收藏夹信息
for folder_id, folder_name in page.iter_folder_id_name():
print(f'收藏夹id: {folder_id}, 收藏夹名称: {folder_name}')
# 获取特定收藏夹的单页,使用favorite_folder方法
page = client.favorite_folder(page=1,
order_by=JmMagicConstants.ORDER_BY_LATEST,
folder_id='0' # 收藏夹id
)分类 / 排行榜
禁漫的分类是一个和搜索有些类似的功能。
搜索是按某一条件进行过滤。
分类没有过滤,就是把某一类别(category)下的本子全都调出来。
禁漫的排行榜就是分类的一种形式
下面演示调用分类api
from jmcomic import *
# 创建客户端
op = JmOption.default()
cl = op.new_jm_client()
# 调用分类接口
# 根据下面的参数,这个调用的意义就是:
# 在全部分类下,选择所有时间范围,按观看数排序后,获取第一页的本子
page: JmCategoryPage = cl.categories_filter(
page=1,
time=JmMagicConstants.TIME_ALL, # 时间选择全部,具体可以写什么请见JmMagicConstants
category=JmMagicConstants.CATEGORY_ALL, # 分类选择全部,具体可以写什么请见JmMagicConstants
order_by=JmMagicConstants.ORDER_BY_VIEW, # 按照观看数排序,具体可以写什么请见JmMagicConstants
)
# 月排行,底层实现也是调的categories_filter
page: JmCategoryPage = cl.month_ranking(1)
# 周排行
page: JmCategoryPage = cl.week_ranking(1)
# 循环获取分页,使用 cl.categories_filter_gen
# 基础用法:简单的 for 循环
for page in cl.categories_filter_gen(page=1, # 起始页码
# 下面是分类参数
time=JmMagicConstants.TIME_WEEK,
category=JmMagicConstants.CATEGORY_ALL,
order_by=JmMagicConstants.ORDER_BY_VIEW,
):
for aid, atitle in page:
print(aid, atitle)
# 高级用法:使用 generator 的 send() 方法在遍历中途动态修改查询条件
# 注意:必须用 while 循环手动接收 send() 的返回值,避免在 for 循环内调用 send() 跳过分页
generator = cl.categories_filter_gen(page=1, time=JmMagicConstants.TIME_WEEK)
try:
page = next(generator) # 预先启动生成器
while True:
# 打印第一页
for aid, atitle in page:
print(aid, atitle)
# 假设我们只想看前一页,下一页想换一个排序方式
# 调用 send 传入包含新参数的 dict 即可覆盖原来的查询条件
page = generator.send({"order_by": JmMagicConstants.ORDER_BY_LATEST})
except StopIteration:
pass高级搜索(分类/副分类)
禁漫网页端的搜索除了常规条件,还支持【分类】和【副分类】的搜索。
在任一搜索页面,你会看到本子图的右上方有两个标签。左边的是【分类】,右边的是【副分类】。
下面演示代码如何编写。
- 注意!!禁漫移动端没有提供如下功能,以下代码仅对网页端生效。
# 在编写代码前,建议先熟悉禁漫网页的搜本功能,下面的代码都是对照网页编写的。
# 网页搜索示例:https://18comic.vip/search/photos/doujin/sub/CG?main_tag=0&search_query=mana&page=1&o=mr&t=a
from jmcomic import *
op = create_option_by_file('op.yml')
# 创建网页端client
html_cl = op.new_jm_client(impl='html')
# 使用站内搜索,指定【分类】和【副分类】
# 分类 = JmMagicConstants.CATEGORY_DOUJIN = 同人本
# 副分类 = JmMagicConstants.SUB_DOUJIN_CG = CG本
# 实际URL:https://18comic.vip/search/photos/doujin/sub/CG?main_tag=0&search_query=mana&page=1&o=mr&t=a
page = html_cl.search_site(search_query='mana',
category=JmMagicConstants.CATEGORY_DOUJIN,
sub_category=JmMagicConstants.SUB_DOUJIN_CG,
page=1,
)
# 打印page内容
for aid, atitle in page.iter_id_title():
print(aid, atitle)
# 循环获取分页
for page in html_cl.search_gen(search_query='mana',
category=JmMagicConstants.CATEGORY_DOUJIN,
sub_category=JmMagicConstants.SUB_DOUJIN_CG,
page=1, # 起始页码
):
# 打印page内容
for aid, atitle in page.iter_id_title():
print(aid, atitle)
# 高级用法:使用 generator 的 send() 方法进行手动翻页或修改查询条件
generator = html_cl.search_gen('mana')
try:
page = next(generator)
while True:
for aid, atitle in page.iter_id_title():
print(aid, atitle)
# 可直接动态传参改变搜索条件,例如下一页换成搜索 'nana'
page = generator.send({"search_query": 'nana'})
except StopIteration:
pass手动创建Client
# 默认的使用方式是先创建option,option封装了所有配置,然后由option.new_jm_client() 创建客户端client,使用client可以访问禁漫接口
# 下面演示直接构造client的方式
from jmcomic import *
"""
创建JM客户端
:param postman: 负责实现HTTP请求的对象,持有cookies、headers、proxies等信息
:param domain_list: 禁漫域名
:param retry_times: 重试次数
"""
# 网页端
cl = JmHtmlClient(
postman=JmModuleConfig.new_postman(),
domain_list=['18comic.vip'],
retry_times=1
)
# API端(APP)
cl = JmApiClient(
postman=JmModuleConfig.new_postman(),
domain_list=JmModuleConfig.DOMAIN_API_LIST,
retry_times=1
)下载返回值
download_album 和 download_photo 下载完成后,单个 ID 返回 DownloadResult,多个 ID 返回 BatchResult。
从 result.detail 可以取得下载的本子/章节的实体类:
from jmcomic import download_album, download_photo
# 下载本子;result.detail 是本子实体
result = download_album('123')
album = result.detail
print(f'本子实体: {album}')
# 下载章节;result.detail 是章节实体
result = download_photo('456')
photo = result.detail
print(f'章节实体: {photo}')获取保存路径和耗时
下载完成后,你通常会关心两件事:文件保存在哪里,以及哪一步比较慢。下表介绍获取方法。
| 对象 | save_path |
duration(单位-秒) |
|---|---|---|
下载结果 result |
通过 result.detail.save_path 查看 |
从调用下载方法到返回,你总共等了多久 |
本子 album |
本子根目录 | 下载这个本子花了多久。包含获取详情、下载章节和插件 |
章节 photo |
章节图片目录 | 下载这个章节花了多久。包含获取详情、下载图片和插件 |
图片 image |
图片文件路径 | 下载这张图片花了多久。包含检查缓存、下载、保存和插件 |
[!NOTE]
一次本子下载可能同时处理多个章节,一个章节也可能同时处理多张图片。因此,把所有章节或图片的耗时相加,不会得到本子的耗时,这是正常现象。
完整示例:查看路径、耗时和缓存状态
from jmcomic import download_album
result = download_album('123')
album = result.detail
# result.duration 是调用下载方法后总共等待的时间
print(f'本子目录: {album.save_path},总共等待: {result.duration:.2f} 秒')
print(f'下载本子用了: {album.duration:.2f} 秒')
for photo in album:
# 查看每个章节的保存目录和处理耗时
print(f'章节 {photo.id} 目录: {photo.save_path},耗时: {photo.duration:.2f} 秒')
for image in photo:
# exists 和 cache 都为 True,表示满足缓存复用条件
not_download = image.exists and image.cache
print(
f'图片 {image.filename} 路径: {image.save_path},'
f'耗时: {image.duration:.2f} 秒,是否因存在而跳过下载: {not_download}'
)not_download 为 True,表示目标图片原本存在并且允许使用缓存。
使用 `manifest` 获取结果文件
相比于遍历实体类, manifest 提供了更直接的写法,适合场景:直接取得全部图片路径,以及取得额外产物(由插件/Feature产出的 PDF、ZIP、长图)
| 想要什么 | 推荐写法 |
|---|---|
| 查看某张图片的路径、耗时和缓存状态 | 遍历实体,读取 image.save_path 等字段 |
| 直接获取所有图片路径(包含命中缓存的图片) | 用manifest,result.manifest.image_filepath_list |
| 额外产物(PDF、ZIP 或长图) | 用manifest,result.manifest.get_export_filepath_list('文件后缀') |
完整示例:获取图片和导出文件
from jmcomic import Feature, download_album
# 下载本子,并使用内置 Feature 导出 PDF、ZIP 和长图
result = download_album(
'123',
extra=Feature.export_pdf + Feature.export_zip + Feature.export_long_img,
)
# 本次成功下载或直接复用的图片路径
print('图片文件:', result.manifest.image_filepath_list)
# 插件导出的文件按后缀查询,后缀前面的点可以省略
pdf_filepath_list = result.manifest.get_export_filepath_list('pdf')
zip_filepath_list = result.manifest.get_export_filepath_list('.zip')
png_filepath_list = result.manifest.get_export_filepath_list('png')
print('PDF 文件:', pdf_filepath_list)
print('ZIP 文件:', zip_filepath_list)
print('长图导出文件:', png_filepath_list)批量下载的返回值
传入多个 ID 时,返回值是 BatchResult。每一项成功下载对应一个 DownloadResult,失败任务则记录在 failed 中:
完整示例:处理批量下载结果
from jmcomic import download_album
# 同时下载多个本子
batch_result = download_album(['123', '456', '789'])
# BatchResult 继承 set,成功结果没有输入顺序保证
for result in batch_result:
album = result.detail
# 通过实体 ID 识别当前结果,不要用遍历位置对应输入列表
print(f'JM{album.id} 下载到: {album.save_path}')
# failed 的键是下载失败的 ID,值是记录失败原因的异常对象
for album_id, error in batch_result.failed.items():
print(f'JM{album_id} 下载失败: {error}')
# total 是实际处理的不同 ID 数量,重复 ID 不会重复下载
print('任务总数:', batch_result.total)
print('是否全部成功:', batch_result.all_succeeded)下载单个 ID 时,请求本子失败会直接抛出异常;如果只有部分章节或图片失败,会在任务结束后汇总抛出 PartialDownloadFailedException,此时不会返回 DownloadResult。批量下载则继续执行其他任务,并把失败项集中放进 batch_result.failed。
速查表
| 你的需求 | 推荐写法 |
|---|---|
| 查看本子或章节信息 | result.detail |
| 查看本子或章节目录 | result.detail.save_path |
| 查看单张图片路径和状态 | 遍历实体后读取 image.save_path 等字段 |
| 查看图片或章节失败原因 | 捕获 PartialDownloadFailedException 后,读取 e.downloader.download_failed_image / download_failed_photo;列表元素为 (实体, 异常) |
| 获取本次成功图片路径列表 | result.manifest.image_filepath_list |
| 获取已登记的导出文件路径 | result.manifest.get_export_filepath_list('后缀') |
| 查看单个 ID 下载的完整时间 | result.duration |
| 定位本子、章节或图片的内部处理慢点 | 对应实体的 duration |
| 检查批量下载失败项 | batch_result.failed |
兼容旧版本的返回值解包写法
旧代码可能会把返回值直接解包成两个变量,这种写法仍然可以继续使用:
from jmcomic import download_album
result = download_album('123')
# 旧写法:第一个变量是本子实体,第二个变量是下载器
album, downloader = result
# 新代码更推荐直接通过 result.detail 读取本子实体
assert album is result.detail