Renew Projects
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
# 常用类和方法
|
||||
|
||||
## 下载本子/章节
|
||||
|
||||
```python
|
||||
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])
|
||||
```
|
||||
|
||||
## 使用option定制化下载本子
|
||||
|
||||
如果你在下载本子时有一些定制化需求,
|
||||
|
||||
例如指定禁漫域名,使用代理,登录禁漫,图片格式转换等等,
|
||||
|
||||
那么,你可以试试看jmcomic提供的option机制
|
||||
|
||||
```python
|
||||
from jmcomic import *
|
||||
|
||||
# 1. 在调用下载api前,通过创建和使用option对象,可以定制化下载行为。
|
||||
# 推荐使用配置文件的方式来创建option对象,
|
||||
# 你可以配置很多东西,比如代理、cookies、下载规则等等。
|
||||
# 配置文件的语法参考: https://jmcomic.readthedocs.io/en/latest/option_file_syntax/
|
||||
option = create_option_by_file('op.yml') # 通过配置文件来创建option对象
|
||||
|
||||
# 2. 调用下载api,把option作为参数传递
|
||||
download_album(123, option)
|
||||
# 也可以使用下面这种面向对象的方式,是一样的
|
||||
option.download_album(123)
|
||||
```
|
||||
|
||||
## 获取本子/章节/图片的实体类,下载图片
|
||||
|
||||
```python
|
||||
from jmcomic import *
|
||||
|
||||
# 客户端
|
||||
client = JmOption.default().new_jm_client()
|
||||
|
||||
# 本子实体类
|
||||
album: JmAlbumDetail = client.get_album_detail('427413')
|
||||
|
||||
|
||||
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
|
||||
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异常处理示例
|
||||
|
||||
```python
|
||||
from jmcomic import *
|
||||
|
||||
# 客户端
|
||||
client = JmOption.default().new_jm_client()
|
||||
|
||||
# 捕获jmcomic可能出现的异常
|
||||
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}')
|
||||
```
|
||||
|
||||
|
||||
## 搜索本子
|
||||
|
||||
```python
|
||||
from jmcomic import *
|
||||
|
||||
client = JmOption.default().new_jm_client()
|
||||
|
||||
# 分页查询,search_site就是禁漫网页上的【站内搜索】
|
||||
page: JmSearchPage = client.search_site(search_query='+MANA +无修正', page=1)
|
||||
# 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)
|
||||
```
|
||||
|
||||
## 搜索并下载本子
|
||||
|
||||
```python
|
||||
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)
|
||||
```
|
||||
|
||||
## 获取收藏夹
|
||||
|
||||
可参考discussions: https://github.com/hect0x7/JMComic-Crawler-Python/discussions/235
|
||||
|
||||
```python
|
||||
from jmcomic import *
|
||||
|
||||
option = JmOption.default()
|
||||
client = option.new_jm_client()
|
||||
client.login('用户名', '密码') # 也可以使用login插件/配置cookies
|
||||
|
||||
# 遍历全部收藏的所有页
|
||||
for page in cl.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
|
||||
|
||||
```python
|
||||
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_LATEST, # 按照观看数排序,具体可以写什么请见JmMagicConstants
|
||||
)
|
||||
|
||||
# 月排行,底层实现也是调的categories_filter
|
||||
page: JmCategoryPage = cl.month_ranking(1)
|
||||
# 周排行
|
||||
page: JmCategoryPage = cl.week_ranking(1)
|
||||
|
||||
# 循环获取分页,使用 cl.categories_filter_gen
|
||||
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)
|
||||
|
||||
```
|
||||
|
||||
## 高级搜索(分类/副分类)
|
||||
|
||||
禁漫网页端的搜索除了常规条件,还支持【分类】和【副分类】的搜索。
|
||||
|
||||
在任一搜索页面,你会看到本子图的右上方有两个标签。左边的是【分类】,右边的是【副分类】。
|
||||
|
||||
下面演示代码如何编写。
|
||||
|
||||
* **注意!!禁漫移动端没有提供如下功能,以下代码仅对网页端生效。**
|
||||
|
||||
```python
|
||||
# 在编写代码前,建议先熟悉禁漫网页的搜本功能,下面的代码都是对照网页编写的。
|
||||
# 网页搜索示例: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)
|
||||
```
|
||||
|
||||
|
||||
## 手动创建Client
|
||||
|
||||
```python
|
||||
# 默认的使用方式是先创建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
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,75 @@
|
||||
# 导出并下载你的禁漫收藏夹数据
|
||||
|
||||
一共需要三步:
|
||||
|
||||
1. fork一份我的代码仓库。
|
||||
2. 配置你的禁漫帐号密码。
|
||||
3. 运行工作流,下载结果。
|
||||
|
||||
下面截图解析这三步的详细过程。
|
||||
|
||||
## 1. fork一份我的代码仓库
|
||||
|
||||
访问下面这个网址:
|
||||
|
||||
`https://github.com/hect0x7/JMComic-Crawler-Python/fork`
|
||||
|
||||
直接拉到页面最底部,如下所示:
|
||||
|
||||
(最新提示,下图的1可以不做,即直接点绿色的Create fork按钮)
|
||||
|
||||

