mirror of
https://github.com/hect0x7/JMComic-Crawler-Python.git
synced 2025-09-26 22:31:30 +08:00
v2.5.7: 跟进禁漫v1.6.7版本更新的分类参数,优化代码和文档。
This commit is contained in:
parent
fbaaffc464
commit
147c796c5d
47
README.md
47
README.md
@ -14,16 +14,26 @@
|
|||||||
|
|
||||||
本项目的核心功能是下载本子,基于此,设计了一套方便使用、便于扩展,能满足一些特殊下载需求的框架。
|
本项目的核心功能是下载本子,基于此,设计了一套方便使用、便于扩展,能满足一些特殊下载需求的框架。
|
||||||
|
|
||||||
除了下载功能以外,也实现了其他的一些禁漫接口,例如登录、搜索、收藏夹、分类、排行榜等等,按需实现。
|
目前核心功能实现较为稳定,项目也处于维护阶段。
|
||||||
|
|
||||||
目前核心功能实现较为稳定,项目也处于维护阶段(因为禁漫接口经常变动,需要经常维护)。
|
除了下载功能以外,也实现了其他的一些禁漫接口,按需实现,具体如下。
|
||||||
|
|
||||||
|
### 已实现的禁漫API:
|
||||||
|
|
||||||
|
- 登录
|
||||||
|
- 搜本
|
||||||
|
- 分类 (排行榜)
|
||||||
|
- 本子章节详情
|
||||||
|
- 图片下载解码
|
||||||
|
- 收藏夹
|
||||||
|
- 移动端接口加解密
|
||||||
|
|
||||||
## 安装教程
|
## 安装教程
|
||||||
|
|
||||||
* 通过pip官方源安装(推荐,并且更新也是这个命令)
|
* 通过pip官方源安装(推荐,并且更新也是这个命令)
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pip install jmcomic -i https://pypi.org/project --upgrade
|
pip install jmcomic -i https://pypi.org/project -U
|
||||||
```
|
```
|
||||||
* 通过源代码安装
|
* 通过源代码安装
|
||||||
|
|
||||||
@ -47,6 +57,21 @@ jmcomic.download_album('422866') # 传入要下载的album的id,即可下载
|
|||||||
$ jmcomic 422866
|
$ jmcomic 422866
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 进阶使用
|
||||||
|
|
||||||
|
文档网站:[jmcomic.readthedocs.io](https://jmcomic.readthedocs.io/en/latest)
|
||||||
|
|
||||||
|
进阶使用可以参考:[jmcomic常用类和方法演示](assets/docs/sources/tutorial/0_demo.md)
|
||||||
|
|
||||||
|
下面列出的是一些常用的文档:
|
||||||
|
|
||||||
|
* [jmcomic常用类和方法演示](assets/docs/sources/tutorial/0_demo.md)
|
||||||
|
* [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
|
||||||
|
* [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)
|
||||||
|
* [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
|
||||||
|
* [插件机制](assets/docs/sources/tutorial/6_plugin.md)
|
||||||
|
* [下载过滤器机制](assets/docs/sources/tutorial/5_filter.md)
|
||||||
|
|
||||||
## 项目特点
|
## 项目特点
|
||||||
|
|
||||||
- **绕过Cloudflare的反爬虫**
|
- **绕过Cloudflare的反爬虫**
|
||||||
@ -83,19 +108,6 @@ $ jmcomic 422866
|
|||||||
- `jpg图片合成为一个pdf插件`
|
- `jpg图片合成为一个pdf插件`
|
||||||
- `导出收藏夹为csv文件插件`
|
- `导出收藏夹为csv文件插件`
|
||||||
|
|
||||||
## 进阶使用
|
|
||||||
|
|
||||||
进阶使用请查阅文档:[文档](https://jmcomic.readthedocs.io/en/latest)
|
|
||||||
|
|
||||||
下面列出一些常用的文档链接:
|
|
||||||
|
|
||||||
* [option配置文件语法(包含插件配置)](./assets/docs/sources/option_file_syntax.md)
|
|
||||||
* [常用类和方法演示(下载本子、获取实体类、搜索本子)](assets/docs/sources/tutorial/3_demo.md)
|
|
||||||
* [命令行使用教程](assets/docs/sources/tutorial/2_command_line.md)
|
|
||||||
* [GitHub Actions使用教程](./assets/docs/sources/tutorial/1_github_actions.md)
|
|
||||||
* [插件机制](assets/docs/sources/tutorial/6_plugin.md)
|
|
||||||
* [下载过滤器机制](assets/docs/sources/tutorial/5_filter.md)
|
|
||||||
|
|
||||||
## 使用小说明
|
## 使用小说明
|
||||||
|
|
||||||
* Python >= 3.7
|
* Python >= 3.7
|
||||||
@ -103,10 +115,11 @@ $ jmcomic 422866
|
|||||||
|
|
||||||
## 项目文件夹介绍
|
## 项目文件夹介绍
|
||||||
|
|
||||||
|
* .github:GitHub Actions配置文件
|
||||||
* assets:存放一些非代码的资源文件
|
* assets:存放一些非代码的资源文件
|
||||||
|
|
||||||
* config:存放配置文件
|
|
||||||
* docs:项目文档
|
* docs:项目文档
|
||||||
|
* option:存放配置文件
|
||||||
|
|
||||||
* src:存放源代码
|
* src:存放源代码
|
||||||
|
|
||||||
|
@ -90,10 +90,13 @@ dir_rule:
|
|||||||
# 本项只建议了解编程的朋友定制,实现在这个类: jmcomic.jm_option.DirRule
|
# 本项只建议了解编程的朋友定制,实现在这个类: jmcomic.jm_option.DirRule
|
||||||
# 写法:
|
# 写法:
|
||||||
# 1. 以'Bd'开头,表示根目录
|
# 1. 以'Bd'开头,表示根目录
|
||||||
# 2. 文件夹每增加一层,使用'_'区隔
|
# 2. 文件夹每增加一层,使用 '_' 或者 '/' 区隔
|
||||||
# 3. 文件夹名字表示为 Pxxx/Ayyy,意思是 JmPhotoDetail.xxx / JmAlbumDetail的.yyy。xxx和yyy可以写什么需要看源码。
|
# 3. 用Pxxx或者Ayyy指代文件夹名,意思是 JmPhotoDetail.xxx / JmAlbumDetail的.yyy。xxx和yyy可以写什么需要看源码。
|
||||||
# 下面是示例,表示使用禁漫网站的默认下载方式 [根目录 / 本子id / 章节序号 / 图片文件]
|
#
|
||||||
# rule: Bd_Aid_Pindex
|
# 下面演示如果要使用禁漫网站的默认下载方式,该怎么写:
|
||||||
|
# 规则: 根目录 / 本子id / 章节序号 / 图片文件
|
||||||
|
# rule: 'Bd / Aid / Pindex'
|
||||||
|
# rule: 'Bd_Aid_Pindex'
|
||||||
|
|
||||||
# 默认规则是: 根目录 / 章节标题 / 图片文件
|
# 默认规则是: 根目录 / 章节标题 / 图片文件
|
||||||
rule: Bd_Ptitle
|
rule: Bd_Ptitle
|
||||||
|
189
assets/docs/sources/tutorial/0_demo.md
Normal file
189
assets/docs/sources/tutorial/0_demo.md
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# jmcomic 常用类和方法演示
|
||||||
|
|
||||||
|
## 下载本子/章节
|
||||||
|
|
||||||
|
```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)
|
||||||
|
|
||||||
|
# 图片实体类
|
||||||
|
image: JmImageDetail
|
||||||
|
for image in photo:
|
||||||
|
print(image.img_url)
|
||||||
|
|
||||||
|
|
||||||
|
# 多线程发起请求
|
||||||
|
multi_thread_launcher(
|
||||||
|
iter_objs=album,
|
||||||
|
apply_each_obj_func=fetch
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 搜索本子
|
||||||
|
|
||||||
|
```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)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 分类 / 排行榜
|
||||||
|
|
||||||
|
禁漫的分类是一个和搜索有些类似的功能。
|
||||||
|
|
||||||
|
搜索是按某一条件进行过滤。
|
||||||
|
|
||||||
|
分类没有过滤,就是把某一类别(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(1, # 起始页码
|
||||||
|
# 下面是分类参数
|
||||||
|
JmMagicConstants.TIME_WEEK,
|
||||||
|
JmMagicConstants.CATEGORY_ALL,
|
||||||
|
JmMagicConstants.ORDER_BY_VIEW,
|
||||||
|
):
|
||||||
|
for aid, atitle in page:
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
```
|
@ -1,117 +0,0 @@
|
|||||||
# 使用jmcomic实现简单功能
|
|
||||||
|
|
||||||
## 下载本子
|
|
||||||
|
|
||||||
```python
|
|
||||||
from jmcomic import *
|
|
||||||
|
|
||||||
ls = str_to_list('''
|
|
||||||
438696
|
|
||||||
https://18comic.vip/album/497896/
|
|
||||||
''')
|
|
||||||
|
|
||||||
download_album(ls)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 获取实体类
|
|
||||||
|
|
||||||
```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)
|
|
||||||
|
|
||||||
# 图片实体类
|
|
||||||
image: JmImageDetail
|
|
||||||
for image in photo:
|
|
||||||
print(image.img_url)
|
|
||||||
|
|
||||||
# 多线程发起请求
|
|
||||||
multi_thread_launcher(
|
|
||||||
iter_objs=album,
|
|
||||||
apply_each_obj_func=fetch
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 搜索本子
|
|
||||||
|
|
||||||
```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)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 手动创建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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
@ -67,3 +67,15 @@ for domain, status in domain_status_dict.items():
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# 程序输出示例
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
@ -2,7 +2,7 @@
|
|||||||
# 被依赖方 <--- 使用方
|
# 被依赖方 <--- 使用方
|
||||||
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
|
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
|
||||||
|
|
||||||
__version__ = '2.5.6'
|
__version__ = '2.5.7'
|
||||||
|
|
||||||
from .api import *
|
from .api import *
|
||||||
from .jm_plugin import *
|
from .jm_plugin import *
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
from .jm_downloader import *
|
from .jm_downloader import *
|
||||||
|
|
||||||
|
__DOWNLOAD_API_RET = Tuple[JmAlbumDetail, JmDownloader]
|
||||||
|
|
||||||
def download_batch(download_api,
|
def download_batch(download_api,
|
||||||
jm_id_iter: Union[Iterable, Generator],
|
jm_id_iter: Union[Iterable, Generator],
|
||||||
option=None,
|
option=None,
|
||||||
downloader=None,
|
downloader=None,
|
||||||
) -> Set[Tuple[JmAlbumDetail, JmDownloader]]:
|
) -> Set[__DOWNLOAD_API_RET]:
|
||||||
"""
|
"""
|
||||||
批量下载 album / photo
|
批量下载 album / photo
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ def download_album(jm_album_id,
|
|||||||
option=None,
|
option=None,
|
||||||
downloader=None,
|
downloader=None,
|
||||||
callback=None,
|
callback=None,
|
||||||
):
|
) -> Union[__DOWNLOAD_API_RET, Set[__DOWNLOAD_API_RET]]:
|
||||||
"""
|
"""
|
||||||
下载一个本子(album),包含其所有的章节(photo)
|
下载一个本子(album),包含其所有的章节(photo)
|
||||||
|
|
||||||
|
@ -6,8 +6,6 @@ Response Entity
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DictModel = AdvancedEasyAccessDict
|
|
||||||
|
|
||||||
|
|
||||||
class JmResp:
|
class JmResp:
|
||||||
|
|
||||||
@ -87,11 +85,11 @@ class JmJsonResp(JmResp):
|
|||||||
def json(self) -> Dict:
|
def json(self) -> Dict:
|
||||||
try:
|
try:
|
||||||
return self.resp.json()
|
return self.resp.json()
|
||||||
except Exception:
|
except Exception as e:
|
||||||
ExceptionTool.raises_resp('json解析失败', self, JsonResolveFailException)
|
ExceptionTool.raises_resp(f'json解析失败: {e}', self, JsonResolveFailException)
|
||||||
|
|
||||||
def model(self) -> DictModel:
|
def model(self) -> AdvancedEasyAccessDict:
|
||||||
return DictModel(self.json())
|
return AdvancedEasyAccessDict(self.json())
|
||||||
|
|
||||||
|
|
||||||
class JmApiResp(JmJsonResp):
|
class JmApiResp(JmJsonResp):
|
||||||
@ -120,9 +118,9 @@ class JmApiResp(JmJsonResp):
|
|||||||
return loads(self.decoded_data)
|
return loads(self.decoded_data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model_data(self) -> DictModel:
|
def model_data(self) -> AdvancedEasyAccessDict:
|
||||||
self.require_success()
|
self.require_success()
|
||||||
return DictModel(self.res_data)
|
return AdvancedEasyAccessDict(self.res_data)
|
||||||
|
|
||||||
|
|
||||||
# album-comment
|
# album-comment
|
||||||
|
@ -2,8 +2,8 @@ from common import time_stamp, str_to_list, field_cache, ProxyBuilder
|
|||||||
|
|
||||||
|
|
||||||
def default_jm_logging(topic: str, msg: str):
|
def default_jm_logging(topic: str, msg: str):
|
||||||
from common import format_ts
|
from common import format_ts, current_thread
|
||||||
print(f'{format_ts()}:【{topic}】{msg}')
|
print('[{}] [{}]:【{}】{}'.format(format_ts(), current_thread().name, topic, msg))
|
||||||
|
|
||||||
|
|
||||||
# 禁漫常量
|
# 禁漫常量
|
||||||
@ -24,17 +24,18 @@ class JmMagicConstants:
|
|||||||
TIME_MONTH = 'm'
|
TIME_MONTH = 'm'
|
||||||
TIME_ALL = 'a'
|
TIME_ALL = 'a'
|
||||||
|
|
||||||
# 全部, 同人, 单本, 短篇, 其他, 韩漫, 美漫, cosplay, 3D
|
# 分类参数API接口的category
|
||||||
# category = ["0", "doujin", "single", "short", "another", "hanman", "meiman", "doujin_cosplay", "3D"]
|
CATEGORY_ALL = '0' # 全部
|
||||||
CATEGORY_ALL = '0'
|
CATEGORY_DOUJIN = 'doujin' # 同人
|
||||||
CATEGORY_DOUJIN = 'doujin'
|
CATEGORY_SINGLE = 'single' # 单本
|
||||||
CATEGORY_SINGLE = 'single'
|
CATEGORY_SHORT = 'short' # 短篇
|
||||||
CATEGORY_SHORT = 'short'
|
CATEGORY_ANOTHER = 'another' # 其他
|
||||||
CATEGORY_ANOTHER = 'another'
|
CATEGORY_HANMAN = 'hanman' # 韩漫
|
||||||
CATEGORY_HANMAN = 'hanman'
|
CATEGORY_MEIMAN = 'meiman' # 美漫
|
||||||
CATEGORY_MEIMAN = 'meiman'
|
CATEGORY_DOUJIN_COSPLAY = 'doujin_cosplay' # cosplay
|
||||||
CATEGORY_DOUJIN_COSPLAY = 'doujin_cosplay'
|
CATEGORY_3D = '3D' # 3D
|
||||||
CATEGORY_3D = '3D'
|
CATEGORY_ENGLISH_SITE = 'english_site' # 英文站
|
||||||
|
CATEGORY_JM_TEAM = '禁漫漢化組'
|
||||||
|
|
||||||
# 分页大小
|
# 分页大小
|
||||||
PAGE_SIZE_SEARCH = 80
|
PAGE_SIZE_SEARCH = 80
|
||||||
@ -52,10 +53,10 @@ class JmMagicConstants:
|
|||||||
APP_TOKEN_SECRET = '18comicAPP'
|
APP_TOKEN_SECRET = '18comicAPP'
|
||||||
APP_TOKEN_SECRET_2 = '18comicAPPContent'
|
APP_TOKEN_SECRET_2 = '18comicAPPContent'
|
||||||
APP_DATA_SECRET = '185Hcomic3PAPP7R'
|
APP_DATA_SECRET = '185Hcomic3PAPP7R'
|
||||||
APP_VERSION = '1.6.6'
|
APP_VERSION = '1.6.7'
|
||||||
APP_HEADERS_TEMPLATE = {
|
APP_HEADERS_TEMPLATE = {
|
||||||
'Accept-Encoding': 'gzip',
|
'Accept-Encoding': 'gzip',
|
||||||
'user-agent': 'Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.09211555; wv) AppleWebKit/537.36 (KHTML, '
|
'user-agent': 'Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.11211812; wv) AppleWebKit/537.36 (KHTML, '
|
||||||
'like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36',
|
'like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,8 +50,10 @@ class JmDownloader(DownloadCallback):
|
|||||||
|
|
||||||
def __init__(self, option: JmOption) -> None:
|
def __init__(self, option: JmOption) -> None:
|
||||||
self.option = option
|
self.option = option
|
||||||
# 收集所有下载的image,为plugin提供数据
|
# 下载成功的记录dict
|
||||||
self.all_downloaded: Dict[JmAlbumDetail, Dict[JmPhotoDetail, List[Tuple[str, JmImageDetail]]]] = {}
|
self.download_success_dict: Dict[JmAlbumDetail, Dict[JmPhotoDetail, List[Tuple[str, JmImageDetail]]]] = {}
|
||||||
|
# 下载失败的记录list
|
||||||
|
self.download_failed_list: List[Tuple[JmImageDetail, BaseException]] = []
|
||||||
|
|
||||||
def download_album(self, album_id):
|
def download_album(self, album_id):
|
||||||
client = self.client_for_album(album_id)
|
client = self.client_for_album(album_id)
|
||||||
@ -101,11 +103,21 @@ class JmDownloader(DownloadCallback):
|
|||||||
if use_cache is True and image.is_exists:
|
if use_cache is True and image.is_exists:
|
||||||
return
|
return
|
||||||
|
|
||||||
client.download_by_image_detail(
|
e = None
|
||||||
image,
|
try:
|
||||||
img_save_path,
|
client.download_by_image_detail(
|
||||||
decode_image=decode_image,
|
image,
|
||||||
)
|
img_save_path,
|
||||||
|
decode_image=decode_image,
|
||||||
|
)
|
||||||
|
except BaseException as e:
|
||||||
|
jm_log('image.failed', f'图片下载失败: [{image.download_url}], 异常: {e}')
|
||||||
|
# 保存失败记录
|
||||||
|
self.download_failed_list.append((image, e))
|
||||||
|
|
||||||
|
if e is not None:
|
||||||
|
raise e
|
||||||
|
|
||||||
self.after_image(image, img_save_path)
|
self.after_image(image, img_save_path)
|
||||||
|
|
||||||
# noinspection PyMethodMayBeStatic
|
# noinspection PyMethodMayBeStatic
|
||||||
@ -164,11 +176,33 @@ class JmDownloader(DownloadCallback):
|
|||||||
"""
|
"""
|
||||||
return self.option.build_jm_client()
|
return self.option.build_jm_client()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_success(self) -> bool:
|
||||||
|
"""
|
||||||
|
是否成功下载了全部图片
|
||||||
|
|
||||||
|
该属性需要等到downloader的全部download_xxx方法完成后才有意义。
|
||||||
|
|
||||||
|
注意!如果使用了filter机制,例如通过filter只下载3张图片,那么all_success也会为False
|
||||||
|
"""
|
||||||
|
if len(self.download_failed_list) != 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for album, photo_dict in self.download_success_dict.items():
|
||||||
|
if len(album) != len(photo_dict):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for photo, image_list in photo_dict.items():
|
||||||
|
if len(photo) != len(image_list):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
# 下面是回调方法
|
# 下面是回调方法
|
||||||
|
|
||||||
def before_album(self, album: JmAlbumDetail):
|
def before_album(self, album: JmAlbumDetail):
|
||||||
super().before_album(album)
|
super().before_album(album)
|
||||||
self.all_downloaded.setdefault(album, {})
|
self.download_success_dict.setdefault(album, {})
|
||||||
self.option.call_all_plugin(
|
self.option.call_all_plugin(
|
||||||
'before_album',
|
'before_album',
|
||||||
album=album,
|
album=album,
|
||||||
@ -185,8 +219,8 @@ class JmDownloader(DownloadCallback):
|
|||||||
|
|
||||||
def before_photo(self, photo: JmPhotoDetail):
|
def before_photo(self, photo: JmPhotoDetail):
|
||||||
super().before_photo(photo)
|
super().before_photo(photo)
|
||||||
self.all_downloaded.setdefault(photo.from_album, {})
|
self.download_success_dict.setdefault(photo.from_album, {})
|
||||||
self.all_downloaded[photo.from_album].setdefault(photo, [])
|
self.download_success_dict[photo.from_album].setdefault(photo, [])
|
||||||
self.option.call_all_plugin(
|
self.option.call_all_plugin(
|
||||||
'before_photo',
|
'before_photo',
|
||||||
photo=photo,
|
photo=photo,
|
||||||
@ -214,7 +248,7 @@ class JmDownloader(DownloadCallback):
|
|||||||
photo = image.from_photo
|
photo = image.from_photo
|
||||||
album = photo.from_album
|
album = photo.from_album
|
||||||
|
|
||||||
self.all_downloaded.get(album).get(photo).append((img_save_path, image))
|
self.download_success_dict.get(album).get(photo).append((img_save_path, image))
|
||||||
self.option.call_all_plugin(
|
self.option.call_all_plugin(
|
||||||
'after_image',
|
'after_image',
|
||||||
image=image,
|
image=image,
|
||||||
|
@ -105,13 +105,11 @@ class DirRule:
|
|||||||
解析下载路径dsl,得到一个路径规则解析列表
|
解析下载路径dsl,得到一个路径规则解析列表
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if '_' not in rule_dsl and rule_dsl != 'Bd':
|
rule_list = self.split_rule_dsl(rule_dsl)
|
||||||
ExceptionTool.raises(f'不支持的dsl: "{rule_dsl}"')
|
|
||||||
|
|
||||||
rule_list = rule_dsl.split('_')
|
|
||||||
solver_ls: List[DirRule.RuleSolver] = []
|
solver_ls: List[DirRule.RuleSolver] = []
|
||||||
|
|
||||||
for rule in rule_list:
|
for rule in rule_list:
|
||||||
|
rule = rule.strip()
|
||||||
if rule == 'Bd':
|
if rule == 'Bd':
|
||||||
solver_ls.append((0, lambda _: base_dir, 'Bd'))
|
solver_ls.append((0, lambda _: base_dir, 'Bd'))
|
||||||
continue
|
continue
|
||||||
@ -124,6 +122,19 @@ class DirRule:
|
|||||||
|
|
||||||
return solver_ls
|
return solver_ls
|
||||||
|
|
||||||
|
# noinspection PyMethodMayBeStatic
|
||||||
|
def split_rule_dsl(self, rule_dsl: str) -> List[str]:
|
||||||
|
if rule_dsl == 'Bd':
|
||||||
|
return [rule_dsl]
|
||||||
|
|
||||||
|
if '/' in rule_dsl:
|
||||||
|
return rule_dsl.split('/')
|
||||||
|
|
||||||
|
if '_' in rule_dsl:
|
||||||
|
return rule_dsl.split('_')
|
||||||
|
|
||||||
|
ExceptionTool.raises(f'不支持的rule配置: "{rule_dsl}"')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_rule_solver(cls, rule: str) -> Optional[RuleSolver]:
|
def get_rule_solver(cls, rule: str) -> Optional[RuleSolver]:
|
||||||
# 查找缓存
|
# 查找缓存
|
||||||
@ -181,6 +192,7 @@ class JmOption:
|
|||||||
client: Dict,
|
client: Dict,
|
||||||
plugins: Dict,
|
plugins: Dict,
|
||||||
filepath=None,
|
filepath=None,
|
||||||
|
call_after_init_plugin=True,
|
||||||
):
|
):
|
||||||
# 路径规则配置
|
# 路径规则配置
|
||||||
self.dir_rule = DirRule(**dir_rule)
|
self.dir_rule = DirRule(**dir_rule)
|
||||||
@ -196,7 +208,21 @@ class JmOption:
|
|||||||
# 需要主线程等待完成的插件
|
# 需要主线程等待完成的插件
|
||||||
self.need_wait_plugins = []
|
self.need_wait_plugins = []
|
||||||
|
|
||||||
self.call_all_plugin('after_init', safe=True)
|
if call_after_init_plugin:
|
||||||
|
self.call_all_plugin('after_init', safe=True)
|
||||||
|
|
||||||
|
def copy_option(self):
|
||||||
|
return self.__class__(
|
||||||
|
dir_rule={
|
||||||
|
'rule': self.dir_rule.rule_dsl,
|
||||||
|
'base_dir': self.dir_rule.base_dir,
|
||||||
|
},
|
||||||
|
download=self.download.src_dict,
|
||||||
|
client=self.client.src_dict,
|
||||||
|
plugins=self.plugins.src_dict,
|
||||||
|
filepath=self.filepath,
|
||||||
|
call_after_init_plugin=False
|
||||||
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
下面是decide系列方法,为了支持重写和增加程序动态性。
|
下面是decide系列方法,为了支持重写和增加程序动态性。
|
||||||
|
@ -298,7 +298,7 @@ class ZipPlugin(JmOptionPlugin):
|
|||||||
|
|
||||||
# 原文件夹 -> zip文件
|
# 原文件夹 -> zip文件
|
||||||
dir_zip_dict: Dict[str, Optional[str]] = {}
|
dir_zip_dict: Dict[str, Optional[str]] = {}
|
||||||
photo_dict = downloader.all_downloaded[album]
|
photo_dict = downloader.download_success_dict[album]
|
||||||
|
|
||||||
if level == 'album':
|
if level == 'album':
|
||||||
zip_path = self.get_zip_path(album, None, filename_rule, suffix, zip_dir)
|
zip_path = self.get_zip_path(album, None, filename_rule, suffix, zip_dir)
|
||||||
@ -383,7 +383,7 @@ class ZipPlugin(JmOptionPlugin):
|
|||||||
dirs = sorted(dir_zip_dict.keys(), reverse=True)
|
dirs = sorted(dir_zip_dict.keys(), reverse=True)
|
||||||
image_paths = [
|
image_paths = [
|
||||||
path
|
path
|
||||||
for photo_dict in self.downloader.all_downloaded.values()
|
for photo_dict in self.downloader.download_success_dict.values()
|
||||||
for image_list in photo_dict.values()
|
for image_list in photo_dict.values()
|
||||||
for path, image in image_list
|
for path, image in image_list
|
||||||
]
|
]
|
||||||
@ -752,7 +752,7 @@ class ConvertJpgToPdfPlugin(JmOptionPlugin):
|
|||||||
|
|
||||||
paths = [
|
paths = [
|
||||||
path
|
path
|
||||||
for path, image in downloader.all_downloaded[photo.from_album][photo]
|
for path, image in downloader.download_success_dict[photo.from_album][photo]
|
||||||
]
|
]
|
||||||
|
|
||||||
paths.append(self.option.decide_image_save_dir(photo, ensure_exists=False))
|
paths.append(self.option.decide_image_save_dir(photo, ensure_exists=False))
|
||||||
|
@ -472,7 +472,7 @@ class JmPageTool:
|
|||||||
return JmFavoritePage(content, folder_list, total)
|
return JmFavoritePage(content, folder_list, total)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_api_to_search_page(cls, data: DictModel) -> JmSearchPage:
|
def parse_api_to_search_page(cls, data: AdvancedEasyAccessDict) -> JmSearchPage:
|
||||||
"""
|
"""
|
||||||
model_data: {
|
model_data: {
|
||||||
"search_query": "MANA",
|
"search_query": "MANA",
|
||||||
@ -501,7 +501,7 @@ class JmPageTool:
|
|||||||
return JmSearchPage(content, total)
|
return JmSearchPage(content, total)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_api_to_favorite_page(cls, data: DictModel) -> JmFavoritePage:
|
def parse_api_to_favorite_page(cls, data: AdvancedEasyAccessDict) -> JmFavoritePage:
|
||||||
"""
|
"""
|
||||||
{
|
{
|
||||||
"list": [
|
"list": [
|
||||||
@ -546,7 +546,7 @@ class JmPageTool:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def adapt_content(cls, content):
|
def adapt_content(cls, content):
|
||||||
def adapt_item(item: DictModel):
|
def adapt_item(item: AdvancedEasyAccessDict):
|
||||||
item: dict = item.src_dict
|
item: dict = item.src_dict
|
||||||
item.setdefault('tags', [])
|
item.setdefault('tags', [])
|
||||||
return item
|
return item
|
||||||
@ -673,7 +673,7 @@ class JmApiAdaptTool:
|
|||||||
series = data['series']
|
series = data['series']
|
||||||
episode_list = []
|
episode_list = []
|
||||||
for chapter in series:
|
for chapter in series:
|
||||||
chapter = DictModel(chapter)
|
chapter = AdvancedEasyAccessDict(chapter)
|
||||||
# photo_id, photo_index, photo_title, photo_pub_date
|
# photo_id, photo_index, photo_title, photo_pub_date
|
||||||
episode_list.append(
|
episode_list.append(
|
||||||
(chapter.id, chapter.sort, chapter.name, None)
|
(chapter.id, chapter.sort, chapter.name, None)
|
||||||
@ -688,7 +688,7 @@ class JmApiAdaptTool:
|
|||||||
sort = 1
|
sort = 1
|
||||||
series: list = data['series'] # series中的sort从1开始
|
series: list = data['series'] # series中的sort从1开始
|
||||||
for chapter in series:
|
for chapter in series:
|
||||||
chapter = DictModel(chapter)
|
chapter = AdvancedEasyAccessDict(chapter)
|
||||||
if int(chapter.id) == int(data['id']):
|
if int(chapter.id) == int(data['id']):
|
||||||
sort = chapter.sort
|
sort = chapter.sort
|
||||||
break
|
break
|
||||||
|
Loading…
Reference in New Issue
Block a user