|
@@ -85,6 +85,7 @@ def get_mysql():
|
|
|
|
|
|
|
|
|
|
|
|
|
SCHEDULER_INTERVAL_SECONDS = 600
|
|
SCHEDULER_INTERVAL_SECONDS = 600
|
|
|
|
|
+MANUAL_SCHEDULER_INTERVAL_SECONDS = 300
|
|
|
PLATFORM_PDD = 3
|
|
PLATFORM_PDD = 3
|
|
|
TASK_STATUS_PENDING = 1
|
|
TASK_STATUS_PENDING = 1
|
|
|
DEVICE_STATUS_IDLE = 0
|
|
DEVICE_STATUS_IDLE = 0
|
|
@@ -109,82 +110,193 @@ USE_MANUAL_TASKS = True
|
|
|
# 必填:search_key;device_id 不需要填写,手动模式会自动选择空闲 PDD 设备。
|
|
# 必填:search_key;device_id 不需要填写,手动模式会自动选择空闲 PDD 设备。
|
|
|
MANUAL_TASKS = [
|
|
MANUAL_TASKS = [
|
|
|
{
|
|
{
|
|
|
- "search_key": "气滞胃痛颗粒",
|
|
|
|
|
- "title_key": "气滞胃痛颗粒",
|
|
|
|
|
- "spec_list": ["12袋"],
|
|
|
|
|
- "brand": "999",
|
|
|
|
|
- "save_search_key": "气滞胃痛颗粒",
|
|
|
|
|
|
|
+ "search_key": "天和骨通贴膏",
|
|
|
|
|
+ "title_key": "骨通贴膏",
|
|
|
|
|
+ "spec_list": ["7cm*10cm*6", "8cm*13cm*6", "8cm*13cm*10", "8cm*13cm*14"],
|
|
|
|
|
+ "brand": "天和",
|
|
|
|
|
+ "save_search_key": "天和骨通贴膏",
|
|
|
"start_page": 0,
|
|
"start_page": 0,
|
|
|
"end_page": 300,
|
|
"end_page": 300,
|
|
|
"max_counts_limit": 300,
|
|
"max_counts_limit": 300,
|
|
|
"sort": "升序"
|
|
"sort": "升序"
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- "search_key": "气滞胃痛颗粒",
|
|
|
|
|
- "title_key": "气滞胃痛颗粒",
|
|
|
|
|
- "spec_list": ["5g*9袋"],
|
|
|
|
|
- "brand": "999",
|
|
|
|
|
- "save_search_key": "气滞胃痛颗粒",
|
|
|
|
|
|
|
+ "search_key": "天和追风膏",
|
|
|
|
|
+ "title_key": "天和追风膏",
|
|
|
|
|
+ "spec_list": ["8cm*13cm*8", "7cm*10cm*8", "8cm*13cm*5"],
|
|
|
|
|
+ "brand": "天和",
|
|
|
|
|
+ "save_search_key": "天和追风膏",
|
|
|
"start_page": 0,
|
|
"start_page": 0,
|
|
|
"end_page": 300,
|
|
"end_page": 300,
|
|
|
"max_counts_limit": 300,
|
|
"max_counts_limit": 300,
|
|
|
"sort": "升序"
|
|
"sort": "升序"
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- "search_key": "气滞胃痛颗粒",
|
|
|
|
|
- "title_key": "气滞胃痛颗粒",
|
|
|
|
|
- "spec_list": ["2.5g*21袋"],
|
|
|
|
|
- "brand": "999",
|
|
|
|
|
- "save_search_key": "气滞胃痛颗粒",
|
|
|
|
|
|
|
+ "search_key": "麝香壮骨膏",
|
|
|
|
|
+ "title_key": "麝香壮骨膏",
|
|
|
|
|
+ "spec_list": ["7cm*10cm*6", "7cm*10cm*12"],
|
|
|
|
|
+ "brand": "天和",
|
|
|
|
|
+ "save_search_key": "麝香壮骨膏",
|
|
|
"start_page": 0,
|
|
"start_page": 0,
|
|
|
"end_page": 300,
|
|
"end_page": 300,
|
|
|
"max_counts_limit": 300,
|
|
"max_counts_limit": 300,
|
|
|
"sort": "升序"
|
|
"sort": "升序"
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- "search_key": "益血生胶囊", # 必填
|
|
|
|
|
|
|
+ "search_key": "益血生胶囊",
|
|
|
"title_key": "益血生胶囊",
|
|
"title_key": "益血生胶囊",
|
|
|
- "spec_list": ["0.25g*84粒"], # 列表可以,代码会自动归一化
|
|
|
|
|
|
|
+ "spec_list": ["0.25g*84粒", "12粒*3*4", "168", "0.25g*12粒*3板", "0.25g*12粒*4板", "0.25g*12粒*5板",
|
|
|
|
|
+ "0.25g*84粒"],
|
|
|
"brand": "999",
|
|
"brand": "999",
|
|
|
"save_search_key": "益血生胶囊",
|
|
"save_search_key": "益血生胶囊",
|
|
|
"start_page": 0,
|
|
"start_page": 0,
|
|
|
"end_page": 300,
|
|
"end_page": 300,
|
|
|
"max_counts_limit": 300,
|
|
"max_counts_limit": 300,
|
|
|
- "sort": "升序",
|
|
|
|
|
|
|
+ "sort": "升序"
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- "search_key": "多烯磷脂酰胆碱胶囊", # 必填
|
|
|
|
|
|
|
+ "search_key": "护肝片",
|
|
|
|
|
+ "title_key": "护肝片",
|
|
|
|
|
+ "spec_list": ["104片"],
|
|
|
|
|
+ "brand": "999",
|
|
|
|
|
+ "save_search_key": "护肝片",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "多烯磷脂酰胆碱胶囊",
|
|
|
"title_key": "多烯磷脂酰胆碱胶囊",
|
|
"title_key": "多烯磷脂酰胆碱胶囊",
|
|
|
- "spec_list": ["36粒"], # 列表可以,代码会自动归一化
|
|
|
|
|
|
|
+ "spec_list": ["36粒"],
|
|
|
"brand": "易善复",
|
|
"brand": "易善复",
|
|
|
"save_search_key": "多烯磷脂酰胆碱胶囊",
|
|
"save_search_key": "多烯磷脂酰胆碱胶囊",
|
|
|
"start_page": 0,
|
|
"start_page": 0,
|
|
|
"end_page": 300,
|
|
"end_page": 300,
|
|
|
"max_counts_limit": 300,
|
|
"max_counts_limit": 300,
|
|
|
- "sort": "升序",
|
|
|
|
|
|
|
+ "sort": "升序"
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- "search_key": "硝呋太尔制霉素阴道软胶囊", # 必填
|
|
|
|
|
|
|
+ "search_key": "消痔软膏",
|
|
|
|
|
+ "title_key": "消痔软膏",
|
|
|
|
|
+ "spec_list": ["4支", "5g*2支"],
|
|
|
|
|
+ "brand": "999",
|
|
|
|
|
+ "save_search_key": "消痔软膏",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "硝呋太尔制霉素阴道软胶囊",
|
|
|
"title_key": "硝呋太尔制霉素阴道软胶囊",
|
|
"title_key": "硝呋太尔制霉素阴道软胶囊",
|
|
|
- "spec_list": ["6粒"], # 列表可以,代码会自动归一化
|
|
|
|
|
|
|
+ "spec_list": ["6粒"],
|
|
|
"brand": "999",
|
|
"brand": "999",
|
|
|
"save_search_key": "硝呋太尔制霉素阴道软胶囊",
|
|
"save_search_key": "硝呋太尔制霉素阴道软胶囊",
|
|
|
"start_page": 0,
|
|
"start_page": 0,
|
|
|
"end_page": 300,
|
|
"end_page": 300,
|
|
|
"max_counts_limit": 300,
|
|
"max_counts_limit": 300,
|
|
|
- "sort": "升序",
|
|
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "玻璃酸钠滴眼液",
|
|
|
|
|
+ "title_key": "玻璃酸钠滴眼液",
|
|
|
|
|
+ "spec_list": ["10ml:10mg", "10支", "0.4ml*5支"],
|
|
|
|
|
+ "brand": "999",
|
|
|
|
|
+ "save_search_key": "玻璃酸钠滴眼液",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "尪痹胶囊",
|
|
|
|
|
+ "title_key": "尪痹胶囊",
|
|
|
|
|
+ "spec_list": ["15*2", "15*4", "12*5"],
|
|
|
|
|
+ "brand": "999",
|
|
|
|
|
+ "save_search_key": "尪痹胶囊",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- "search_key": "葡萄糖酸钙锌口服溶液", # 必填
|
|
|
|
|
|
|
+ "search_key": "益气清肺颗粒",
|
|
|
|
|
+ "title_key": "益气清肺颗粒",
|
|
|
|
|
+ "spec_list": ["15g*6袋"],
|
|
|
|
|
+ "brand": "0",
|
|
|
|
|
+ "save_search_key": "益气清肺颗粒",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "气滞胃痛片",
|
|
|
|
|
+ "title_key": "气滞胃痛片",
|
|
|
|
|
+ "spec_list": ["", "15", "45片"],
|
|
|
|
|
+ "brand": "999",
|
|
|
|
|
+ "save_search_key": "气滞胃痛片",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "温经汤颗粒",
|
|
|
|
|
+ "title_key": "温经汤颗粒",
|
|
|
|
|
+ "spec_list": ["10g*9袋"],
|
|
|
|
|
+ "brand": "999",
|
|
|
|
|
+ "save_search_key": "温经汤颗粒",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "气滞胃痛颗粒",
|
|
|
|
|
+ "title_key": "气滞胃痛颗粒",
|
|
|
|
|
+ "spec_list": ["12袋", "5g*9袋", "2.5g*21袋"],
|
|
|
|
|
+ "brand": "999",
|
|
|
|
|
+ "save_search_key": "气滞胃痛颗粒",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "葡萄糖酸钙锌口服溶液",
|
|
|
"title_key": "葡萄糖酸钙锌口服溶液",
|
|
"title_key": "葡萄糖酸钙锌口服溶液",
|
|
|
- "spec_list": ["10ml*30支"], # 列表可以,代码会自动归一化
|
|
|
|
|
|
|
+ "spec_list": ["10ml*20袋", "10ml*24袋", "10ml*36袋", "15ml*33袋", "10ml*28袋", "10ml*12支", "10ml*18支", "10ml*48支", "10ml*24支", "10ml*30支", "5ml*36支", "15ml*20支"],
|
|
|
"brand": "澳诺",
|
|
"brand": "澳诺",
|
|
|
"save_search_key": "葡萄糖酸钙锌口服溶液",
|
|
"save_search_key": "葡萄糖酸钙锌口服溶液",
|
|
|
"start_page": 0,
|
|
"start_page": 0,
|
|
|
"end_page": 300,
|
|
"end_page": 300,
|
|
|
"max_counts_limit": 300,
|
|
"max_counts_limit": 300,
|
|
|
- "sort": "升序",
|
|
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "小儿氨酚烷胺颗粒",
|
|
|
|
|
+ "title_key": "小儿氨酚烷胺颗粒",
|
|
|
|
|
+ "spec_list": ["10ml*18袋"],
|
|
|
|
|
+ "brand": "好娃娃",
|
|
|
|
|
+ "save_search_key": "小儿氨酚烷胺颗粒",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
},
|
|
},
|
|
|
|
|
+ {
|
|
|
|
|
+ "search_key": "维生素D滴剂",
|
|
|
|
|
+ "title_key": "维生素D滴剂",
|
|
|
|
|
+ "spec_list": ["8粒", "32粒", "48粒", "60粒", "64粒", "80粒", "40粒", "10粒", "20粒", "30粒", "50粒"],
|
|
|
|
|
+ "brand": "澳诺",
|
|
|
|
|
+ "save_search_key": "维生素D滴剂",
|
|
|
|
|
+ "start_page": 0,
|
|
|
|
|
+ "end_page": 300,
|
|
|
|
|
+ "max_counts_limit": 300,
|
|
|
|
|
+ "sort": "升序"
|
|
|
|
|
+ }
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
# 这些集合只表示“当前进程里的占用状态”。
|
|
# 这些集合只表示“当前进程里的占用状态”。
|
|
@@ -514,6 +626,20 @@ def wait_for_active_workers(check_interval=1):
|
|
|
time.sleep(check_interval)
|
|
time.sleep(check_interval)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def has_active_workers():
|
|
|
|
|
+ cleanup_finished_workers()
|
|
|
|
|
+ with dispatch_lock:
|
|
|
|
|
+ return any(thread.is_alive() for thread in worker_threads.values())
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def all_manual_tasks_dispatched():
|
|
|
|
|
+ expected_task_ids = {
|
|
|
|
|
+ parse_optional_int(task.get("task_id"), 900000 + idx + 1)
|
|
|
|
|
+ for idx, task in enumerate(MANUAL_TASKS)
|
|
|
|
|
+ }
|
|
|
|
|
+ return expected_task_ids.issubset(manual_dispatched_task_ids)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
|
|
|
def run_task_worker(task_payload):
|
|
def run_task_worker(task_payload):
|
|
|
# 功能:单个任务线程的主入口,负责构建 PDD 实例、执行采集并在异常时兜底收尾。
|
|
# 功能:单个任务线程的主入口,负责构建 PDD 实例、执行采集并在异常时兜底收尾。
|
|
@@ -614,14 +740,16 @@ def dispatch_pending_tasks():
|
|
|
worker_threads.pop(device_id, None)
|
|
worker_threads.pop(device_id, None)
|
|
|
raise
|
|
raise
|
|
|
|
|
|
|
|
-def schedule_dispatch(delay_seconds=SCHEDULER_INTERVAL_SECONDS):
|
|
|
|
|
|
|
+def schedule_dispatch(delay_seconds=SCHEDULER_INTERVAL_SECONDS, job_func=None):
|
|
|
# 功能:安排下一次轮询触发时间。
|
|
# 功能:安排下一次轮询触发时间。
|
|
|
# 功能:注册下一轮调度定时器,让调度器按固定间隔持续轮询。
|
|
# 功能:注册下一轮调度定时器,让调度器按固定间隔持续轮询。
|
|
|
global scheduler_timer
|
|
global scheduler_timer
|
|
|
if scheduler_stop_event.is_set():
|
|
if scheduler_stop_event.is_set():
|
|
|
return
|
|
return
|
|
|
|
|
+ if job_func is None:
|
|
|
|
|
+ job_func = scheduled_dispatch_job
|
|
|
# 采用递归定时器而不是死循环,便于后续统一停机。
|
|
# 采用递归定时器而不是死循环,便于后续统一停机。
|
|
|
- scheduler_timer = threading.Timer(delay_seconds, scheduled_dispatch_job)
|
|
|
|
|
|
|
+ scheduler_timer = threading.Timer(delay_seconds, job_func)
|
|
|
scheduler_timer.daemon = False
|
|
scheduler_timer.daemon = False
|
|
|
scheduler_timer.name = "pdd-scheduler"
|
|
scheduler_timer.name = "pdd-scheduler"
|
|
|
scheduler_timer.start()
|
|
scheduler_timer.start()
|
|
@@ -636,6 +764,18 @@ def scheduled_dispatch_job():
|
|
|
finally:
|
|
finally:
|
|
|
schedule_dispatch(SCHEDULER_INTERVAL_SECONDS)
|
|
schedule_dispatch(SCHEDULER_INTERVAL_SECONDS)
|
|
|
|
|
|
|
|
|
|
+def manual_scheduled_dispatch_job():
|
|
|
|
|
+ try:
|
|
|
|
|
+ dispatch_pending_tasks()
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logging.exception(f"PDD 手动任务定时调度异常: {e}")
|
|
|
|
|
+ finally:
|
|
|
|
|
+ if all_manual_tasks_dispatched() and not has_active_workers():
|
|
|
|
|
+ logging.info("手动任务模式:全部任务已完成,停止轮询并退出")
|
|
|
|
|
+ scheduler_stop_event.set()
|
|
|
|
|
+ return
|
|
|
|
|
+ schedule_dispatch(MANUAL_SCHEDULER_INTERVAL_SECONDS, manual_scheduled_dispatch_job)
|
|
|
|
|
+
|
|
|
def report_api(task_id,page=None,start=None,end_page=None,end_time=None,finish_status=None):
|
|
def report_api(task_id,page=None,start=None,end_page=None,end_time=None,finish_status=None):
|
|
|
# 手动任务模式:不与调度接口产生任何交互。
|
|
# 手动任务模式:不与调度接口产生任何交互。
|
|
|
# 仅跳过“状态上报”,不影响采集数据写入数据库。
|
|
# 仅跳过“状态上报”,不影响采集数据写入数据库。
|
|
@@ -2754,12 +2894,16 @@ class PDD:
|
|
|
# pdd
|
|
# pdd
|
|
|
def main():
|
|
def main():
|
|
|
# 功能:启动调度器入口,先立即执行一轮派单,再注册后续轮询。
|
|
# 功能:启动调度器入口,先立即执行一轮派单,再注册后续轮询。
|
|
|
- logging.info(f"PDD 调度器启动,轮询间隔 {SCHEDULER_INTERVAL_SECONDS} 秒")
|
|
|
|
|
|
|
+ interval_seconds = MANUAL_SCHEDULER_INTERVAL_SECONDS if USE_MANUAL_TASKS else SCHEDULER_INTERVAL_SECONDS
|
|
|
|
|
+ logging.info(f"PDD 调度器启动,轮询间隔 {interval_seconds} 秒")
|
|
|
dispatch_pending_tasks()
|
|
dispatch_pending_tasks()
|
|
|
if USE_MANUAL_TASKS:
|
|
if USE_MANUAL_TASKS:
|
|
|
- logging.info("手动任务模式:只派发一次任务,不启动定时轮询")
|
|
|
|
|
- wait_for_active_workers()
|
|
|
|
|
- logging.info("手动任务模式:当前批次任务已完成,进程退出")
|
|
|
|
|
|
|
+ logging.info(f"手动任务模式:启动定时轮询,间隔 {MANUAL_SCHEDULER_INTERVAL_SECONDS} 秒")
|
|
|
|
|
+ if all_manual_tasks_dispatched() and not has_active_workers():
|
|
|
|
|
+ logging.info("手动任务模式:全部任务已完成,进程退出")
|
|
|
|
|
+ return
|
|
|
|
|
+ schedule_dispatch(MANUAL_SCHEDULER_INTERVAL_SECONDS, manual_scheduled_dispatch_job)
|
|
|
|
|
+ scheduler_stop_event.wait()
|
|
|
return
|
|
return
|
|
|
schedule_dispatch(SCHEDULER_INTERVAL_SECONDS)
|
|
schedule_dispatch(SCHEDULER_INTERVAL_SECONDS)
|
|
|
scheduler_stop_event.wait()
|
|
scheduler_stop_event.wait()
|