|
||||
|
||||
## 2. 配置你的禁漫帐号密码
|
||||
|
||||
在开始下面的步骤之前,你需要先启用你的repo的Actions,开启方式如下:
|
||||

|
||||
|
||||
|
||||
**注意事项:
|
||||
最好使用secrets保证不会泄漏信息。
|
||||
虽然也支持通过工作流dispatch输入帐号密码,但是那种方式会让账号密码明文显示在Actions日志中。
|
||||
如果你打算采用后者,最好下载完就及时删除工作流。**
|
||||
|
||||
然后访问下面这个网址:
|
||||
|
||||
`https://github.com/你的用户名/JMComic-Crawler-Python/settings/secrets/actions`
|
||||
|
||||
|
||||
按下图步骤进行操作:
|
||||
|
||||

|
||||
|
||||
然后来到填写页面
|
||||
|
||||

|
||||
|
||||
你需要填入下面的内容,并点【Add secret】保存:
|
||||
|
||||
`JM_USERNAME`: 你的禁漫帐号用户名
|
||||
|
||||
同样的方式再配置如下内容:
|
||||
|
||||
`JM_PASSWORD`: 你的禁漫帐号密码
|
||||
|
||||
`ZIP_PASSWORD`: 压缩文件密码,防止别人下载使用你的文件
|
||||
|
||||
|
||||
|
||||
## 3. 运行工作流,下载结果。
|
||||
|
||||
访问如下网址:
|
||||
|
||||
`https://github.com/你的用户名/JMComic-Crawler-Python/actions/workflows/export_favorites.yml`
|
||||
|
||||
按照下图步骤点击,即可运行工作流。
|
||||
|
||||
* 如果你在步骤2已经配置了secrets,无需在意输入框。
|
||||
|
||||
* 如果你没有配置secrets,你可以在输入框中输入账号 密码 压缩密码,但要记得及时删除工作流来避免信息泄漏。
|
||||
|
||||

|
||||
|
||||
待工作流变绿,表示已完成,点击去拉到页面底部,即可下载文件。
|
||||
|
||||

