Skip to content

Commit 929dd46

Browse files
authored
v2.1.7: 修复永久网域的重定向bug,重构api提高可扩展性 (#85)
1 parent 5e013e1 commit 929dd46

File tree

7 files changed

+226
-155
lines changed

7 files changed

+226
-155
lines changed

src/jmcomic/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# 模块依赖关系如下:
22
# 被依赖方 <--- 使用方
3-
# config <--- entity <--- toolkit <--- client <--- option
3+
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
44

5-
__version__ = '2.1.6'
5+
__version__ = '2.1.7'
66

7+
# noinspection PyUnresolvedReferences
8+
from common import *
79
from .api import *

src/jmcomic/api.py

Lines changed: 33 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,56 @@
1-
from .jm_option import *
2-
3-
4-
def download_album(jm_album_id, option=None):
5-
"""
6-
下载一个本子集,入口api
7-
@param jm_album_id: 禁漫的本子的id,类型可以是str/int/iterable[str]。
8-
如果是iterable[str],则会调用批量下载方法 download_album_batch
9-
@param option: 下载选项,为空默认是 JmOption.default()
10-
"""
11-
12-
if not isinstance(jm_album_id, (str, int)):
13-
return download_album_batch(jm_album_id, option)
14-
15-
option, jm_client = build_client(option)
16-
album: JmAlbumDetail = jm_client.get_album_detail(jm_album_id)
17-
18-
option.before_album(album)
19-
execute_by_condition(
20-
iter_objs=album,
21-
apply=lambda photo: download_by_photo_detail(photo, option),
22-
count_batch=option.decide_photo_batch_count(album)
23-
)
24-
option.after_album(album)
25-
26-
27-
def download_photo(jm_photo_id, option=None):
28-
"""
29-
下载一个本子的一章,入口api
30-
"""
31-
option, jm_client = build_client(option)
32-
photo = jm_client.get_photo_detail(jm_photo_id)
33-
download_by_photo_detail(photo, option)
34-
35-
36-
def download_by_photo_detail(photo: JmPhotoDetail, option=None):
37-
"""
38-
下载一个本子的一章,根据 photo
39-
@param photo: 本子章节信息
40-
@param option: 选项
41-
"""
42-
option, jm_client = build_client(option)
43-
44-
# 下载准备
45-
use_cache = option.download_cache
46-
decode_image = option.download_image_decode
47-
jm_client.check_photo(photo)
48-
49-
# 下载每个图片的函数
50-
def download_image(image: JmImageDetail):
51-
img_save_path = option.decide_image_filepath(image)
52-
image.is_exists = file_exists(img_save_path)
53-
54-
option.before_image(image, img_save_path)
55-
if use_cache is True and image.is_exists:
56-
return
57-
jm_client.download_by_image_detail(
58-
image,
59-
img_save_path,
60-
decode_image=decode_image,
61-
)
62-
option.after_image(image, img_save_path)
63-
64-
option.before_photo(photo)
65-
execute_by_condition(
66-
iter_objs=photo,
67-
apply=download_image,
68-
count_batch=option.decide_image_batch_count(photo)
69-
)
70-
option.before_photo(photo)
1+
from .jm_downloader import *
712

723

734
def download_album_batch(jm_album_id_iter: Union[Iterable, Generator],
745
option=None,
75-
wait_finish=True,
76-
) -> List[Thread]:
6+
):
777
"""
78-
批量下载album,每个album一个线程,使用的是同一个option。
8+
批量下载album.
9+
一个album,对应一个线程,对应一个option
7910
80-
@param jm_album_id_iter: album_id的可迭代对象
11+
@param jm_album_id_iter: album_id的迭代器
8112
@param option: 下载选项,为空默认是 JmOption.default()
82-
@param wait_finish: 是否要等待这些下载线程全部完成
83-
@return 返回值是List[Thread],里面是每个下载漫画的线程。
8413
"""
85-
if option is None:
86-
option = JmOption.default()
14+
from common import multi_thread_launcher
8715

