mirror of
https://github.com/hect0x7/JMComic-Crawler-Python.git
synced 2025-09-26 22:31:30 +08:00
parent
fc2f2a908d
commit
14050d4657
@ -1,7 +1,8 @@
|
||||
# Plan For Update Content
|
||||
|
||||
| 版本范围 | 更新内容 |
|
||||
| 版本范围 | 更新计划 |
|
||||
|:--------:|:--------------------------------------:|
|
||||
| v2.5.* | 引入新插件`jm-server`,实现基于浏览器观看本地本子。 |
|
||||
| v2.4.* | 项目实现基本稳定,进入维护期,按需增加功能。 |
|
||||
| v2.3.* | 实现移动端API的基础功能,统一HTML和API的实现。 |
|
||||
| v2.2.* | 新的插件体系,新的命令行调用,完善搜索功能。 |
|
||||
|
@ -5,7 +5,6 @@
|
||||
* option有`默认值`,当你使用配置文件来创建option时,你配置文件中的值会覆盖`默认值`。
|
||||
|
||||
因此,在配置option时,不需要配置全部的值,只需要配置特定部分即可。
|
||||
|
||||
* 你可以使用下面的代码来得到option的默认值,你可以删除其中的大部分配置项,只保留你要覆盖的配置项
|
||||
|
||||
```python
|
||||
@ -13,12 +12,15 @@ from jmcomic import JmOption
|
||||
JmOption.default().to_file('./option.yml') # 创建默认option,导出为option.yml文件
|
||||
```
|
||||
|
||||
## 2. option常用配置项
|
||||
## 2. option常规配置项
|
||||
|
||||
```yml
|
||||
# 配置客户端相关
|
||||
client:
|
||||
# impl: 客户端实现类,不配默认是html,表示网页端
|
||||
# impl: 客户端实现类,不配置默认会使用JmModuleConfig.DEFAULT_CLIENT_IMPL
|
||||
# 可配置:
|
||||
# html - 表示网页端
|
||||
# api - 表示使用APP端
|
||||
impl: html
|
||||
|
||||
# domain: 域名配置,默认是 [],表示运行时自动获取域名。
|
||||
@ -89,8 +91,8 @@ dir_rule:
|
||||
rule: Bd_Ptitle
|
||||
```
|
||||
|
||||
|
||||
## 3. option插件配置项
|
||||
|
||||
```yml
|
||||
# 插件的配置示例
|
||||
# 当kwargs的值为字符串类型时,支持使用环境变量,语法为 ${环境变量名}
|
||||
@ -109,11 +111,11 @@ plugins:
|
||||
- plugin: find_update # 只下载新章插件
|
||||
kwargs:
|
||||
145504: 290266 # 下载本子145504的章节290266以后的新章
|
||||
|
||||
|
||||
- plugin: image_suffix_filter # 图片后缀过滤器插件,可以控制只下载哪些后缀的图片
|
||||
kwargs:
|
||||
allowed_orig_suffix: # 后缀列表,表示只想下载以.gif结尾的图片
|
||||
- .gif
|
||||
- .gif
|
||||
|
||||
- plugin: client_proxy # 客户端实现类代理插件,不建议非开发人员使用
|
||||
kwargs:
|
||||
@ -124,6 +126,28 @@ plugins:
|
||||
kwargs:
|
||||
browser: chrome
|
||||
domain: 18comic.vip
|
||||
|
||||
# v2.5.0 引入的插件
|
||||
# 可以启动一个服务器,可以在浏览器上查看本子
|
||||
# 基于flask框架,需要安装额外库: pip install plugin_jm_server
|
||||
# 源码:https://github.com/hect0x7/plugin-jm-server
|
||||
- plugin: jm_server
|
||||
kwargs:
|
||||
password: '3333' # 服务器访问密码
|
||||
base_dir: D:/a/b/c/ # 根目录,默认使用dir_rule.base_dir
|
||||
|
||||
# 下面是高级配置,不配置也可以
|
||||
|
||||
# run下的参数是flask框架的app对象的run方法参数,详见flask文档
|
||||
run:
|
||||
host: 0.0.0.0 # 默认接收所有ip的请求
|
||||
port: 80 # 服务器端口,默认为80
|
||||
debug: false # 是否开启debug模式,默认为false
|
||||
|
||||
# 支持重写背景图片,可以使用你喜欢的背景图片作为背景
|
||||
img_overwrite:
|
||||
bg.jpg: D:/浏览器的背景图
|
||||
m_bg.jpeg: D:/移动设备浏览器的背景图
|
||||
|
||||
after_album:
|
||||
- plugin: zip # 压缩文件插件
|
||||
@ -156,4 +180,4 @@ plugins:
|
||||
filename_rule: Pid # pdf命名规则
|
||||
quality: 100 # pdf质量,0 - 100
|
||||
|
||||
```
|
||||
```
|
||||
|
@ -4,4 +4,5 @@ PyYAML
|
||||
Pillow
|
||||
psutil
|
||||
pycryptodome
|
||||
requests
|
||||
requests
|
||||
plugin_jm_server
|
1
setup.py
1
setup.py
@ -42,6 +42,7 @@ setup(
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
|
@ -2,7 +2,7 @@
|
||||
# 被依赖方 <--- 使用方
|
||||
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
|
||||
|
||||
__version__ = '2.4.14'
|
||||
__version__ = '2.5.0'
|
||||
|
||||
from .api import *
|
||||
from .jm_plugin import *
|
||||
|
@ -187,6 +187,9 @@ class JmOption:
|
||||
# 其他配置
|
||||
self.filepath = filepath
|
||||
|
||||
# 需要主线程等待完成的插件
|
||||
self.need_wait_plugins = []
|
||||
|
||||
self.call_all_plugin('after_init', safe=True)
|
||||
|
||||
"""
|
||||
@ -630,3 +633,9 @@ class JmOption:
|
||||
)
|
||||
|
||||
return new_kwargs
|
||||
|
||||
def wait_all_plugins_finish(self):
|
||||
from .jm_plugin import JmOptionPlugin
|
||||
for plugin in self.need_wait_plugins:
|
||||
plugin: JmOptionPlugin
|
||||
plugin.wait_until_finish()
|
||||
|
@ -94,6 +94,15 @@ class JmOptionPlugin:
|
||||
import subprocess
|
||||
subprocess.run(cmd, shell=True, check=True)
|
||||
|
||||
def enter_wait_list(self):
|
||||
self.option.need_wait_plugins.append(self)
|
||||
|
||||
def leave_wait_list(self):
|
||||
self.option.need_wait_plugins.remove(self)
|
||||
|
||||
def wait_until_finish(self):
|
||||
pass
|
||||
|
||||
|
||||
class JmLoginPlugin(JmOptionPlugin):
|
||||
"""
|
||||
@ -407,7 +416,7 @@ class ClientProxyPlugin(JmOptionPlugin):
|
||||
|
||||
proxy_clazz = JmModuleConfig.client_impl_class(proxy_client_key)
|
||||
clazz_init_kwargs = kwargs
|
||||
new_jm_client = self.option.new_jm_client
|
||||
new_jm_client: Callable = self.option.new_jm_client
|
||||
|
||||
def hook_new_jm_client(*args, **kwargs):
|
||||
client = new_jm_client(*args, **kwargs)
|
||||
@ -749,3 +758,149 @@ class ConvertJpgToPdfPlugin(JmOptionPlugin):
|
||||
|
||||
paths.append(self.option.decide_image_save_dir(photo, ensure_exists=False))
|
||||
self.execute_deletion(paths)
|
||||
|
||||
|
||||
class JmServerPlugin(JmOptionPlugin):
|
||||
plugin_key = 'jm_server'
|
||||
|
||||
default_run_kwargs = {
|
||||
'host': '0.0.0.0',
|
||||
'port': '80',
|
||||
'debug': False,
|
||||
}
|
||||
|
||||
from threading import Lock
|
||||
single_instance_lock = Lock()
|
||||
|
||||
def __init__(self, option: JmOption):
|
||||
super().__init__(option)
|
||||
self.run_server_lock = Lock()
|
||||
self.running = False
|
||||
self.server_thread: Optional[Thread] = None
|
||||
|
||||
def invoke(self,
|
||||
password='',
|
||||
base_dir=None,
|
||||
album=None,
|
||||
photo=None,
|
||||
downloader=None,
|
||||
run=None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
|
||||
:param password: 密码
|
||||
:param base_dir: 初始访问服务器的根路径
|
||||
:param album: 为了支持 after_album 这种调用时机
|
||||
:param photo: 为了支持 after_album 这种调用时机
|
||||
:param downloader: 为了支持 after_album 这种调用时机
|
||||
:param run: 用于启动服务器: app.run(**run_kwargs)
|
||||
:param kwargs: 用于JmServer构造函数: JmServer(base_dir, password, **kwargs)
|
||||
"""
|
||||
|
||||
if base_dir is None:
|
||||
base_dir = self.option.dir_rule.base_dir
|
||||
|
||||
if run is None:
|
||||
run = self.default_run_kwargs
|
||||
else:
|
||||
base_run_kwargs = self.default_run_kwargs.copy()
|
||||
base_run_kwargs.update(run)
|
||||
run = base_run_kwargs
|
||||
|
||||
if self.running is True:
|
||||
return
|
||||
|
||||
with self.run_server_lock:
|
||||
if self.running is True:
|
||||
return
|
||||
|
||||
self.running = True
|
||||
|
||||
# 服务器的代码位于一个独立库:plugin_jm_server,需要独立安装
|
||||
# 源代码仓库:https://github.com/hect0x7/plugin-jm-server
|
||||
try:
|
||||
import plugin_jm_server
|
||||
self.log(f'当前使用plugin_jm_server版本: {plugin_jm_server.__version__}')
|
||||
except ImportError:
|
||||
self.warning_lib_not_install('plugin_jm_server')
|
||||
return
|
||||
|
||||
# 核心函数,启动服务器,会阻塞当前线程
|
||||
def blocking_run_server():
|
||||
self.server_thread = current_thread()
|
||||
self.enter_wait_list()
|
||||
server = plugin_jm_server.JmServer(base_dir, password, **kwargs)
|
||||
# run方法会阻塞当前线程直到flask退出
|
||||
server.run(**run)
|
||||
|
||||
# 对于debug模式,特殊处理
|
||||
if run['debug'] is True:
|
||||
run.setdefault('use_reloader', False)
|
||||
|
||||
# debug模式只能在主线程启动,判断当前线程是不是主线程
|
||||
if current_thread() is not threading.main_thread():
|
||||
# 不是主线程,return
|
||||
return self.warning_wrong_usage_of_debug()
|
||||
else:
|
||||
# 是主线程,启动服务器
|
||||
blocking_run_server()
|
||||
|
||||
else:
|
||||
# 非debug模式,开新线程启动
|
||||
threading.Thread(target=blocking_run_server, daemon=True).start()
|
||||
atexit_register(self.wait_server_stop)
|
||||
|
||||
def warning_wrong_usage_of_debug(self):
|
||||
self.log('注意!当配置debug=True时,请确保当前插件是在主线程中被调用。\n'
|
||||
'因为如果本插件配置在 [after_album/after_photo] 这种时机调用,\n'
|
||||
'会使得flask框架不在主线程debug运行,\n'
|
||||
'导致报错(ValueError: signal only works in main thread of the main interpreter)。\n',
|
||||
'【基于上述原因,当前线程非主线程,不启动服务器】'
|
||||
'warning'
|
||||
)
|
||||
|
||||
def wait_server_stop(self, proactive=False):
|
||||
st = self.server_thread
|
||||
if (
|
||||
st is None
|
||||
or st == current_thread()
|
||||
or not st.is_alive()
|
||||
):
|
||||
return
|
||||
|
||||
if proactive:
|
||||
msg = f'[{self.plugin_key}]的服务器线程仍运行中,可按下ctrl+c结束程序'
|
||||
else:
|
||||
msg = f'主线程执行完毕,但插件[{self.plugin_key}]的服务器线程仍运行中,可按下ctrl+c结束程序'
|
||||
|
||||
self.log(msg, 'wait')
|
||||
|
||||
while st.is_alive():
|
||||
try:
|
||||
st.join(timeout=0.5)
|
||||
except KeyboardInterrupt:
|
||||
self.log('收到ctrl+c,结束程序', 'wait')
|
||||
return
|
||||
|
||||
def wait_until_finish(self):
|
||||
self.wait_server_stop(proactive=True)
|
||||
|
||||
@classmethod
|
||||
def build(cls, option: JmOption) -> 'JmOptionPlugin':
|
||||
"""
|
||||
单例模式
|
||||
"""
|
||||
field_name = 'single_instance'
|
||||
|
||||
instance = getattr(cls, field_name, None)
|
||||
if instance is not None:
|
||||
return instance
|
||||
|
||||
with cls.single_instance_lock:
|
||||
instance = getattr(cls, field_name, None)
|
||||
if instance is not None:
|
||||
return instance
|
||||
instance = JmServerPlugin(option)
|
||||
setattr(cls, field_name, instance)
|
||||
return instance
|
||||
|
@ -51,7 +51,7 @@ class JmcomicText:
|
||||
# 點擊喜歡
|
||||
pattern_html_album_likes = compile(r'<span id="albim_likes_\d+">(.*?)</span>')
|
||||
# 觀看
|
||||
pattern_html_album_views = compile(r'<span>(.*?)</span>\n<span>(次觀看|观看次数)</span>')
|
||||
pattern_html_album_views = compile(r'<span>(.*?)</span>\n *<span>(次觀看|观看次数)</span>')
|
||||
# 評論(div)
|
||||
pattern_html_album_comment_count = compile(r'<div class="badge"[^>]*?id="total_video_comments">(\d+)</div>'), 0
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user