|
||||
@@ -0,0 +1,55 @@
|
||||
# 日志自定义
|
||||
|
||||
本文档缘起于 GitHub Discussions: [discussions/195](https://github.com/hect0x7/JMComic-Crawler-Python/discussions/195)
|
||||
|
||||
下面是这个问题的解决方法:
|
||||
|
||||
## 1. 日志完全开启/关闭
|
||||
|
||||
使用代码:
|
||||
|
||||
```
|
||||
from jmcomic import disable_jm_log
|
||||
disable_jm_log()
|
||||
```
|
||||
|
||||
使用配置:
|
||||
|
||||
```yaml
|
||||
log: false
|
||||
```
|
||||
|
||||
## 2. 日志过滤,只保留特定topic
|
||||
|
||||
使用插件配置
|
||||
|
||||
```yaml
|
||||
log: true
|
||||
|
||||
plugins:
|
||||
after_init:
|
||||
- plugin: log_topic_filter # 日志topic过滤插件
|
||||
kwargs:
|
||||
whitelist: [ # 只保留api和html,这两个是Client发请求时会打的日志topic
|
||||
'api',
|
||||
'html',
|
||||
]
|
||||
```
|
||||
|
||||
## 3. 屏蔽插件的日志
|
||||
|
||||
给插件配置加上一个`log`配置项即可
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
after_init:
|
||||
- plugin: client_proxy # 提高移动端的请求效率的插件
|
||||
log: false # 插件自身不打印日志
|
||||
kwargs:
|
||||
proxy_client_key: photo_concurrent_fetcher_proxy
|
||||
whitelist: [ api, ]
|
||||
```
|
||||
|
||||
## 4. 完全自定义 jmcomic 日志
|
||||
|
||||
你可以自定义jmcomic的模块日志打印函数,参考文档:[模块自定义](./4_module_custom.md#自定义log)
|
||||
@@ -0,0 +1,74 @@
|
||||
# GitHub Actions使用教程
|
||||
|
||||
一共需要三步:
|
||||
|
||||
1. fork一份我的代码仓库。
|
||||
2. 填写你需要下载的本子id。
|
||||
3. 等待GitHub Actions下载完成,下载成品zip文件。
|
||||
|
||||
下面截图解析这三步的详细过程。
|
||||
|
||||
## 1. fork一份我的代码仓库
|
||||
|
||||
访问下面这个网址:
|
||||
|
||||
`https://github.com/hect0x7/JMComic-Crawler-Python/fork`
|
||||
|
||||
直接拉到页面最底部,如下所示:
|
||||
|
||||
(最新提示,下图的1可以不做,即直接点绿色的Create fork按钮)
|
||||
|
||||

|
||||
|
||||
## 2. 填写你需要下载的本子id
|
||||
|
||||
在开始下面的步骤之前,你需要先启用你的repo的Actions,开启方式如下:
|
||||

|
||||
|
||||
|
||||
### 2.1. 方式一(最新、简单、推荐)
|
||||
|
||||
访问下面这个网址:
|
||||
|
||||
`https://github.com/你的用户名/JMComic-Crawler-Python/actions/workflows/download_dispatch.yml`
|
||||
|
||||
按下图步骤进行操作:
|
||||
|
||||

|
||||
|
||||
### 2.2. 方式二
|
||||
|
||||
访问下面这个网址:
|
||||
|
||||
`https://github.com/你的用户名/JMComic-Crawler-Python/edit/master/usage/workflow_download.py`
|
||||
|
||||
其实就是编辑 `usage/workflow_download.py`文件,任意分支皆可。
|
||||
|
||||
按下图步骤进行操作:
|
||||
|
||||

|
||||
|
||||
看到上面的绿色框里的代码了吗?(位置可能有些变化)
|
||||
|
||||
上面有注释,把你要下载的本子的id填入,一行一个,id前面可以带`JM`。
|
||||
|
||||
填完点提交,会自动触发GitHub Actions下载这些本子。
|
||||
|
||||
## 3. 等待GitHub Actions下载完成,下载成品zip文件
|
||||
|
||||
来到Actions页面,选择最新的一次记录,等待它完成。
|
||||
|
||||

|
||||
|
||||
完成以后,在页面最底部,点击下载你的成品即可。
|
||||
|
||||

|
||||
|
||||
如果你发现GitHub Actions显示❌,表明出现了问题,运行失败。
|
||||
|
||||
下面是问题的排查步骤:
|
||||
|
||||
1. 检查你在步骤2中填写是否有误。
|
||||
2. 点进build,查看运行流程,找到`运行下载脚本`这个步骤,查看报错的具体信息
|
||||
3. 来到我的项目仓库,点进Issue,先根据报错信息搜索Issue,如果能搜到类似问题,自行参考解决。
|
||||
4. 如果还是不知道如何解决,提一个Issue,详细描述下你的问题。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 命令行教程
|
||||
|
||||
## 1. 基本用法
|
||||
|
||||
```
|
||||
# 下载album 123 456,下载photo 333。彼此之间使用空格间隔
|
||||
jmcomic 123 456 p333
|
||||
```
|
||||
|
||||
## 2. 自定义option
|
||||
|
||||
### 2.1. 通过命令行
|
||||
使用 --option 参数指定option配置文件路径
|
||||
|
||||
```
|
||||
jmcomic 123 --option="D:/a.yml"
|
||||
```
|
||||
|
||||
### 2.2. 使用环境变量
|
||||
配置环境变量 `JM_OPTION_PATH` 为option配置文件路径
|
||||
|
||||
```
|
||||
set JM_OPTION_PATH="D:/a.yml"
|
||||
jmcomic 123
|
||||
```
|
||||
@@ -0,0 +1,194 @@
|
||||
# 模块自定义
|
||||
|
||||
|
||||
|
||||
下方所有函数都省略了如下的导包和准备代码
|
||||
|
||||
```python
|
||||
from jmcomic import *
|
||||
option = JmOption.default()
|
||||
client: JmcomicClient = option.build_jm_client()
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 自定义下载前后的回调函数
|
||||
|
||||
```python
|
||||
def custom_download_callback():
|
||||
"""
|
||||
该函数演示自定义下载时的回调函数
|
||||
"""
|
||||
|
||||
# jmcomic的下载功能由 JmModuleConfig.CLASS_DOWNLOADER 这个类来负责执行
|
||||
# 这个类默认是 JmDownloader,继承了DownloadCallback
|
||||
# 你可以写一个自定义类,继承JmDownloader,覆盖属于DownloadCallback的方法,来实现自定义回调
|
||||
class MyDownloader(JmDownloader):
|
||||
# 覆盖 album 下载完成后的回调
|
||||
def after_album(self, album: JmAlbumDetail):
|
||||
print(f'album下载完毕: {album}')
|
||||
pass
|
||||
|
||||
# 最后,让你的自定义类生效
|
||||
JmModuleConfig.CLASS_DOWNLOADER = MyDownloader
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 自定义option类
|
||||
|
||||
|
||||
```python
|
||||
def custom_option_class():
|
||||
"""
|
||||
该函数演示自定义option类
|
||||
"""
|
||||
|
||||
# jmcomic模块支持自定义Option类,
|
||||
# 你可以写一个自己的类,继承JmOption,然后覆盖其中的一些方法。
|
||||
class MyOption(JmOption):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
print('MyOption 初始化开始')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
print('调用了MyOption.default()')
|
||||
return super().default()
|
||||
|
||||
# 最后,替换默认Option类即可
|
||||
JmModuleConfig.CLASS_OPTION = MyOption
|
||||
```
|
||||
|
||||
|
||||
## 自定义client类
|
||||
|
||||
```python
|
||||
def custom_client_class():
|
||||
"""
|
||||
该文件演示自定义client类
|
||||
"""
|
||||
|
||||
# 默认情况下,JmOption使用client类是根据配置项 `client.impl` 决定的
|
||||
# JmOption会根据`client.impl`到 JmModuleConfig.CLASS_CLIENT_IMPL 中查找
|
||||
|
||||
# 自定义client的步骤如下
|
||||
|
||||
# 1. 自定义Client类
|
||||
class MyClient(JmHtmlClient):
|
||||
client_key = 'myclient'
|
||||
pass
|
||||
|
||||
# 2. 让MyClient生效
|
||||
JmModuleConfig.register_client(MyClient)
|
||||
|
||||
# 3. 在配置文件中使用你定义的client.impl,后续使用这个option即可
|
||||
"""
|
||||
client:
|
||||
impl: myclient
|
||||
"""
|
||||
```
|
||||
|
||||
|
||||
## 自定义实体类(本子/章节/图片)
|
||||
|
||||
```python
|
||||
def custom_album_photo_image_detail_class():
|
||||
"""
|
||||
该函数演示自定义实体类(本子/章节/图片)
|
||||
|
||||
在使用路径规则 DirRule 时,可能会遇到需要自定义实体类属性的情况,例如:
|
||||
dir_rule:
|
||||
base_dir: ${workspace}
|
||||
rule: Bd_Acustom_Pcustom
|
||||
|
||||
上面的Acustom,Pcustom都是自定义字段
|
||||
如果你想要使用这种自定义字段,你就需要替换默认的实体类,方式如下
|
||||
"""
|
||||
|
||||
# 自定义本子实体类
|
||||
class MyAlbum(JmAlbumDetail):
|
||||
# 自定义 custom 属性
|
||||
@property
|
||||
def custom(self):
|
||||
return f'custom_{self.title}'
|
||||
|
||||
# 自定义章节实体类
|
||||
class MyPhoto(JmPhotoDetail):
|
||||
# 自定义 custom 属性
|
||||
@property
|
||||
def custom(self):
|
||||
return f'custom_{self.title}'
|
||||
|
||||
"""
|
||||
v2.3.3: 支持更灵活的自定义方式,可以使用函数,效果同上,示例见下
|
||||
"""
|
||||
|
||||
class MyAlbum2(JmAlbumDetail):
|
||||
|
||||
def get_dirname(self, ref: str) -> str:
|
||||
if ref == 'custom':
|
||||
return f'custom_{self.name}'
|
||||
|
||||
return super().get_dirname(ref)
|
||||
|
||||
# 最后,替换默认实体类来让你的自定义类生效
|
||||
JmModuleConfig.CLASS_ALBUM = MyAlbum
|
||||
JmModuleConfig.CLASS_PHOTO = MyPhoto
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 自定义log
|
||||
|
||||
```python
|
||||
def custom_jm_log():
|
||||
"""
|
||||
该函数演示自定义log
|
||||
"""
|
||||
|
||||
# jmcomic模块在运行过程中会使用 jm_log() 这个函数进行打印信息
|
||||
# jm_log() 这个函数 最后会调用 JmModuleConfig.log_executor 函数
|
||||
# 你可以写一个自己的函数,替换 JmModuleConfig.log_executor,实现自定义log
|
||||
|
||||
# 1. 自定义log函数
|
||||
def my_log(topic: str, msg: str):
|
||||
"""
|
||||
这个log函数的参数列表必须包含两个参数,topic和msg
|
||||
@param topic: log主题,例如 'album.before', 'req.error', 'plugin.error'
|
||||
@param msg: 具体log的信息
|
||||
"""
|
||||
pass
|
||||
|
||||
# 2. 让my_log生效
|
||||
JmModuleConfig.log_executor = my_log
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 自定义异常监听器/回调
|
||||
|
||||
```python
|
||||
def custom_exception_listener():
|
||||
"""
|
||||
该函数演示jmcomic的异常监听器机制
|
||||
"""
|
||||
|
||||
# 1. 选一个可能会发生的、你感兴趣的异常
|
||||
etype = ResponseUnexpectedException
|
||||
|
||||
|
||||
def listener(e):
|
||||
"""
|
||||
你的监听器方法
|
||||
该方法无需返回值
|
||||
:param e: 异常实例
|
||||
"""
|
||||
print(f'my exception listener invoke !!! exception happened: {e}')
|
||||
|
||||
|
||||
# 注册监听器/回调
|
||||
# 这个异常类(或者这个异常的子类)的实例将要被raise前,你的listener方法会被调用
|
||||
JmModuleConfig.register_exception_listener(etype, listener)
|
||||
```
|
||||
@@ -0,0 +1,78 @@
|
||||
# Filter - 下载过滤器
|
||||
|
||||
filter(过滤器)是v2.1.12新引入的机制,
|
||||
利用filter,你可以实现下载时过滤本子/章节/图片,完全控制你要下载的内容。
|
||||
|
||||
使用filter的步骤如下:
|
||||
|
||||
```
|
||||
1. 自定义class,继承JmDownloader,重写do_filter方法,即:
|
||||
class MyDownloader(JmDownloader):
|
||||
def do_filter(self, detail):
|
||||
# 如何重写?参考JmDownloader.do_filter和下面的示例
|
||||
...
|
||||
|
||||
2. 让你的class生效,使用如下代码:
|
||||
JmModuleConfig.CLASS_DOWNLOADER = MyDownloader
|
||||
|
||||
3. 照常使用下载api:
|
||||
download_album(xxx, option)
|
||||
```
|
||||
|
||||
* 下面的示例只演示步骤1
|
||||
|
||||
|
||||
|
||||
## 示例1:只下载章节的前三张图
|
||||
|
||||
```python
|
||||
from jmcomic import *
|
||||
|
||||
class First3ImageDownloader(JmDownloader):
|
||||
|
||||
def do_filter(self, detail):
|
||||
if detail.is_photo():
|
||||
photo: JmPhotoDetail = detail
|
||||
# 支持[start,end,step]
|
||||
return photo[:3]
|
||||
|
||||
return detail
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 示例2:只下载本子的特定章节以后的章节
|
||||
|
||||
```python
|
||||
from jmcomic import *
|
||||
|
||||
# 参考:https://github.com/hect0x7/JMComic-Crawler-Python/issues/95
|
||||
class FindUpdateDownloader(JmDownloader):
|
||||
album_after_photo = {
|
||||
'xxx': 'yyy'
|
||||
}
|
||||
|
||||
def do_filter(self, detail):
|
||||
if not detail.is_album():
|
||||
return detail
|
||||
|
||||
return self.find_update(detail)
|
||||
|
||||
# 带入漫画id, 章节id(第x章),寻找该漫画下第x章节後的所有章节Id
|
||||
def find_update(self, album: JmAlbumDetail):
|
||||
if album.album_id not in self.album_after_photo:
|
||||
return album
|
||||
|
||||
photo_ls = []
|
||||
photo_begin = self.album_after_photo[album.album_id]
|
||||
is_new_photo = False
|
||||
|
||||
for photo in album:
|
||||
if is_new_photo:
|
||||
photo_ls.append(photo)
|
||||
|
||||
if photo.photo_id == photo_begin:
|
||||
is_new_photo = True
|
||||
|
||||
return photo_ls
|
||||
```
|
||||
@@ -0,0 +1,121 @@
|
||||
# Plugin - 插件
|
||||
|
||||
plugin(扩展/插件)是v2.2.0新引入的机制,使用插件可以实现灵活无感知的功能增强。
|
||||
|
||||
目前jmcomic已经内置了一些插件,源码位于 src/jmcomic/jm_plugin.py。
|
||||
|
||||
你可以在这里查看这些插件的配置→ [option_file_syntax](../option_file_syntax.md#3-option插件配置项)
|
||||
|
||||
## 1. 插件机制介绍
|
||||
|
||||
你可以在option配置文件中配置插件,插件会特定的<u>`事件`</u>发生时自动执行。
|
||||
|
||||
jmcomic里的内置事件如下:
|
||||
|
||||
- `after_init`: option对象创建后
|
||||
|
||||
|
||||
- `before_image`: 下载图片前
|
||||
- `before_album`: 下载本子前
|
||||
- `before_photo`: 下载章节前
|
||||
|
||||
|
||||
- `after_image`: 下载图片后
|
||||
- `after_album`: 下载本子后
|
||||
- `after_photo`: 下载章节后
|
||||
|
||||
举例:你可以配置一个login插件,在option对象创建后(`after_init`)时,调用`login`插件,执行登录禁漫的功能。
|
||||
|
||||
这样你后续使用option下载本子时,都会处于已登录状态。已登录状态可以让你访问所有禁漫本子。
|
||||
|
||||
## 2. 示例:在after_init时调用login插件
|
||||
|
||||
`login`插件功能:登录JM,获取并保存cookies到option内。后续option创建的Client都会携带cookies,即都是登录状态。
|
||||
|
||||
配置方式:将以下配置写入option文件
|
||||
|
||||
```yaml
|
||||
plugins: # 插件配置项
|
||||
after_init: # 在after_init事件时自动执行插件
|
||||
- plugin: login # 插件的key
|
||||
kwargs: # 下面是给插件的参数 (kwargs),由插件类自定义
|
||||
username: un # 禁漫帐号
|
||||
password: pw # 密码
|
||||
```
|
||||
|
||||
有了上述option配置,当你调用下面的代码时,login插件就会执行。
|
||||
|
||||
```python
|
||||
import jmcomic
|
||||
|
||||
option = jmcomic.create_option_by_file('xxx.yml') # 创建option对象
|
||||
# 程序走到这里,login插件已经调用完毕了
|
||||
# 后续下载本子就都是已登录状态里了
|
||||
option.download_album(123)
|
||||
```
|
||||
|
||||
## 3. 怎么手动调用插件
|
||||
|
||||
你可以使用下面的代码触发某个事件
|
||||
|
||||
```python
|
||||
# 假设你已经创建了option对象
|
||||
from jmcomic import JmOption
|
||||
|
||||
option: JmOption
|
||||
# 手动调用after_init事件下的插件
|
||||
option.call_all_plugin('after_init')
|
||||
# 手动调用一个特定事件的插件(如果你没有配置这个事件的插件,那么无事发生。)
|
||||
option.call_all_plugin('my_event')
|
||||
|
||||
```
|
||||
|
||||
## 4. 示例:自定义插件
|
||||
|
||||
* 如果你有好的plugin想法,也欢迎向我提PR,将你的plugin内置到jmcomic模块中
|
||||
|
||||
下面演示自定义插件,分为3步:
|
||||
|
||||
1. 自定义plugin类
|
||||
2. 让plugin类生效
|
||||
3. 使用plugin的key
|
||||
|
||||
```python
|
||||
# 1. 自定义plugin类
|
||||
from jmcomic import JmOptionPlugin, JmModuleConfig
|
||||
|
||||
|
||||
# 自定义一个类,继承JmOptionPlugin
|
||||
class MyPlugin(JmOptionPlugin):
|
||||
# 指定你的插件的key
|
||||
plugin_key = 'myplugin'
|
||||
|
||||
# 实现invoke方法
|
||||
# 方法的参数可以自定义,这里假设方法只有一个参数 word
|
||||
def invoke(self, word) -> None:
|
||||
print(word)
|
||||
|
||||
|
||||
# 2. 让plugin类生效
|
||||
JmModuleConfig.register_plugin(MyPlugin)
|
||||
```
|
||||
|
||||
接下来,在option中配置如下内容来使用你的插件
|
||||
|
||||
```yaml
|
||||
# 3. 在配置文件中使用plugin
|
||||
plugins:
|
||||
after_init: # 事件
|
||||
- plugin: myplugin # 你自定义的插件key
|
||||
kwargs:
|
||||
word: hello jmcomic # 你自定义的插件的参数
|
||||
```
|
||||
|
||||
完成上述步骤后,每当你使用下面的代码,你的plugin会被调用,控制台就会打印出 `hello jmcomic`
|
||||
|
||||
```python
|
||||
from jmcomic import create_option
|
||||
|
||||
option = create_option('xxx')
|
||||
```
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# 综合使用实例
|
||||
|
||||
* 基于v2.2.2+内置插件
|
||||
|
||||
## 功能需求
|
||||
|
||||
1. 下载id为145504的本子;
|
||||
2. 只下载章节在290266之后的新章;
|
||||
3. 最后要把图片转为jpg格式;
|
||||
4. 要使用登录状态去下载;
|
||||
5. 压缩文件,按照章节压缩,一个章节一个压缩文件,压缩文件的命名: 章节标题.zip;
|
||||
6. 自动把下载的文件全部删除,最后只要保留压缩文件。
|
||||
|
||||
## 实现方案
|
||||
|
||||
#### 1. 写2行python代码
|
||||
```python
|
||||
from jmcomic import create_option
|
||||
create_option('myoption.yml')
|
||||
```
|
||||
|
||||
#### 2. 配置option,调用1的脚本即可
|
||||
```yaml
|
||||
dir_rule: # 下载路径规则
|
||||
rule: Bd_Aid
|
||||
base_dir: D:/jmcomic
|
||||
|
||||
download:
|
||||
image:
|
||||
suffix: .jpg # 转为jpg格式的图片
|
||||
|
||||
client:
|
||||
domain:
|
||||
- 18comic.vip # 指定域名
|
||||
|
||||
plugins:
|
||||
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:/jmcomic # 压缩文件存放的文件夹
|
||||
delete_original_file: true # 压缩成功后,删除所有原文件和文件夹
|
||||
```
|
||||
@@ -0,0 +1,81 @@
|
||||
# 趣味用法:测试你的ip可以访问哪些禁漫域名
|
||||
|
||||
```python
|
||||
"""
|
||||
该脚本的作用:测试当前ip可以访问哪些禁漫域名
|
||||
"""
|
||||
|
||||
from jmcomic import *
|
||||
|
||||
option = JmOption.default()
|
||||
|
||||
meta_data = {
|
||||
# 'proxies': ProxyBuilder.clash_proxy()
|
||||
}
|
||||
|
||||
disable_jm_log()
|
||||
|
||||
|
||||
def get_all_domain():
|
||||
template = 'https://jmcmomic.github.io/go/{}.html'
|
||||
url_ls = [
|
||||
template.format(i)
|
||||
for i in range(300, 309)
|
||||
]
|
||||
domain_set: Set[str] = set()
|
||||
|
||||
def fetch_domain(url):
|
||||
from curl_cffi import requests as postman
|
||||
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(
|
||||
iter_objs=url_ls,
|
||||
apply_each_obj_func=fetch_domain,
|
||||
)
|
||||
return domain_set
|
||||
|
||||
|
||||
domain_set = get_all_domain()
|
||||
print(f'获取到{len(domain_set)}个域名,开始测试')
|
||||
domain_status_dict = {}
|
||||
|
||||
|
||||
def test_domain(domain: str):
|
||||
client = option.new_jm_client(impl='html', domain_list=[domain], **meta_data)
|
||||
status = 'ok'
|
||||
|
||||
try:
|
||||
client.get_album_detail('123456')
|
||||
except Exception as e:
|
||||
status = str(e.args)
|
||||
pass
|
||||
|
||||
domain_status_dict[domain] = status
|
||||
|
||||
|
||||
multi_thread_launcher(
|
||||
iter_objs=domain_set,
|
||||
apply_each_obj_func=test_domain,
|
||||
)
|
||||
|
||||
for domain, status in domain_status_dict.items():
|
||||
print(f'{domain}: {status}')
|
||||
|
||||
```
|
||||
|
||||
# 程序输出示例
|
||||
|
||||
```text
|
||||
获取到7个域名,开始测试
|
||||
18comic.vip: ok
|
||||
18comic.org: ok
|
||||
18comic-palworld.vip: ok
|
||||
18comic-c.art: ok
|
||||
jmcomic1.me: ok
|
||||
jmcomic.me: ok
|
||||
18comic-palworld.club: ok
|
||||
```
|
||||
@@ -0,0 +1,144 @@
|
||||
# 自定义下载文件夹名
|
||||
|
||||
## 0. 最简单直接粗暴有效的方式
|
||||
|
||||
使用插件`replace_path_string`:
|
||||
|
||||
这个插件可以直接替换下载文件夹路径,配置示例如下(把如下配置放入option配置文件即可):
|
||||
|
||||
```yml
|
||||
plugins:
|
||||
after_init:
|
||||
- plugin: replace_path_string
|
||||
kwargs:
|
||||
replace:
|
||||
# {左边写你要替换的原文}: {右边写替换成什么文本}
|
||||
kyockcho: きょくちょ
|
||||
```
|
||||
该示例会把文件夹路径中所有`kyockcho`都变为`きょくちょ`,例如:
|
||||
|
||||
`D:/a/[kyockcho]本子名称 - kyockcho/` 改为↓
|
||||
|
||||
`D:/a/[きょくちょ]本子名称 - きょくちょ/`
|
||||
|
||||
---------------
|
||||
**_如果上述简单的文本替换无法满足你,或者你需要更多上下文写逻辑代码,那么下面的内容正适合你阅读。_**
|
||||
|
||||
## 1. DirRule机制简介
|
||||
|
||||
|
||||
当你使用download_album下载本子时,本子会以一定的路径规则(DirRule)下载到你的磁盘上。
|
||||
|
||||
你可以使用配置文件定制DirRule,例如下面的例子:
|
||||
|
||||
```yaml
|
||||
dir_rule:
|
||||
# 设定根目录 base_dir
|
||||
base_dir: D:/a/b/c/
|
||||
rule: Bd / Ptitle # P表示章节,title表示使用章节的title字段
|
||||
# 这个规则的含义是,把图片下载到路径 {base_dir}/{Ptitle}/ 下
|
||||
# 即:根目录 / 章节标题 / 图片文件
|
||||
```
|
||||
|
||||
例如,假设一个章节的名称(Ptitle)是ddd,则最后的下载文件夹结构为 `D:/a/b/c/ddd/`:
|
||||
|
||||
```
|
||||
D:/a/b/c/ddd/00001.webp
|
||||
D:/a/b/c/ddd/00002.webp
|
||||
D:/a/b/c/ddd/00003.webp
|
||||
...
|
||||
```
|
||||
|
||||
上述的Ptitle,P表示章节,title表示使用章节的title字段。
|
||||
|
||||
除了title,你还可以写什么?其实Ptitle表示的是jmcomic里的章节实体类 JmPhotoDetail 的属性。
|
||||
|
||||
最终能写什么,取决于JmPhotoDetail有哪些属性,建议使用IDE来获知这些属性,不过这需要你懂一些python基础。
|
||||
|
||||
除了Pxxx,你还可以写Axxx,表示这个章节所在的本子的属性xxx,详见本子实体类 JmAlbumDetail。
|
||||
|
||||
|
||||
## 2. 自定义字段名
|
||||
|
||||
上述例子使用了title字段,如果你想自定义一个字段,然后在DirRule中使用自定义字段,该怎么做?
|
||||
|
||||
基于v2.4.6,你可以使用如下方式
|
||||
|
||||
|
||||
|
||||
1. 给你的自定义字段取个名
|
||||
|
||||
```yaml
|
||||
dir_rule: # 忽略base_dir配置项
|
||||
rule: Bd_Amyname # A表示本子,myname表示本子的一个自定义字段
|
||||
```
|
||||
|
||||
|
||||
|
||||
2. 在代码中,加入你自定义字段的处理函数
|
||||
|
||||
```python
|
||||
from jmcomic import JmModuleConfig
|
||||
# 你需要写一个函数,把字段名作为key,函数作为value,加到JmModuleConfig.AFIELD_ADVICE这个字典中
|
||||
JmModuleConfig.AFIELD_ADVICE['myname'] = lambda album: f'[{album.id}] {album.title}'
|
||||
```
|
||||
|
||||
|
||||
|
||||
这样一来,Amyname这个规则就会交由你的函数进行处理,你便可以返回一个自定义的文件夹名
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 3. 更多的使用例子
|
||||
|
||||
|
||||
|
||||
### 完全使用自己的文件夹名
|
||||
|
||||
```python
|
||||
from jmcomic import JmModuleConfig
|
||||
|
||||
dic = {
|
||||
'248965': '社团学姐(爆赞韩漫)'
|
||||
}
|
||||
|
||||
# Amyname
|
||||
JmModuleConfig.AFIELD_ADVICE['myname'] = lambda album: dic[album.id]
|
||||
download_album(248965)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 文件夹名=作者+标题
|
||||
|
||||
```python
|
||||
from jmcomic import JmModuleConfig
|
||||
# Amyname
|
||||
JmModuleConfig.AFIELD_ADVICE['myname'] = lambda album: f'【{album.author}】{album.title}'
|
||||
# album有一个内置字段 authoroname,效果类似
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 文件夹名=禁漫车号+标题
|
||||
|
||||
```python
|
||||
from jmcomic import JmModuleConfig
|
||||
# Pmyname
|
||||
JmModuleConfig.PFIELD_ADVICE['myname'] = lambda photo: f'【{photo.id}】{photo.title}'
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 文件夹名=第x话+标题
|
||||
|
||||
```yaml
|
||||
# 直接使用内置字段 indextitle 即可
|
||||
dir_rule:
|
||||
rule: Bd_Pindextitle
|
||||
```
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user