88-
return thread_pool_executor(
89-
iter_objs=set(JmcomicText.parse_to_album_id(album_id) for album_id in jm_album_id_iter),
90-
apply_each_obj_func=lambda album_id: download_album(album_id, option),
91-
wait_finish=wait_finish,
16+
return multi_thread_launcher(
17+
iter_objs=set(
18+
JmcomicText.parse_to_album_id(album_id)
19+
for album_id in jm_album_id_iter
20+
),
21+
apply_each_obj_func=lambda aid: download_album(aid, option),
9222
)
9323

9424

95-
def execute_by_condition(iter_objs, apply: Callable, count_batch: int):
25+
def download_album(jm_album_id, option=None):
9626
"""
97-
章节/图片的下载调度逻辑
27+
下载一个本子
28+
@param jm_album_id: 禁漫的本子的id,类型可以是str/int/iterable[str]。
29+
如果是iterable[str],则会调用 download_album_batch
30+
@param option: 下载选项,为空默认是 JmOption.default()
9831
"""
99-
count_real = len(iter_objs)
10032

101-
if count_batch >= count_real:
102-
# 一个图/章节 对应 一个线程
103-
multi_thread_launcher(
104-
iter_objs=iter_objs,
105-
apply_each_obj_func=apply,
106-
)
107-
else:
108-
# 创建batch个线程的线程池
109-
thread_pool_executor(
110-
iter_objs=iter_objs,
111-
apply_each_obj_func=apply,
112-
max_workers=count_batch,
113-
)
33+
if not isinstance(jm_album_id, (str, int)):
34+
return download_album_batch(jm_album_id, option)
11435

36+
with new_downloader(option) as dler:
37+
dler.download_album(jm_album_id)
11538

116-
def build_client(option: Optional[JmOption]) -> Tuple[JmOption, JmcomicClient]:
39+
40+
def download_photo(jm_photo_id, option=None):
11741
"""
118-
处理option的判空,并且创建jm_client
42+
下载一个章节
11943
"""
44+
with new_downloader(option) as dler:
45+
dler.download_photo(jm_photo_id)
46+
47+
48+
def new_downloader(option=None):
12049
if option is None:
121-
option = JmOption.default()
50+
option = JmModuleConfig.option_class().default()
12251

123-
jm_client = option.build_jm_client()
124-
return option, jm_client
52+
return JmModuleConfig.downloader_class()(option)
12553

12654

127-
def create_option(filepath: str) -> JmOption:
128-
option = JmOption.from_file(filepath)
129-
return option
55+
def create_option(filepath):
56+
return JmModuleConfig.option_class().from_file(filepath)

src/jmcomic/jm_client_impl.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def request_with_retry(self,
8585

8686
# noinspection PyMethodMayBeStatic, PyUnusedLocal
8787
def before_retry(self, e, kwargs, retry_count, url):
88-
jm_debug('error', str(e))
88+
jm_debug('retry', str(e))
8989

9090
def enable_cache(self, debug=False):
9191
def wrap_func_cache(func_name, cache_dict_name):
@@ -131,8 +131,11 @@ def get_jmcomic_url(self, postman=None):
131131
def get_jmcomic_domain_all(self, postman=None):
132132
return JmModuleConfig.get_jmcomic_domain_all(postman or self.get_root_postman())
133133

134+
# noinspection PyUnusedLocal
134135
def fallback(self, request, url, domain_index, retry_count, **kwargs):
135-
raise AssertionError(f"请求重试全部失败: [{url}], {self.domain_list}")
136+
msg = f"请求重试全部失败: [{url}], {self.domain_list}"
137+
jm_debug('fallback', "msg")
138+
raise AssertionError(msg)
136139

137140

138141
# 基于网页实现的JmClient
@@ -406,3 +409,15 @@ def headers_key_ts(self):
406409
"user-agent": "okhttp/3.12.1",
407410
"accept-encoding": "gzip",
408411
}, key_ts
412+
413+
414+
class AsyncSaveImageClient(JmImageClient):
415+
416+
def __init__(self, workers=None) -> None:
417+
from concurrent.futures import ThreadPoolExecutor, Future
418+
self.executor = ThreadPoolExecutor(max_workers=workers)
419+
self.future_list: List[Future] = []
420+
421+
def save_image_resp(self, *args, **kwargs):
422+
future = self.executor.submit(lambda: super().save_image_resp(*args, **kwargs))
423+
self.future_list.append(future)

src/jmcomic/jm_client_interface.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,10 @@ def download_image(self,
236236

237237
resp.require_success()
238238

239-
# gif图无需加解密,需要最先判断
239+
return self.save_image_resp(decode_image, img_save_path, img_url, resp, scramble_id)
240240

241+
def save_image_resp(self, decode_image, img_save_path, img_url, resp, scramble_id):
242+
# gif图无需加解密,需要最先判断
241243
if self.img_is_not_need_to_decode(img_url, resp):
242244
JmImageSupport.save_resp_img(resp, img_save_path, False)
243245
else:
@@ -248,7 +250,7 @@ def download_by_image_detail(self,
248250
img_save_path,
249251
decode_image=True,
250252
):
251-
self.download_image(
253+
return self.download_image(
252254
image.download_url,
253255
img_save_path,
254256
image.scramble_id,

src/jmcomic/jm_config.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ class JmModuleConfig:
5757
enable_jm_debug = True
5858
debug_executor = default_jm_debug
5959
postman_constructor = default_postman_constructor
60+
DOWNLOADER_CLASS = None
61+
OPTION_CLASS = None
62+
63+
@classmethod
64+
def downloader_class(cls):
65+
if cls.DOWNLOADER_CLASS is not None:
66+
return cls.DOWNLOADER_CLASS
67+
68+
from .jm_downloader import JmDownloader
69+
return JmDownloader
70+
71+
@classmethod
72+
def option_class(cls):
73+
if cls.OPTION_CLASS is not None:
74+
return cls.OPTION_CLASS
75+
76+
from .jm_option import JmOption
77+
return JmOption
6078

6179
@classmethod
6280
@field_cache("DOMAIN")
@@ -115,8 +133,7 @@ def get_jmcomic_url(cls, postman=None):
115133
"""
116134
postman = postman or cls.new_postman(session=True)
117135

118-
resp = postman.get(cls.JM_REDIRECT_URL)
119-
url = resp.url
136+
url = postman.with_redirect_catching().get(cls.JM_REDIRECT_URL)
120137
cls.jm_debug('获取禁漫地址', f'[{cls.JM_REDIRECT_URL}] → [{url}]')
121138
return url
122139

0 commit comments

Comments
 (0)