mirror of
https://github.com/hect0x7/JMComic-Crawler-Python.git
synced 2025-09-26 22:31:30 +08:00
parent
c6e8d69fc8
commit
a847b922ee
11
.github/workflows/test.yml
vendored
11
.github/workflows/test.yml
vendored
@ -4,12 +4,11 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "dev" ]
|
||||
paths-ignore:
|
||||
- 'src/**/__init__.py'
|
||||
- '.github/workflows/*'
|
||||
- 'usage/workflow_download.py'
|
||||
- 'assets/config/option_workflow_download.yml'
|
||||
- 'assets/docs/*'
|
||||
paths:
|
||||
- 'src/**/*.py'
|
||||
- 'test/**/*.py'
|
||||
- '.github/workflows/test.yml'
|
||||
- 'assets/config/option_test.yml'
|
||||
|
||||
jobs:
|
||||
test: # This code is based on https://github.com/gaogaotiantian/viztracer/blob/master/.github/workflows/python-package.yml
|
||||
|
31
README.md
31
README.md
@ -6,6 +6,8 @@
|
||||
|
||||
**友情提示:珍爱JM,为了减轻JM的服务器压力,请不要一次性爬取太多本子,西门🙏🙏🙏**.
|
||||
|
||||
|
||||
|
||||
## 安装教程
|
||||
|
||||
* 通过pip官方源安装(推荐,并且更新也是这个命令)
|
||||
@ -19,6 +21,8 @@
|
||||
pip install -e ./
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 快速上手
|
||||
|
||||
使用下面的两行代码,即可实现功能:把某个本子(album)里的所有章节(photo)下载到本地
|
||||
@ -33,15 +37,28 @@ jmcomic.download_album('422866') # 传入要下载的album的id,即可下载
|
||||
# 如果你想要配置,请参考文件 assets/config/常用配置介绍.yml
|
||||
```
|
||||
|
||||
进阶使用可以参考本repo下usage文件夹内的示例代码文件:
|
||||
|
||||
|
||||
## 进阶使用
|
||||
|
||||
进阶使用可以参考本repo下usage文件夹内的示例代码文件,下面是各个文件的作用,你可以挑感兴趣的阅读:
|
||||
|
||||
- API上手介绍: `getting_started.py`
|
||||
- 使用API实现简单功能: `usage_simple.py`
|
||||
- 演示jmcomic模块的可自定义功能点: `usage_custom.py`
|
||||
- 使用API的Filter过滤功能: `usage_feature_filter.py`
|
||||
- 演示jmcomic模块的Plugin插件体系: `usage_feature_plugin.py`
|
||||
- 演示一个综合使用实例: `usage_advanced.py`
|
||||
- 包括6个功能需求的介绍、实现方案和完整运行日志
|
||||
- 实现方案非常简洁,充分jmcomic的便利性,以及强大的插件扩展机制
|
||||
|
||||
|
||||
以及一些趣味用法:
|
||||
|
||||
- 测试你的ip可以访问哪些禁漫域名: `pick_domain.py`
|
||||
- 基于GitHub Actions下载本子: `workflow_download.py`
|
||||
- 演示jmcomic模块的自定义功能点: `usage_custom.py`
|
||||
- 演示jmcomic模块的Plugin插件体系: `usage_plugin.py`
|
||||
|
||||
|
||||
|
||||
## 项目特点
|
||||
|
||||
@ -53,7 +70,7 @@ jmcomic.download_album('422866') # 传入要下载的album的id,即可下载
|
||||
- 配置点有:`是否使用磁盘缓存` `并发下载图片数` `图片类型转换` `下载路径` `请求元信息(headers,cookies,proxies)`等
|
||||
- **可扩展性强**
|
||||
- **支持Plugin插件,可以方便地扩展功能,以及使用别人的插件**
|
||||
- 目前内置支持的插件:`登录插件`,`硬件占用监控插件`
|
||||
- 目前内置支持的插件有:`登录插件` `硬件占用监控插件` `只下载新章插件` `压缩文件插件`
|
||||
- 支持自定义本子/章节/图片下载前后的回调函数
|
||||
- 支持自定义debug日志的开关/格式
|
||||
- 支持自定义Downloader/Option/Client/实体类
|
||||
@ -62,11 +79,15 @@ jmcomic.download_album('422866') # 传入要下载的album的id,即可下载
|
||||
- **多线程下载**(可细化到一图一线程,效率极高)
|
||||
- 跟进了JM最新的图片分割算法(2023-02-08)
|
||||
|
||||
|
||||
|
||||
## 使用小说明
|
||||
|
||||
* Python >= 3.7
|
||||
* 个人项目,文档和示例会有不及时之处,可以Issue提问
|
||||
|
||||
|
||||
|
||||
## 项目文件夹介绍
|
||||
|
||||
* assets:存放一些非代码的资源文件
|
||||
@ -79,6 +100,8 @@ jmcomic.download_album('422866') # 传入要下载的album的id,即可下载
|
||||
* tests:测试目录,存放测试代码,使用unittest
|
||||
* usage:用法目录,存放示例/使用代码
|
||||
|
||||
|
||||
|
||||
## 感谢以下项目
|
||||
|
||||
### 图片分割算法代码+禁漫移动端API
|
||||
|
@ -5,9 +5,21 @@ plugin:
|
||||
- plugin: usage-log # 实时打印硬件占用率的插件
|
||||
kwargs:
|
||||
interval: 0.5 # 间隔时间
|
||||
enable_warning: true
|
||||
enable_warning: true # 占用过大时发出预警
|
||||
|
||||
- plugin: login # 登录插件
|
||||
kwargs:
|
||||
username: un
|
||||
password: pw
|
||||
|
||||
- plugin: find_update # 只下载新章插件
|
||||
kwargs:
|
||||
145504: 290266 # 下载本子145504的章节290266以后的新章
|
||||
|
||||
after_album:
|
||||
- plugin: zip # 压缩文件插件
|
||||
kwargs:
|
||||
level: photo # 按照章节,一个章节一个压缩文件
|
||||
filename_rule: Ptitle # 压缩文件的命名规则
|
||||
zip_dir: D:/GitProject/dev/pip/jmcomic/ # 压缩文件存放的文件夹
|
||||
delete_original_file: true # 压缩成功后,删除所有原文件和文件夹
|
@ -2,7 +2,7 @@
|
||||
# 被依赖方 <--- 使用方
|
||||
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
|
||||
|
||||
__version__ = '2.2.1'
|
||||
__version__ = '2.2.2'
|
||||
|
||||
from .api import *
|
||||
from .jm_plugin import *
|
||||
|
@ -4,6 +4,7 @@ from .jm_downloader import *
|
||||
def download_batch(download_api,
|
||||
jm_id_iter: Union[Iterable, Generator],
|
||||
option=None,
|
||||
downloader=None,
|
||||
):
|
||||
"""
|
||||
批量下载 album / photo
|
||||
@ -12,6 +13,7 @@ def download_batch(download_api,
|
||||
@param download_api: 下载api
|
||||
@param jm_id_iter: jmid (album_id, photo_id) 的迭代器
|
||||
@param option: 下载选项,对所有的jmid使用同一个,默认是 JmOption.default()
|
||||
@param downloader: 下载器类
|
||||
"""
|
||||
from common import multi_thread_launcher
|
||||
|
||||
@ -23,41 +25,45 @@ def download_batch(download_api,
|
||||
JmcomicText.parse_to_album_id(jmid)
|
||||
for jmid in jm_id_iter
|
||||
),
|
||||
apply_each_obj_func=lambda aid: download_api(aid, option),
|
||||
apply_each_obj_func=lambda aid: download_api(aid, option, downloader),
|
||||
)
|
||||
|
||||
|
||||
def download_album(jm_album_id, option=None):
|
||||
def download_album(jm_album_id, option=None, downloader=None):
|
||||
"""
|
||||
下载一个本子
|
||||
@param jm_album_id: 禁漫的本子的id,类型可以是str/int/iterable[str]。
|
||||
如果是iterable[str],则会调用 download_album_batch
|
||||
@param option: 下载选项,为空默认是 JmOption.default()
|
||||
@param downloader: 下载器类
|
||||
"""
|
||||
|
||||
if not isinstance(jm_album_id, (str, int)):
|
||||
return download_batch(download_album, jm_album_id, option)
|
||||
return download_batch(download_album, jm_album_id, option, downloader)
|
||||
|
||||
with new_downloader(option) as dler:
|
||||
with new_downloader(option, downloader) as dler:
|
||||
dler.download_album(jm_album_id)
|
||||
|
||||
|
||||
def download_photo(jm_photo_id, option=None):
|
||||
def download_photo(jm_photo_id, option=None, downloader=None):
|
||||
"""
|
||||
下载一个章节
|
||||
"""
|
||||
if not isinstance(jm_photo_id, (str, int)):
|
||||
return download_batch(download_photo, jm_photo_id, option)
|
||||
|
||||
with new_downloader(option) as dler:
|
||||
with new_downloader(option, downloader) as dler:
|
||||
dler.download_photo(jm_photo_id)
|
||||
|
||||
|
||||
def new_downloader(option=None):
|
||||
def new_downloader(option=None, downloader=None) -> JmDownloader:
|
||||
if option is None:
|
||||
option = JmModuleConfig.option_class().default()
|
||||
|
||||
return JmModuleConfig.downloader_class()(option)
|
||||
if downloader is None:
|
||||
downloader = JmModuleConfig.downloader_class()
|
||||
|
||||
return downloader(option)
|
||||
|
||||
|
||||
def create_option(filepath):
|
||||
|
@ -5,6 +5,7 @@ class AbstractJmClient(
|
||||
JmcomicClient,
|
||||
PostmanProxy,
|
||||
):
|
||||
client_key: None
|
||||
|
||||
def __init__(self,
|
||||
postman: Postman,
|
||||
@ -153,6 +154,7 @@ class AbstractJmClient(
|
||||
|
||||
# 基于网页实现的JmClient
|
||||
class JmHtmlClient(AbstractJmClient):
|
||||
client_key = 'html'
|
||||
|
||||
def get_album_detail(self, album_id) -> JmAlbumDetail:
|
||||
# 参数校验
|
||||
@ -366,6 +368,7 @@ class JmHtmlClient(AbstractJmClient):
|
||||
|
||||
|
||||
class JmApiClient(AbstractJmClient):
|
||||
client_key = 'api'
|
||||
API_SEARCH = '/search'
|
||||
|
||||
def search_album(self, search_query, main_tag=0, page=1) -> JmApiResp:
|
||||
@ -437,5 +440,5 @@ class AsyncSaveImageClient(JmImageClient):
|
||||
self.future_list.append(future)
|
||||
|
||||
|
||||
JmModuleConfig.CLASS_CLIENT_IMPL['html'] = JmHtmlClient
|
||||
JmModuleConfig.CLASS_CLIENT_IMPL['api'] = JmApiClient
|
||||
JmModuleConfig.register_client(JmHtmlClient)
|
||||
JmModuleConfig.register_client(JmApiClient)
|
||||
|
@ -297,6 +297,10 @@ class JmModuleConfig:
|
||||
def register_plugin(cls, plugin_class):
|
||||
cls.plugin_registry[plugin_class.plugin_key] = plugin_class
|
||||
|
||||
@classmethod
|
||||
def register_client(cls, client_class):
|
||||
cls.CLASS_CLIENT_IMPL[client_class.client_key] = client_class
|
||||
|
||||
|
||||
jm_debug = JmModuleConfig.jm_debug
|
||||
disable_jm_debug = JmModuleConfig.disable_jm_debug
|
||||
|
@ -38,7 +38,7 @@ class DownloadCallback:
|
||||
|
||||
def after_photo(self, photo: JmPhotoDetail):
|
||||
jm_debug('photo.after',
|
||||
f'章节下载完成: {photo.id} ({photo.album_id}[{photo.index}/{len(photo.from_album)}])')
|
||||
f'章节下载完成: [{photo.id}] ({photo.album_id}[{photo.index}/{len(photo.from_album)}])')
|
||||
|
||||
def before_image(self, image: JmImageDetail, img_save_path):
|
||||
if image.is_exists:
|
||||
@ -65,6 +65,9 @@ class JmDownloader(DownloadCallback):
|
||||
self.use_cache = self.option.download_cache
|
||||
self.decode_image = self.option.download_image_decode
|
||||
|
||||
# 收集所有下载的image,为plugin提供数据
|
||||
self.all_downloaded: Dict[JmAlbumDetail, Dict[JmPhotoDetail, List[Tuple[str, JmImageDetail]]]] = {}
|
||||
|
||||
def download_album(self, album_id):
|
||||
client = self.client_for_album(album_id)
|
||||
album = client.get_album_detail(album_id)
|
||||
@ -121,6 +124,9 @@ class JmDownloader(DownloadCallback):
|
||||
iter_objs = self.filter_iter_objs(iter_objs)
|
||||
count_real = len(iter_objs)
|
||||
|
||||
if count_real == 0:
|
||||
return
|
||||
|
||||
if count_batch >= count_real:
|
||||
# 一个图/章节 对应 一个线程
|
||||
multi_thread_launcher(
|
||||
@ -162,6 +168,34 @@ class JmDownloader(DownloadCallback):
|
||||
"""
|
||||
return self.option.build_jm_client()
|
||||
|
||||
# 下面是回调方法
|
||||
|
||||
def before_album(self, album: JmAlbumDetail):
|
||||
super().before_album(album)
|
||||
self.all_downloaded.setdefault(album, {})
|
||||
|
||||
def before_photo(self, photo: JmPhotoDetail):
|
||||
super().before_photo(photo)
|
||||
self.all_downloaded.setdefault(photo.from_album, {})
|
||||
self.all_downloaded[photo.from_album].setdefault(photo, [])
|
||||
|
||||
def after_album(self, album: JmAlbumDetail):
|
||||
super().after_album(album)
|
||||
self.option.call_all_plugin(
|
||||
'after_album',
|
||||
album=album,
|
||||
downloader=self,
|
||||
)
|
||||
|
||||
def after_image(self, image: JmImageDetail, img_save_path):
|
||||
super().after_image(image, img_save_path)
|
||||
photo = image.from_photo
|
||||
album = photo.from_album
|
||||
|
||||
self.all_downloaded.get(album).get(photo).append((img_save_path, image))
|
||||
|
||||
# 下面是对with语法的支持
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
|
@ -14,56 +14,37 @@ class DirRule:
|
||||
# 需要替换JmModuleConfig.CLASS_ALBUM / CLASS_PHOTO才能让自定义属性生效
|
||||
]
|
||||
|
||||
RuleFunc = Callable[[Union[JmAlbumDetail, JmPhotoDetail, None]], str]
|
||||
RuleSolver = List[Tuple[int, RuleFunc]]
|
||||
Detail = Union[JmAlbumDetail, JmPhotoDetail, None]
|
||||
RuleFunc = Callable[[Detail], str]
|
||||
RuleSolver = Tuple[int, RuleFunc]
|
||||
RuleSolverList = List[RuleSolver]
|
||||
|
||||
rule_solver_cache: Dict[Tuple[str, str], RuleSolver] = dict()
|
||||
rule_solver_cache: Dict[str, RuleSolver] = {}
|
||||
|
||||
def __init__(self, rule, base_dir=None):
|
||||
self.base_dir = self.parse_dsl(base_dir)
|
||||
def __init__(self, rule: str, base_dir=None):
|
||||
base_dir = self.parse_dsl(base_dir)
|
||||
self.base_dir = base_dir
|
||||
self.rule_dsl = rule
|
||||
self.get_rule_solver()
|
||||
self.solver_list = self.get_role_solver_list(rule, base_dir)
|
||||
|
||||
def deside_image_save_dir(self,
|
||||
album: JmAlbumDetail,
|
||||
photo: JmPhotoDetail,
|
||||
) -> str:
|
||||
def choose_param(key):
|
||||
if key == 0:
|
||||
return None
|
||||
if key == 1:
|
||||
return album
|
||||
if key == 2:
|
||||
return photo
|
||||
|
||||
solver_ls = self.get_rule_solver()
|
||||
|
||||
path_ls = []
|
||||
for i, (key, func) in enumerate(solver_ls):
|
||||
for solver in self.solver_list:
|
||||
try:
|
||||
param = choose_param(key)
|
||||
ret = func(param)
|
||||
path_ls.append(str(ret))
|
||||
ret = self.apply_rule_solver(album, photo, solver)
|
||||
except BaseException as e:
|
||||
# noinspection PyUnboundLocalVariable
|
||||
raise JmModuleConfig.exception(
|
||||
f'路径规则"{self.rule_dsl}"的第{i + 1}个解析出错: {e},'
|
||||
f'param is {param}'
|
||||
)
|
||||
jm_debug('dir_rule', f'路径规则"{self.rule_dsl}"的解析出错: {e},')
|
||||
raise e
|
||||
|
||||
path_ls.append(str(ret))
|
||||
|
||||
return fix_filepath('/'.join(path_ls), is_dir=True)
|
||||
|
||||
def get_rule_solver(self):
|
||||
key = self.rule_dsl, self.base_dir
|
||||
solver_ls = self.rule_solver_cache.get(key, None)
|
||||
|
||||
if solver_ls is None:
|
||||
solver_ls = self.solve_rule_dsl(*key)
|
||||
self.rule_solver_cache[key] = solver_ls
|
||||
|
||||
return solver_ls
|
||||
|
||||
def solve_rule_dsl(self, rule_dsl: str, base_dir: str) -> RuleSolver:
|
||||
def get_role_solver_list(self, rule_dsl: str, base_dir: str) -> RuleSolverList:
|
||||
"""
|
||||
解析下载路径dsl,得到一个路径规则解析列表
|
||||
"""
|
||||
@ -79,18 +60,60 @@ class DirRule:
|
||||
solver_ls.append((0, lambda _: base_dir))
|
||||
continue
|
||||
|
||||
# Axxx or Pyyy
|
||||
if not rule.startswith(('A', 'P')):
|
||||
rule_solver = self.get_rule_solver(rule)
|
||||
if rule_solver is None:
|
||||
raise NotImplementedError(f'不支持的dsl: "{rule}" in "{rule_dsl}"')
|
||||
|
||||
key = 1 if rule[0] == 'A' else 2
|
||||
solver_ls.append((
|
||||
key,
|
||||
lambda entity, ref=rule[1:]: fix_windir_name(str(getattr(entity, ref)))
|
||||
))
|
||||
solver_ls.append(rule_solver)
|
||||
|
||||
return solver_ls
|
||||
|
||||
@classmethod
|
||||
def get_rule_solver(cls, rule: str) -> Optional[RuleSolver]:
|
||||
# 查找缓存
|
||||
if rule in cls.rule_solver_cache:
|
||||
return cls.rule_solver_cache[rule]
|
||||
|
||||
# 检查dsl
|
||||
if not rule.startswith(('A', 'P')):
|
||||
return None
|
||||
|
||||
# Axxx or Pyyy
|
||||
key = 1 if rule[0] == 'A' else 2
|
||||
solve_func = lambda entity, ref=rule[1:]: fix_windir_name(str(getattr(entity, ref)))
|
||||
|
||||
# 保存缓存
|
||||
rule_solver = (key, solve_func)
|
||||
cls.rule_solver_cache[rule] = rule_solver
|
||||
return rule_solver
|
||||
|
||||
@classmethod
|
||||
def apply_rule_solver(cls, album, photo, rule_solver: RuleSolver) -> str:
|
||||
"""
|
||||
应用规则解析器(RuleSolver)
|
||||
|
||||
@param album: JmAlbumDetail
|
||||
@param photo: JmPhotoDetail
|
||||
@param rule_solver: Ptitle
|
||||
@return: photo.title
|
||||
"""
|
||||
|
||||
def choose_detail(key):
|
||||
if key == 0:
|
||||
return None
|
||||
if key == 1:
|
||||
return album
|
||||
if key == 2:
|
||||
return photo
|
||||
|
||||
key, func = rule_solver
|
||||
detail = choose_detail(key)
|
||||
return func(detail)
|
||||
|
||||
@classmethod
|
||||
def apply_rule_directly(cls, album, photo, rule: str) -> str:
|
||||
return cls.apply_rule_solver(album, photo, cls.get_rule_solver(rule))
|
||||
|
||||
dsl_support = {
|
||||
'${workspace}': lambda text: workspace(),
|
||||
}
|
||||
@ -165,6 +188,27 @@ class JmOption:
|
||||
mkdir_if_not_exists(save_dir)
|
||||
return save_dir
|
||||
|
||||
def decide_album_dir(self, album: JmAlbumDetail) -> str:
|
||||
"""
|
||||
该方法目前仅在 plugin-zip 中使用,不建议外部调用
|
||||
"""
|
||||
dir_layer = []
|
||||
dir_rule = self.dir_rule
|
||||
for rule in dir_rule.rule_dsl.split('_'):
|
||||
if rule == 'Bd':
|
||||
dir_layer.append(dir_rule.base_dir)
|
||||
continue
|
||||
|
||||
if rule[0] == 'A':
|
||||
name = dir_rule.apply_rule_directly(album, None, rule)
|
||||
dir_layer.append(name)
|
||||
|
||||
if rule[0] == 'P':
|
||||
break
|
||||
|
||||
from os.path import join
|
||||
return join(*dir_layer)
|
||||
|
||||
def decide_image_suffix(self, image: JmImageDetail):
|
||||
# 动图则使用原后缀
|
||||
suffix = image.img_file_suffix
|
||||
@ -298,12 +342,13 @@ class JmOption:
|
||||
from .api import download_album
|
||||
download_album(album_id, self)
|
||||
|
||||
def download_album(self, photo_id):
|
||||
from .api import download_album
|
||||
download_album(photo_id, self)
|
||||
def download_photo(self, photo_id):
|
||||
from .api import download_photo
|
||||
download_photo(photo_id, self)
|
||||
|
||||
# 下面的方法为调用插件提供支持
|
||||
def call_all_plugin(self, group: str):
|
||||
|
||||
def call_all_plugin(self, group: str, **extra):
|
||||
plugin_list: List[dict] = self.plugin.get(group, [])
|
||||
if plugin_list is None or len(plugin_list) == 0:
|
||||
return
|
||||
@ -319,21 +364,62 @@ class JmOption:
|
||||
if plugin_class is None:
|
||||
raise JmModuleConfig.exception(f'[{group}] 未注册的plugin: {key}')
|
||||
|
||||
self.invoke_plugin(plugin_class, kwargs)
|
||||
self.invoke_plugin(plugin_class, kwargs, extra)
|
||||
|
||||
def invoke_plugin(self, plugin_class, kwargs: dict):
|
||||
def invoke_plugin(self, plugin_class, kwargs: Any, extra: dict):
|
||||
# 保证 jm_plugin.py 被加载
|
||||
from .jm_plugin import JmOptionPlugin
|
||||
|
||||
plugin_class: Type[JmOptionPlugin]
|
||||
pkey = plugin_class.plugin_key
|
||||
|
||||
try:
|
||||
# 检查插件的参数类型
|
||||
kwargs = self.fix_kwargs(kwargs)
|
||||
# 把插件的配置数据kwargs和附加数据extra合并
|
||||
# extra会覆盖kwargs
|
||||
if len(extra) != 0:
|
||||
kwargs.update(extra)
|
||||
# 构建插件对象
|
||||
plugin = plugin_class.build(self)
|
||||
# 调用插件功能
|
||||
jm_debug('plugin.invoke', f'调用插件: [{pkey}]')
|
||||
plugin.invoke(**kwargs)
|
||||
except JmcomicException as e:
|
||||
msg = str(e)
|
||||
jm_debug('plugin.exception', msg)
|
||||
raise JmModuleConfig.exception(msg)
|
||||
jm_debug('plugin.exception', f'插件[{pkey}]调用失败,异常信息: {msg}')
|
||||
raise e
|
||||
except BaseException as e:
|
||||
msg = str(e)
|
||||
jm_debug('plugin.error', msg)
|
||||
jm_debug('plugin.error', f'插件[{pkey}]运行遇到未捕获异常,异常信息: {msg}')
|
||||
raise e
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def fix_kwargs(self, kwargs) -> Dict[str, Any]:
|
||||
"""
|
||||
kwargs将来要传给方法参数,这要求kwargs的key是str类型,
|
||||
该方法检查kwargs的key的类型,如果不是str,尝试转为str,不行则抛异常。
|
||||
"""
|
||||
if not isinstance(kwargs, dict):
|
||||
raise JmModuleConfig.exception(f'插件的kwargs参数必须为dict类型,而不能是类型: {type(kwargs)}')
|
||||
|
||||
kwargs: dict
|
||||
new_kwargs: Dict[str, Any] = {}
|
||||
|
||||
for k, v in kwargs.items():
|
||||
if isinstance(k, str):
|
||||
new_kwargs[k] = v
|
||||
continue
|
||||
|
||||
if isinstance(k, (int, float)):
|
||||
newk = str(k)
|
||||
jm_debug('plugin.kwargs', f'插件参数类型转换: {k} ({type(k)}) -> {newk} ({type(newk)})')
|
||||
new_kwargs[newk] = v
|
||||
continue
|
||||
|
||||
raise JmModuleConfig.exception(
|
||||
f'插件kwargs参数类型有误,'
|
||||
f'字段: {k},预期类型为str,实际类型为{type(k)}'
|
||||
)
|
||||
|
||||
return new_kwargs
|
||||
|
@ -2,7 +2,7 @@
|
||||
该文件存放的是option扩展功能类
|
||||
"""
|
||||
|
||||
from .jm_option import JmOption, JmModuleConfig, jm_debug
|
||||
from .jm_option import *
|
||||
|
||||
|
||||
class JmOptionPlugin:
|
||||
@ -137,5 +137,158 @@ class UsageLogPlugin(JmOptionPlugin):
|
||||
sleep(interval)
|
||||
|
||||
|
||||
# 参考: https://github.com/hect0x7/JMComic-Crawler-Python/issues/95
|
||||
class FindUpdatePlugin(JmOptionPlugin):
|
||||
plugin_key = 'find_update'
|
||||
|
||||
def invoke(self, **kwargs) -> None:
|
||||
self.download_album_with_find_update(kwargs or {})
|
||||
|
||||
def download_album_with_find_update(self, dic):
|
||||
from .api import download_album
|
||||
from .jm_downloader import JmDownloader
|
||||
|
||||
dic: Dict[str, int]
|
||||
|
||||
# 带入漫画id, 章节id(第x章),寻找该漫画下第x章节後的所有章节Id
|
||||
def find_update(album: JmAlbumDetail):
|
||||
if album.album_id not in dic:
|
||||
return album
|
||||
|
||||
photo_ls = []
|
||||
photo_begin = int(dic[album.album_id])
|
||||
is_new_photo = False
|
||||
|
||||
for photo in album:
|
||||
if is_new_photo:
|
||||
photo_ls.append(photo)
|
||||
|
||||
if int(photo.photo_id) == photo_begin:
|
||||
is_new_photo = True
|
||||
|
||||
return photo_ls
|
||||
|
||||
class FindUpdateDownloader(JmDownloader):
|
||||
def filter_iter_objs(self, iter_objs):
|
||||
if not isinstance(iter_objs, JmAlbumDetail):
|
||||
return iter_objs
|
||||
|
||||
return find_update(iter_objs)
|
||||
|
||||
# 调用下载api,指定option和downloader
|
||||
download_album(
|
||||
jm_album_id=dic.keys(),
|
||||
option=self.option,
|
||||
downloader=FindUpdateDownloader,
|
||||
)
|
||||
|
||||
|
||||
class ZipPlugin(JmOptionPlugin):
|
||||
plugin_key = 'zip'
|
||||
|
||||
def invoke(self,
|
||||
album: JmAlbumDetail,
|
||||
downloader,
|
||||
delete_original_file=False,
|
||||
level='photo',
|
||||
filename_rule='Ptitle',
|
||||
suffix='zip',
|
||||
zip_dir='./'
|
||||
) -> None:
|
||||
|
||||
from .jm_downloader import JmDownloader
|
||||
downloader: JmDownloader
|
||||
|
||||
mkdir_if_not_exists(zip_dir)
|
||||
photo_dict = downloader.all_downloaded[album]
|
||||
original_file_dir_list = []
|
||||
|
||||
if level == 'album':
|
||||
zip_path = self.get_zip_path(album, None, filename_rule, suffix, zip_dir)
|
||||
original_file_dir_list.append(
|
||||
self.zip_album(album, photo_dict, zip_path)
|
||||
)
|
||||
|
||||
elif level == 'photo':
|
||||
# dst_file_list = []
|
||||
|
||||
for photo, image_list in photo_dict.items():
|
||||
zip_path = self.get_zip_path(None, photo, filename_rule, suffix, zip_dir)
|
||||
original_file_dir_list.append(
|
||||
self.zip_photo(photo, image_list, zip_path)
|
||||
)
|
||||
# dst_file_list.append(zip_path)
|
||||
|
||||
else:
|
||||
raise NotImplementedError(f'level: {level}')
|
||||
|
||||
if delete_original_file is True:
|
||||
self.delete_all_files(downloader.all_downloaded, original_file_dir_list)
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def delete_all_files(self, all_downloaded: dict, dir_list: List[str]):
|
||||
import os
|
||||
for album, photo_dict in all_downloaded.items():
|
||||
for photo, image_list in photo_dict.items():
|
||||
for f, image in image_list:
|
||||
os.remove(f)
|
||||
jm_debug('plugin.zip.remove', f'移除原文件: {f}')
|
||||
|
||||
for d in dir_list:
|
||||
if len(files_of_dir(d)) == 0:
|
||||
os.removedirs(d)
|
||||
jm_debug('plugin.zip.remove', f'移除文件夹: {d}')
|
||||
|
||||
def zip_photo(self, photo, image_list: list, zip_path: str):
|
||||
photo_dir = self.option.decide_image_save_dir(photo) \
|
||||
if len(image_list) == 0 \
|
||||
else os.path.dirname(image_list[0][0])
|
||||
|
||||
all_filepath = set(map(lambda t: t[0], image_list))
|
||||
|
||||
if len(all_filepath) == 0:
|
||||
jm_debug('plugin.zip.skip', '无下载文件,无需压缩')
|
||||
return
|
||||
|
||||
from common import backup_dir_to_zip
|
||||
backup_dir_to_zip(photo_dir, zip_path, acceptor=lambda f: f in all_filepath)
|
||||
jm_debug('plugin.zip.finish', f'压缩章节[{photo.photo_id}]成功 → {zip_path}')
|
||||
return photo_dir
|
||||
|
||||
def zip_album(self, album, photo_dict: dict, zip_path):
|
||||
album_dir = self.option.decide_album_dir(album)
|
||||
all_filepath: Set[str] = set()
|
||||
|
||||
for image_list in photo_dict.values():
|
||||
image_list: List[Tuple[str, JmImageDetail]]
|
||||
for path, _ in image_list:
|
||||
all_filepath.add(path)
|
||||
|
||||
if len(all_filepath) == 0:
|
||||
jm_debug('plugin.zip.skip', '无下载文件,无需压缩')
|
||||
return
|
||||
|
||||
from common import backup_dir_to_zip
|
||||
backup_dir_to_zip(
|
||||
album_dir,
|
||||
zip_path,
|
||||
acceptor=lambda f: f in all_filepath
|
||||
)
|
||||
|
||||
jm_debug('plugin.zip.finish', f'压缩本子[{album.album_id}]成功 → {zip_path}')
|
||||
return album_dir
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def get_zip_path(self, album, photo, filename_rule, suffix, zip_dir):
|
||||
filename = DirRule.apply_rule_directly(album, photo, filename_rule)
|
||||
from os.path import join
|
||||
return join(
|
||||
zip_dir,
|
||||
filename + fix_suffix(suffix),
|
||||
)
|
||||
|
||||
|
||||
JmModuleConfig.register_plugin(JmLoginPlugin)
|
||||
JmModuleConfig.register_plugin(UsageLogPlugin)
|
||||
JmModuleConfig.register_plugin(FindUpdatePlugin)
|
||||
JmModuleConfig.register_plugin(ZipPlugin)
|
||||
|
@ -7,6 +7,12 @@ from jmcomic import *
|
||||
|
||||
option = JmOption.default()
|
||||
|
||||
meta_data = {
|
||||
# 'proxies': ProxyBuilder.clash_proxy()
|
||||
}
|
||||
|
||||
disable_jm_debug()
|
||||
|
||||
|
||||
def get_domain_ls():
|
||||
template = 'https://jmcmomic.github.io/go/{}.html'
|
||||
@ -17,9 +23,12 @@ def get_domain_ls():
|
||||
domain_set: Set[str] = set()
|
||||
|
||||
def fetch_domain(url):
|
||||
postman = JmModuleConfig.new_postman()
|
||||
text = postman.get(url, allow_redirects=False).text
|
||||
# from curl_cffi import requests as postman
|
||||
postman = CurlCffiPostman.create()
|
||||
text = postman.get(url, allow_redirects=False, **meta_data).text
|
||||
for domain in JmcomicText.analyse_jm_pub_html(text):
|
||||
if domain.startswith('jm365.work'):
|
||||
continue
|
||||
domain_set.add(domain)
|
||||
|
||||
multi_thread_launcher(
|
||||
@ -31,10 +40,11 @@ def get_domain_ls():
|
||||
|
||||
domain_set = get_domain_ls()
|
||||
domain_status_dict = {}
|
||||
print(f'获取到{len(domain_set)}个域名,开始测试')
|
||||
|
||||
|
||||
def test_domain(domain: str):
|
||||
client = option.new_jm_client(domain_list=[domain])
|
||||
client = option.new_jm_client(domain_list=[domain], **meta_data)
|
||||
status = 'ok'
|
||||
|
||||
try:
|
||||
|
200
usage/usage_advanced.py
Normal file
200
usage/usage_advanced.py
Normal file
@ -0,0 +1,200 @@
|
||||
"""
|
||||
jmcomic综合使用实例,基于v2.2.2+内置插件
|
||||
|
||||
功能需求:
|
||||
1. 下载id为145504的本子;
|
||||
2. 只下载章节在290266之后的新章;
|
||||
3. 最后要把图片转为jpg格式;
|
||||
4. 要使用登录状态去下载;
|
||||
5. 压缩文件,按照章节压缩,一个章节一个压缩文件,压缩文件的命名: 章节标题.zip;
|
||||
6. 自动把下载的文件全部删除,最后只要保留压缩文件。
|
||||
|
||||
|
||||
实现方案:
|
||||
1. 写2行python代码
|
||||
2. 配置option,调用1的脚本即可
|
||||
|
||||
"""
|
||||
|
||||
# 1. 写2行python代码
|
||||
from jmcomic import create_option
|
||||
create_option('myoption.yml')
|
||||
|
||||
# 2. 配置option,调用1的脚本即可
|
||||
"""
|
||||
dir_rule: # 下载路径规则
|
||||
rule: Bd_Aid
|
||||
base_dir: D:/jmcomic
|
||||
|
||||
download:
|
||||
image:
|
||||
suffix: .jpg # 转为jpg格式的图片
|
||||
|
||||
client:
|
||||
domain:
|
||||
- 18comic.vip # 指定域名
|
||||
|
||||
plugin:
|
||||
after_init:
|
||||
- plugin: login # 登录插件
|
||||
kwargs:
|
||||
username: un
|
||||
password: pw
|
||||
|
||||
- plugin: find_update # 只下载新章插件
|
||||
kwargs:
|
||||
145504: 290266 # 下载本子145504的章节290266以后的新章
|
||||
|
||||
after_album:
|
||||
- plugin: zip # 压缩文件插件
|
||||
kwargs:
|
||||
level: photo # 按照章节,一个章节一个压缩文件
|
||||
filename_rule: Ptitle # 压缩文件的命名规则
|
||||
zip_dir: D:/GitProject/dev/pip/jmcomic/ # 压缩文件存放的文件夹
|
||||
delete_original_file: true # 压缩成功后,删除所有原文件和文件夹
|
||||
"""
|
||||
|
||||
|
||||
# 到此,程序已开始运行,最后会完成所有需求,并打印出如下日志。
|
||||
# 你可以详细查看以下日志,运行时所有关键节点都有日志打印。
|
||||
"""
|
||||
2023-08-01 02:23:57:【plugin.invoke】调用插件: [login]
|
||||
2023-08-01 02:23:57:【html】https://18comic.vip/login
|
||||
2023-08-01 02:23:58:【plugin.login】登录成功
|
||||
2023-08-01 02:23:58:【plugin.kwargs】插件参数类型转换: 145504 (<class 'int'>) -> 145504 (<class 'str'>)
|
||||
2023-08-01 02:23:58:【plugin.invoke】调用插件: [find_update]
|
||||
2023-08-01 02:23:58:【html】https://18comic.vip/album/145504
|
||||
2023-08-01 02:23:59:【album.before】本子获取成功: [145504], 作者: [G.HO], 章节数: [104], 总页数: [3598], 标题: [健身教练 / もしも、幼馴染を抱いたなら / Fitness], 关键词: [['完結', '韩漫', '上柱香再走', '以晨:我爹呢', '你妈真香', '連載中', '肌肉豪名不虚传']], 2023-08-01 02:23:59:【html】https://18comic.vip/photo/291640
|
||||
2023-08-01 02:24:00:【photo.before】开始下载章节: 291640 (145504[104/104]), 标题: [健身教练 完結], 图片数为[41]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00001.webp [1/41], [https://cdn-msp.18comic.vip/media/photos/291640/00001.webp] → [D:/jmcomic/145504/00001.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00004.webp [4/41], [https://cdn-msp.18comic.vip/media/photos/291640/00004.webp] → [D:/jmcomic/145504/00004.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00003.webp [3/41], [https://cdn-msp.18comic.vip/media/photos/291640/00003.webp] → [D:/jmcomic/145504/00003.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00007.webp [7/41], [https://cdn-msp.18comic.vip/media/photos/291640/00007.webp] → [D:/jmcomic/145504/00007.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00005.webp [5/41], [https://cdn-msp.18comic.vip/media/photos/291640/00005.webp] → [D:/jmcomic/145504/00005.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00002.webp [2/41], [https://cdn-msp.18comic.vip/media/photos/291640/00002.webp] → [D:/jmcomic/145504/00002.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00008.webp [8/41], [https://cdn-msp.18comic.vip/media/photos/291640/00008.webp] → [D:/jmcomic/145504/00008.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00006.webp [6/41], [https://cdn-msp.18comic.vip/media/photos/291640/00006.webp] → [D:/jmcomic/145504/00006.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00009.webp [9/41], [https://cdn-msp.18comic.vip/media/photos/291640/00009.webp] → [D:/jmcomic/145504/00009.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00010.webp [10/41], [https://cdn-msp.18comic.vip/media/photos/291640/00010.webp] → [D:/jmcomic/145504/00010.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00011.webp [11/41], [https://cdn-msp.18comic.vip/media/photos/291640/00011.webp] → [D:/jmcomic/145504/00011.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00012.webp [12/41], [https://cdn-msp.18comic.vip/media/photos/291640/00012.webp] → [D:/jmcomic/145504/00012.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00013.webp [13/41], [https://cdn-msp.18comic.vip/media/photos/291640/00013.webp] → [D:/jmcomic/145504/00013.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00014.webp [14/41], [https://cdn-msp.18comic.vip/media/photos/291640/00014.webp] → [D:/jmcomic/145504/00014.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00015.webp [15/41], [https://cdn-msp.18comic.vip/media/photos/291640/00015.webp] → [D:/jmcomic/145504/00015.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00016.webp [16/41], [https://cdn-msp.18comic.vip/media/photos/291640/00016.webp] → [D:/jmcomic/145504/00016.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00017.webp [17/41], [https://cdn-msp.18comic.vip/media/photos/291640/00017.webp] → [D:/jmcomic/145504/00017.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00018.webp [18/41], [https://cdn-msp.18comic.vip/media/photos/291640/00018.webp] → [D:/jmcomic/145504/00018.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00019.webp [19/41], [https://cdn-msp.18comic.vip/media/photos/291640/00019.webp] → [D:/jmcomic/145504/00019.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00020.webp [20/41], [https://cdn-msp.18comic.vip/media/photos/291640/00020.webp] → [D:/jmcomic/145504/00020.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00021.webp [21/41], [https://cdn-msp.18comic.vip/media/photos/291640/00021.webp] → [D:/jmcomic/145504/00021.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00023.webp [23/41], [https://cdn-msp.18comic.vip/media/photos/291640/00023.webp] → [D:/jmcomic/145504/00023.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00022.webp [22/41], [https://cdn-msp.18comic.vip/media/photos/291640/00022.webp] → [D:/jmcomic/145504/00022.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00024.webp [24/41], [https://cdn-msp.18comic.vip/media/photos/291640/00024.webp] → [D:/jmcomic/145504/00024.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00025.webp [25/41], [https://cdn-msp.18comic.vip/media/photos/291640/00025.webp] → [D:/jmcomic/145504/00025.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00027.webp [27/41], [https://cdn-msp.18comic.vip/media/photos/291640/00027.webp] → [D:/jmcomic/145504/00027.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00026.webp [26/41], [https://cdn-msp.18comic.vip/media/photos/291640/00026.webp] → [D:/jmcomic/145504/00026.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00028.webp [28/41], [https://cdn-msp.18comic.vip/media/photos/291640/00028.webp] → [D:/jmcomic/145504/00028.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00029.webp [29/41], [https://cdn-msp.18comic.vip/media/photos/291640/00029.webp] → [D:/jmcomic/145504/00029.jpg]
|
||||
2023-08-01 02:24:00:【image.before】图片准备下载: 291640/00030.webp [30/41], [https://cdn-msp.18comic.vip/media/photos/291640/00030.webp] → [D:/jmcomic/145504/00030.jpg]
|
||||
2023-08-01 02:24:01:【image.after】图片下载完成: 291640/00017.webp [17/41], [https://cdn-msp.18comic.vip/media/photos/291640/00017.webp] → [D:/jmcomic/145504/00017.jpg]
|
||||
2023-08-01 02:24:01:【image.before】图片准备下载: 291640/00031.webp [31/41], [https://cdn-msp.18comic.vip/media/photos/291640/00031.webp] → [D:/jmcomic/145504/00031.jpg]
|
||||
2023-08-01 02:24:01:【image.after】图片下载完成: 291640/00007.webp [7/41], [https://cdn-msp.18comic.vip/media/photos/291640/00007.webp] → [D:/jmcomic/145504/00007.jpg]
|
||||
2023-08-01 02:24:01:【image.before】图片准备下载: 291640/00032.webp [32/41], [https://cdn-msp.18comic.vip/media/photos/291640/00032.webp] → [D:/jmcomic/145504/00032.jpg]
|
||||
2023-08-01 02:24:01:【image.after】图片下载完成: 291640/00021.webp [21/41], [https://cdn-msp.18comic.vip/media/photos/291640/00021.webp] → [D:/jmcomic/145504/00021.jpg]
|
||||
2023-08-01 02:24:01:【image.before】图片准备下载: 291640/00033.webp [33/41], [https://cdn-msp.18comic.vip/media/photos/291640/00033.webp] → [D:/jmcomic/145504/00033.jpg]
|
||||
2023-08-01 02:24:02:【image.after】图片下载完成: 291640/00003.webp [3/41], [https://cdn-msp.18comic.vip/media/photos/291640/00003.webp] → [D:/jmcomic/145504/00003.jpg]
|
||||
2023-08-01 02:24:02:【image.before】图片准备下载: 291640/00034.webp [34/41], [https://cdn-msp.18comic.vip/media/photos/291640/00034.webp] → [D:/jmcomic/145504/00034.jpg]
|
||||
2023-08-01 02:24:03:【image.after】图片下载完成: 291640/00008.webp [8/41], [https://cdn-msp.18comic.vip/media/photos/291640/00008.webp] → [D:/jmcomic/145504/00008.jpg]
|
||||
2023-08-01 02:24:03:【image.before】图片准备下载: 291640/00035.webp [35/41], [https://cdn-msp.18comic.vip/media/photos/291640/00035.webp] → [D:/jmcomic/145504/00035.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00015.webp [15/41], [https://cdn-msp.18comic.vip/media/photos/291640/00015.webp] → [D:/jmcomic/145504/00015.jpg]
|
||||
2023-08-01 02:24:04:【image.before】图片准备下载: 291640/00036.webp [36/41], [https://cdn-msp.18comic.vip/media/photos/291640/00036.webp] → [D:/jmcomic/145504/00036.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00029.webp [29/41], [https://cdn-msp.18comic.vip/media/photos/291640/00029.webp] → [D:/jmcomic/145504/00029.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00030.webp [30/41], [https://cdn-msp.18comic.vip/media/photos/291640/00030.webp] → [D:/jmcomic/145504/00030.jpg]
|
||||
2023-08-01 02:24:04:【image.before】图片准备下载: 291640/00037.webp [37/41], [https://cdn-msp.18comic.vip/media/photos/291640/00037.webp] → [D:/jmcomic/145504/00037.jpg]
|
||||
2023-08-01 02:24:04:【image.before】图片准备下载: 291640/00038.webp [38/41], [https://cdn-msp.18comic.vip/media/photos/291640/00038.webp] → [D:/jmcomic/145504/00038.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00019.webp [19/41], [https://cdn-msp.18comic.vip/media/photos/291640/00019.webp] → [D:/jmcomic/145504/00019.jpg]
|
||||
2023-08-01 02:24:04:【image.before】图片准备下载: 291640/00039.webp [39/41], [https://cdn-msp.18comic.vip/media/photos/291640/00039.webp] → [D:/jmcomic/145504/00039.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00024.webp [24/41], [https://cdn-msp.18comic.vip/media/photos/291640/00024.webp] → [D:/jmcomic/145504/00024.jpg]
|
||||
2023-08-01 02:24:04:【image.before】图片准备下载: 291640/00040.webp [40/41], [https://cdn-msp.18comic.vip/media/photos/291640/00040.webp] → [D:/jmcomic/145504/00040.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00026.webp [26/41], [https://cdn-msp.18comic.vip/media/photos/291640/00026.webp] → [D:/jmcomic/145504/00026.jpg]
|
||||
2023-08-01 02:24:04:【image.before】图片准备下载: 291640/00041.webp [41/41], [https://cdn-msp.18comic.vip/media/photos/291640/00041.webp] → [D:/jmcomic/145504/00041.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00001.webp [1/41], [https://cdn-msp.18comic.vip/media/photos/291640/00001.webp] → [D:/jmcomic/145504/00001.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00010.webp [10/41], [https://cdn-msp.18comic.vip/media/photos/291640/00010.webp] → [D:/jmcomic/145504/00010.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00012.webp [12/41], [https://cdn-msp.18comic.vip/media/photos/291640/00012.webp] → [D:/jmcomic/145504/00012.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00032.webp [32/41], [https://cdn-msp.18comic.vip/media/photos/291640/00032.webp] → [D:/jmcomic/145504/00032.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00011.webp [11/41], [https://cdn-msp.18comic.vip/media/photos/291640/00011.webp] → [D:/jmcomic/145504/00011.jpg]
|
||||
2023-08-01 02:24:04:【image.after】图片下载完成: 291640/00034.webp [34/41], [https://cdn-msp.18comic.vip/media/photos/291640/00034.webp] → [D:/jmcomic/145504/00034.jpg]
|
||||
2023-08-01 02:24:05:【image.after】图片下载完成: 291640/00023.webp [23/41], [https://cdn-msp.18comic.vip/media/photos/291640/00023.webp] → [D:/jmcomic/145504/00023.jpg]
|
||||
2023-08-01 02:24:05:【image.after】图片下载完成: 291640/00002.webp [2/41], [https://cdn-msp.18comic.vip/media/photos/291640/00002.webp] → [D:/jmcomic/145504/00002.jpg]
|
||||
2023-08-01 02:24:05:【image.after】图片下载完成: 291640/00038.webp [38/41], [https://cdn-msp.18comic.vip/media/photos/291640/00038.webp] → [D:/jmcomic/145504/00038.jpg]
|
||||
2023-08-01 02:24:05:【image.after】图片下载完成: 291640/00041.webp [41/41], [https://cdn-msp.18comic.vip/media/photos/291640/00041.webp] → [D:/jmcomic/145504/00041.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00035.webp [35/41], [https://cdn-msp.18comic.vip/media/photos/291640/00035.webp] → [D:/jmcomic/145504/00035.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00009.webp [9/41], [https://cdn-msp.18comic.vip/media/photos/291640/00009.webp] → [D:/jmcomic/145504/00009.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00031.webp [31/41], [https://cdn-msp.18comic.vip/media/photos/291640/00031.webp] → [D:/jmcomic/145504/00031.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00033.webp [33/41], [https://cdn-msp.18comic.vip/media/photos/291640/00033.webp] → [D:/jmcomic/145504/00033.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00027.webp [27/41], [https://cdn-msp.18comic.vip/media/photos/291640/00027.webp] → [D:/jmcomic/145504/00027.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00039.webp [39/41], [https://cdn-msp.18comic.vip/media/photos/291640/00039.webp] → [D:/jmcomic/145504/00039.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00036.webp [36/41], [https://cdn-msp.18comic.vip/media/photos/291640/00036.webp] → [D:/jmcomic/145504/00036.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00020.webp [20/41], [https://cdn-msp.18comic.vip/media/photos/291640/00020.webp] → [D:/jmcomic/145504/00020.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00037.webp [37/41], [https://cdn-msp.18comic.vip/media/photos/291640/00037.webp] → [D:/jmcomic/145504/00037.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00040.webp [40/41], [https://cdn-msp.18comic.vip/media/photos/291640/00040.webp] → [D:/jmcomic/145504/00040.jpg]
|
||||
2023-08-01 02:24:06:【image.after】图片下载完成: 291640/00004.webp [4/41], [https://cdn-msp.18comic.vip/media/photos/291640/00004.webp] → [D:/jmcomic/145504/00004.jpg]
|
||||
2023-08-01 02:24:07:【image.after】图片下载完成: 291640/00005.webp [5/41], [https://cdn-msp.18comic.vip/media/photos/291640/00005.webp] → [D:/jmcomic/145504/00005.jpg]
|
||||
2023-08-01 02:24:07:【image.after】图片下载完成: 291640/00025.webp [25/41], [https://cdn-msp.18comic.vip/media/photos/291640/00025.webp] → [D:/jmcomic/145504/00025.jpg]
|
||||
2023-08-01 02:24:07:【image.after】图片下载完成: 291640/00014.webp [14/41], [https://cdn-msp.18comic.vip/media/photos/291640/00014.webp] → [D:/jmcomic/145504/00014.jpg]
|
||||
2023-08-01 02:24:07:【image.after】图片下载完成: 291640/00028.webp [28/41], [https://cdn-msp.18comic.vip/media/photos/291640/00028.webp] → [D:/jmcomic/145504/00028.jpg]
|
||||
2023-08-01 02:24:07:【image.after】图片下载完成: 291640/00018.webp [18/41], [https://cdn-msp.18comic.vip/media/photos/291640/00018.webp] → [D:/jmcomic/145504/00018.jpg]
|
||||
2023-08-01 02:24:08:【image.after】图片下载完成: 291640/00022.webp [22/41], [https://cdn-msp.18comic.vip/media/photos/291640/00022.webp] → [D:/jmcomic/145504/00022.jpg]
|
||||
2023-08-01 02:24:08:【image.after】图片下载完成: 291640/00016.webp [16/41], [https://cdn-msp.18comic.vip/media/photos/291640/00016.webp] → [D:/jmcomic/145504/00016.jpg]
|
||||
2023-08-01 02:24:08:【image.after】图片下载完成: 291640/00006.webp [6/41], [https://cdn-msp.18comic.vip/media/photos/291640/00006.webp] → [D:/jmcomic/145504/00006.jpg]
|
||||
2023-08-01 02:24:08:【image.after】图片下载完成: 291640/00013.webp [13/41], [https://cdn-msp.18comic.vip/media/photos/291640/00013.webp] → [D:/jmcomic/145504/00013.jpg]
|
||||
2023-08-01 02:24:08:【photo.after】章节下载完成: [291640] (145504[104/104])
|
||||
2023-08-01 02:24:08:【album.after】本子下载完成: [145504]
|
||||
2023-08-01 02:24:08:【plugin.invoke】调用插件: [zip]
|
||||
2023-08-01 02:24:08:【plugin.zip.finish】压缩章节[291640]成功 → D:/GitProject/dev/pip/jmcomic/健身教练 完結.zip
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00017.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00007.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00021.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00003.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00008.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00015.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00029.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00030.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00019.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00024.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00026.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00001.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00010.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00012.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00032.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00011.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00034.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00023.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00002.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00038.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00041.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00035.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00009.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00031.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00033.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00027.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00039.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00036.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00020.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00037.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00040.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00004.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00005.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00025.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00014.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00028.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00018.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00022.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00016.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00006.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除原文件: D:/jmcomic/145504/00013.jpg
|
||||
2023-08-01 02:24:08:【plugin.zip.remove】移除文件夹: D:/jmcomic/145504
|
||||
|
||||
进程已结束,退出代码为 0
|
||||
|
||||
"""
|
@ -1,6 +1,5 @@
|
||||
"""
|
||||
|
||||
本文件演示 jmcomic 的filter(过滤器)
|
||||
filter(过滤器)是v2.1.12新引入的机制,
|
||||
利用filter,你可以实现下载时过滤本子/章节/图片,完全控制你要下载的内容。
|
||||
|
||||
使用filter的步骤如下:
|
||||
|
@ -1,11 +1,13 @@
|
||||
"""
|
||||
plugin(扩展/插件)是jmcomic=2.2.0新引入的机制,
|
||||
plugin(扩展/插件)是v2.2.0新引入的机制,
|
||||
plugin机制可以实现在`特定时间` 回调 `特定插件`,实现灵活无感知的功能增强,
|
||||
目前仅支持一个时机: after_init,表示在option对象的 __init__ 初始化方法的最后。
|
||||
|
||||
下面以内置插件[login]为例:
|
||||
插件功能:登录JM,获取并保存cookies。
|
||||
你可以在option配置文件当中,配置如下内容,来实现在 after_init 时机,调用login插件。
|
||||
下面举例演示:
|
||||
特定时间:after_init
|
||||
特定插件:login
|
||||
插件功能:登录JM,获取并保存cookies,所有的Client都会使用这个cookies。
|
||||
|
||||
你可以在option配置文件当中,配置如下内容,来实现在after_init时,调用login插件。
|
||||
|
||||
plugin:
|
||||
after_init: # 时机
|
||||
@ -15,6 +17,18 @@ plugin:
|
||||
username: un # 禁漫帐号
|
||||
password: pw # 密码
|
||||
|
||||
有了上述option配置,当你调用下面的代码时,login插件就会执行。
|
||||
import jmcomic
|
||||
option = jmcomic.create_option('xxx.yml')
|
||||
# 程序走到这里,login插件已经调用完毕了。
|
||||
|
||||
-----------------------------------------
|
||||
|
||||
目前jmcomic已经内置了一些强大的插件,源码位于 src/jmcomic/jm_plugin.py。
|
||||
配置插件的方式请查看 assets/config/option_plugin。
|
||||
|
||||
-----------------------------------------
|
||||
|
||||
你也可以自定义插件和插件时机
|
||||
自定义插件时机需要你重写Option类,示例请见 usage_custom
|
||||
下面演示自定义插件,分为3步:
|
Loading…
Reference in New Issue
Block a user