Skip to content

Commit 58f1b8b

Browse files
authored
v2.1.0: 优化请求重试机制 (#74)
1 parent 9ab4de6 commit 58f1b8b

File tree

5 files changed

+151
-24
lines changed

5 files changed

+151
-24
lines changed

src/jmcomic/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
# 被依赖方 <--- 使用方
33
# config <--- entity <--- toolkit <--- client <--- option
44

5-
__version__ = '2.0.9'
5+
__version__ = '2.1.0'
66

77
from .api import *

src/jmcomic/jm_client_impl.py

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .jm_client_interface import *
22

33

4+
# noinspection PyAbstractClass
45
class AbstractJmClient(
56
JmcomicClient,
67
PostmanProxy,
@@ -39,21 +40,32 @@ def request_with_retry(self,
3940
retry_count=0,
4041
**kwargs,
4142
):
43+
"""
44+
统一请求,支持重试
45+
@param request: 请求方法
46+
@param url: 图片url / path (/album/xxx)
47+
@param domain_index: 域名下标
48+
@param retry_count: 重试次数
49+
@param kwargs: 请求方法的kwargs
50+
"""
4251
if domain_index >= len(self.domain_list):
43-
raise AssertionError("All domains failed.")
44-
45-
domain = self.domain_list[domain_index]
52+
raise AssertionError(f"请求重试全部失败: [{url}], {self.domain_list}")
4653

47-
if not url.startswith(JmModuleConfig.PROT):
54+
if url.startswith('/'):
55+
# path
56+
domain = self.domain_list[domain_index]
4857
url = self.of_api_url(url, domain)
4958
jm_debug('api', url)
59+
else:
60+
# 图片url
61+
pass
5062

51-
if domain_index != 0 and retry_count != 0:
63+
if domain_index != 0 or retry_count != 0:
5264
jm_debug(
53-
f'请求重试',
65+
f'request_retry',
5466
', '.join([
5567
f'次数: [{retry_count}/{self.retry_times}]',
56-
f'域名: [{domain} ({domain_index}/{len(self.domain_list)})]',
68+
f'域名: [{domain_index} of {self.domain_list}]',
5769
f'路径: [{url}]',
5870
f'参数: [{kwargs if "login" not in url else "#login_form#"}]'
5971
])
@@ -64,10 +76,11 @@ def request_with_retry(self,
6476
except Exception as e:
6577
self.before_retry(e, kwargs, retry_count, url)
6678

67-
if retry_count < self.retry_times:
68-
return self.request_with_retry(request, url, domain_index, retry_count + 1, **kwargs)
69-
else:
70-
return self.request_with_retry(request, url, domain_index + 1, 0, **kwargs)
79+
if retry_count < self.retry_times:
80+
return self.request_with_retry(request, url, domain_index, retry_count + 1, **kwargs)
81+
else:
82+
return self.request_with_retry(request, url, domain_index + 1, 0, **kwargs)
83+
7184

7285
# noinspection PyMethodMayBeStatic, PyUnusedLocal
7386
def before_retry(self, e, kwargs, retry_count, url):
@@ -237,7 +250,60 @@ def raise_request_error(cls, resp, msg: Optional[str] = None):
237250
raise AssertionError(msg)
238251

239252
def get_jm_image(self, img_url) -> JmImageResp:
240-
return JmImageResp(self.get(img_url))
253+
254+
def get_if_fail_raise(url):
255+
"""
256+
使用此方法包装 self.get
257+
"""
258+
resp = JmImageResp(self.get(url))
259+
260+
if resp.is_success:
261+
return resp
262+
263+
self.raise_request_error(
264+
resp.resp, resp.get_error_msg()
265+
)
266+
267+
return resp
268+
269+
return self.request_with_retry(get_if_fail_raise, img_url)
270+
271+
def album_comment(self,
272+
video_id,
273+
comment,
274+
originator='',
275+
status='true',
276+
comment_id=None,
277+
**kwargs,
278+
) -> JmAcResp:
279+
data = {
280+
'video_id': video_id,
281+
'comment': comment,
282+
'originator': originator,
283+
'status': status,
284+
}
285+
286+
# 处理回复评论
287+
if comment_id is not None:
288+
data.pop('status')
289+
data['comment_id'] = comment_id
290+
data['is_reply'] = 1
291+
data['forum_subject'] = 1
292+
293+
jm_debug('album_comment',
294+
f'{video_id}: [{comment}]' +
295+
(f' to ({comment_id})' if comment_id is not None else '')
296+
)
297+
298+
resp = self.post('https://18comic.vip/ajax/album_comment',
299+
headers=JmModuleConfig.album_comment_headers,
300+
data=data,
301+
)
302+
303+
ret = JmAcResp(resp)
304+
jm_debug('album_comment', f'{video_id}: [{comment}] ← ({ret.model().cid})')
305+
306+
return ret
241307

242308
@classmethod
243309
def require_resp_success_else_raise(cls, resp, req_url):

src/jmcomic/jm_client_interface.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,31 @@ class JmResp(CommonResp):
1313
def is_success(self) -> bool:
1414
return self.http_code == 200 and len(self.content) != 0
1515

16+
def json(self, **kwargs) -> Dict:
17+
raise NotImplementedError
18+
19+
def model(self) -> DictModel:
20+
return DictModel(self.json())
21+
1622

1723
class JmImageResp(JmResp):
1824

25+
def json(self, **kwargs) -> Dict:
26+
raise AssertionError
27+
1928
def require_success(self):
2029
if self.is_success:
2130
return
2231

32+
raise AssertionError(self.get_error_msg())
33+
34+
def get_error_msg(self):
2335
msg = f'禁漫图片获取失败: [{self.url}]'
2436
if self.http_code != 200:
2537
msg += f',http状态码={self.http_code}'
2638
if len(self.content) == 0:
2739
msg += f',响应数据为空'
28-
29-
raise AssertionError(msg)
40+
return msg
3041

3142
def transfer_to(self,
3243
path,
@@ -106,15 +117,25 @@ def res_data(self) -> Any:
106117
def json(self, **kwargs) -> Dict:
107118
return self.resp.json()
108119

109-
def model(self) -> DictModel:
110-
return DictModel(self.json())
111-
112120
@property
113121
def model_data(self) -> DictModel:
114122
self.require_success()
115123
return DictModel(self.res_data)
116124

117125

126+
# album-comment
127+
class JmAcResp(JmResp):
128+
129+
def is_success(self) -> bool:
130+
return super().is_success and self.json()['err'] is False
131+
132+
def json(self, **kwargs) -> Dict:
133+
return self.resp.json()
134+
135+
def model(self) -> DictModel:
136+
return DictModel(self.json())
137+
138+
118139
"""
119140
120141
Client Interface
@@ -154,6 +175,25 @@ def login(self,
154175
):
155176
raise NotImplementedError
156177

178+
def album_comment(self,
179+
video_id,
180+
comment,
181+
originator='',
182+
status='true',
183+
comment_id=None,
184+
**kwargs,
185+
) -> JmAcResp:
186+
"""
187+
评论漫画/评论回复
188+
@param video_id: album_id/photo_id
189+
@param comment: 评论内容
190+
@param status: 是否 "有劇透"
191+
@param comment_id: 被回复评论的id
192+
@param originator:
193+
@return: JmAcResp 对象
194+
"""
195+
raise NotImplementedError
196+
157197

158198
class JmImageClient:
159199

@@ -204,6 +244,7 @@ def img_is_not_need_to_decode(cls, data_original: str, _resp):
204244
return data_original.endswith('.gif')
205245

206246

247+
# noinspection PyAbstractClass
207248
class JmcomicClient(
208249
JmImageClient,
209250
JmDetailClient,

src/jmcomic/jm_config.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ def domain(cls, postman=None):
6565
return cls.DOMAIN # jmcomic默认域名
6666

6767
@classmethod
68-
def headers(cls, authority=None):
68+
def headers(cls, domain='18comic.vip'):
6969
return {
70-
'authority': authority or '18comic.vip',
70+
'authority': domain,
7171
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
7272
'application/signed-exchange;v=b3;q=0.7',
7373
'accept-language': 'zh-CN,zh;q=0.9',
7474
'cache-control': 'no-cache',
75-
'referer': 'https://18comic.vip',
75+
'referer': f'https://{domain}',
7676
'pragma': 'no-cache',
7777
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
7878
'sec-ch-ua-mobile': '?0',
@@ -130,6 +130,26 @@ def get_jmcomic_domain_all(cls, postman=None):
130130
from .jm_toolkit import JmcomicText
131131
return JmcomicText.analyse_jm_pub_html(resp.text)
132132

133+
album_comment_headers = {
134+
'authority': '18comic.vip',
135+
'accept': 'application/json, text/javascript, */*; q=0.01',
136+
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
137+
'cache-control': 'no-cache',
138+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
139+
'origin': 'https://18comic.vip',
140+
'pragma': 'no-cache',
141+
'referer': 'https://18comic.vip/album/248965/',
142+
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
143+
'sec-ch-ua-mobile': '?0',
144+
'sec-ch-ua-platform': '"Windows"',
145+
'sec-fetch-dest': 'empty',
146+
'sec-fetch-mode': 'cors',
147+
'sec-fetch-site': 'same-origin',
148+
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
149+
'Chrome/114.0.0.0 Safari/537.36',
150+
'x-requested-with': 'XMLHttpRequest',
151+
}
152+
133153

134154
jm_debug = JmModuleConfig.jm_debug
135155
disable_jm_debug = JmModuleConfig.disable_jm_debug

tests/test_jmcomic/test_jm_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def setUpClass(cls):
1010

1111
def test_download_image(self):
1212
jm_photo_id = 'JM438516'
13-
photo_detail = self.client.get_photo_detail(jm_photo_id)
13+
photo_detail = self.client.get_photo_detail(jm_photo_id, False)
1414
self.client.download_by_image_detail(
1515
photo_detail[0],
1616
img_save_path=workspace('test_download_image.png')
@@ -25,7 +25,7 @@ def test_get_photo_detail_by_jm_photo_id(self):
2525
测试通过 JmcomicClient 和 jm_photo_id 获取 JmPhotoDetail对象
2626
"""
2727
jm_photo_id = 'JM438516'
28-
photo_detail = self.client.get_photo_detail(jm_photo_id)
28+
photo_detail = self.client.get_photo_detail(jm_photo_id, False)
2929
photo_detail.when_del_save_file = True
3030
photo_detail.after_save_print_info = True
3131
del photo_detail
@@ -46,7 +46,7 @@ def test_search(self):
4646

4747
def test_gt_300_photo(self):
4848
photo_id = '147643'
49-
photo_detail: JmPhotoDetail = self.client.get_photo_detail(photo_id)
49+
photo_detail: JmPhotoDetail = self.client.get_photo_detail(photo_id, False)
5050
image = photo_detail[3000]
5151
print(image.img_url)
5252
self.client.download_by_image_detail(image, workspace('3000.png'))

0 commit comments

Comments
 (0)