chenjunhao 2 semanas atrás
pai
commit
a04ba430d1
1 arquivos alterados com 113 adições e 85 exclusões
  1. 113 85
      start_run_yaoshibang_v2.py

+ 113 - 85
start_run_yaoshibang_v2.py

@@ -1,8 +1,5 @@
 """药师帮统一入口 — 根据任务 snapshot_collect_status 字段自动切换普通/快照模式。
-
-与公共类 collect_schedule_runner 不同:本入口直接对接 CrawlerScheduler API
-(拉取任务/心跳/进度上报),并支持快照模式切换、账号注入等药师帮定制逻辑,
-不再依赖 CollectScheduleRunner。
+支持账号池轮换:一个账号被锁自动换下一个。
 """
 import random
 import time
@@ -18,21 +15,49 @@ from commons.feishu_webhook import send_text
 PLATFORM_NAME = "药师帮"
 PLATFORM_ID = 5
 
+# 账号池
+_account_pool = []
+_account_index = 0
+
 
-def get_device_id_from_db():
-    """从 accounts_platform 表获取一个可用的药师帮账号(platform=5, status=1, equipment_id=3),
-    用 phone 作为 DEVICE_ID(回告接口的 username 也用它),同时返回密码供 spider 登录。"""
+def _load_account_pool():
+    """从 DB 加载所有可用药师帮账号"""
+    global _account_pool, _account_index
     db = MySQLPoolOn2()
     sql = """SELECT `name`, `phone`, `password` FROM `accounts_platform`
-             WHERE `platform`=5 AND `status`=1 AND `equipment_id`=3
-             ORDER BY `cookie_timestamp` ASC LIMIT 1"""
-    rows = db.select_data(sql)
+             WHERE `platform`=%s AND `status`=1 AND `equipment_id`=3
+             ORDER BY `cookie_timestamp` ASC"""
+    rows = db.select_data(sql, (PLATFORM_ID,))
     if not rows:
-        logger.error("无可用药师帮账号(platform=5, status=1, equipment_id=3)")
-        return None
-    account = rows[0]
-    logger.info("获取到药师帮账号: name=%s, phone=%s", account["name"], account["phone"])
-    return account["phone"], account["password"], account["name"]
+        logger.error("无可用药师帮账号(platform=%s, status=1)", PLATFORM_ID)
+        return False
+    _account_pool = [(r["phone"], r["password"], r["name"]) for r in rows]
+    _account_index = 0
+    logger.info("从 DB 加载 %s 个药师帮账号", len(_account_pool))
+    return True
+
+
+def _get_current_account():
+    """获取当前账号,池空则重新加载"""
+    global _account_pool, _account_index
+    if not _account_pool:
+        if not _load_account_pool():
+            return None
+    return _account_pool[_account_index]
+
+
+def _rotate_account():
+    """换下一个账号,返回 (phone, password, name)"""
+    global _account_index
+    if not _account_pool:
+        if not _load_account_pool():
+            return None
+    _account_index = (_account_index + 1) % len(_account_pool)
+    new_acc = _account_pool[_account_index]
+    logger.warning("账号被锁,切换: %s → %s (第%s/%s个)",
+                   _account_pool[(_account_index - 1) % len(_account_pool)][2],
+                   new_acc[2], _account_index + 1, len(_account_pool))
+    return new_acc
 
 
 def _build_task_dict(api_task, device_id, account_password, account_name, scheduler):
@@ -84,9 +109,8 @@ def _report_progress(scheduler, task_id, is_finished, crawled_count=0,
         "current_page": current_page,
         "is_finished": is_finished,
         "total_pages": total_pages,
+        "crawled_count": crawled_count,
     }
-    if crawled_count:
-        data["crawled_count"] = crawled_count
     if exception_type is not None:
         data["exception_type"] = exception_type
     if remark:
@@ -99,83 +123,87 @@ def _report_progress(scheduler, task_id, is_finished, crawled_count=0,
         logger.warning("任务状态上报失败: %s", e)
 
 
-def run_one_task(scheduler, device_id, account_password, account_name):
-    """拉取一个任务,根据 snapshot_collect_status 选 spider 执行并上报。"""
-    # 拉取任务
-    api_task = scheduler.get_task()
-    if not api_task:
-        logger.info("%s暂无任务", PLATFORM_NAME)
-        return
-
-    task_id = api_task.get("id", "")
-    task_dict = _build_task_dict(api_task, device_id, account_password,
-                                 account_name, scheduler)
-
-    # 打印任务信息(隐藏不可序列化的 scheduler)
-    log_dict = {k: v for k, v in task_dict.items() if k != "scheduler"}
-    logger.info("拉取到任务: %s", json.dumps(log_dict, ensure_ascii=False))
-
-    # 根据 snapshot_collect_status 决定走普通爬虫还是快照爬虫
-    snapshot_status = task_dict.get("snapshot_collect_status", 0)
-    if not snapshot_status:
-        spider_cls = YaoShiBangSnapshot
-        logger.info("任务 %s 启用快照模式", task_id)
-    else:
-        spider_cls = YsbSpider
-        logger.info("任务 %s 使用普通模式", task_id)
-
-    # 执行爬虫
-    result = spider_cls(task_dict).run()
-    if len(result) == 3:
-        crawl_count, is_success, total_pages = result
-    else:
-        crawl_count, is_success = result
-        total_pages = 0
-
-    # 飞书通知
-    send_text(
-        f"{time.strftime('%Y-%m-%d %H:%M:%S')} 通知:\n"
-        f"平台: {PLATFORM_NAME}, 药品: {task_dict.get('product_name')}, "
-        f"爬取数据: {crawl_count}条"
-    )
-
-    # 上报任务结果
-    if is_success:
-        _report_progress(scheduler, task_id, is_finished=1,
-                         crawled_count=crawl_count,
-                         current_page=total_pages, total_pages=total_pages)
-    else:
-        _report_progress(scheduler, task_id, is_finished=0,
-                         crawled_count=crawl_count,
-                         current_page=total_pages, total_pages=total_pages)
-
 
 if __name__ == "__main__":
-    # 1. 获取账号
-    result = get_device_id_from_db()
-    if result:
-        device_id, account_password, account_name = result
-    else:
-        device_id, account_password, account_name = "YSB", None, None
-
-    # 2. 初始化调度器(心跳 + 拉任务 + 上报)
-    scheduler = CrawlerScheduler(
-        DEVICE_ID=device_id,
-        platform=str(PLATFORM_ID),
-    )
-    scheduler.start()
-    time.sleep(2)  # 等待首次心跳完成
-    logger.info("调度器已启动,device_id=%s,心跳已就绪", device_id)
+    # 1. 加载账号池
+    if not _load_account_pool():
+        logger.error("无可用药师帮账号,退出")
+        exit(1)
 
-    # 3. 循环执行
     idle_seconds = random.randint(180, 300)
+    empty_count = 0          # 连续拉不到任务的次数
+    max_empty_before_rotate = 2  # 连续 2 次拉不到就换号
+
+    # 2. 初始化调度器
+    phone, password, name = _get_current_account()
+    scheduler = CrawlerScheduler(DEVICE_ID=phone, platform=str(PLATFORM_ID))
+    scheduler.start()
+    time.sleep(2)
+    logger.info("调度器已启动,当前账号: %s (phone=%s)", name, phone)
+
     logger.info("循环任务已启动,平台=%s,每轮间隔 %s 秒", PLATFORM_NAME, idle_seconds)
 
     while True:
         try:
             logger.info("开始执行%s爬虫任务", PLATFORM_NAME)
-            run_one_task(scheduler, device_id, account_password, account_name)
-            logger.info("%s爬虫任务执行完成", PLATFORM_NAME)
+            api_task = scheduler.get_task()
+
+            if not api_task:
+                empty_count += 1
+                logger.info("%s暂无任务 (连续%s次),等待 %s 秒后重试",
+                            PLATFORM_NAME, empty_count, idle_seconds)
+                if empty_count >= max_empty_before_rotate:
+                    # 换个号试试
+                    new_acc = _rotate_account()
+                    if new_acc:
+                        scheduler.stop()
+                        phone, password, name = new_acc
+                        scheduler = CrawlerScheduler(DEVICE_ID=phone, platform=str(PLATFORM_ID))
+                        scheduler.start()
+                        empty_count = 0
+                        logger.info("已切换到账号: %s", name)
+                time.sleep(idle_seconds)
+                continue
+
+            empty_count = 0  # 拉到任务了,重置
+
+            task_id = api_task.get("id", "")
+            task_dict = _build_task_dict(api_task, phone, password, name, scheduler)
+
+            log_dict = {k: v for k, v in task_dict.items() if k != "scheduler"}
+            logger.info("拉取到任务: %s", json.dumps(log_dict, ensure_ascii=False))
+
+            # 根据 snapshot_collect_status 选爬虫
+            snapshot_status = task_dict.get("snapshot_collect_status", 0)
+            if not snapshot_status:
+                spider_cls = YaoShiBangSnapshot
+                logger.info("任务 %s 启用快照模式", task_id)
+            else:
+                spider_cls = YsbSpider
+                logger.info("任务 %s 使用普通模式", task_id)
+
+            result = spider_cls(task_dict).run()
+            if len(result) == 3:
+                crawl_count, is_success, total_pages = result
+            else:
+                crawl_count, is_success = result
+                total_pages = 0
+
+            send_text(
+                f"{time.strftime('%Y-%m-%d %H:%M:%S')} 通知:\n"
+                f"平台: {PLATFORM_NAME}, 药品: {task_dict.get('product_name')}, "
+                f"爬取数据: {crawl_count}条"
+            )
+
+            if is_success:
+                _report_progress(scheduler, task_id, is_finished=1,
+                                 crawled_count=crawl_count,
+                                 current_page=total_pages, total_pages=total_pages)
+            else:
+                _report_progress(scheduler, task_id, is_finished=0,
+                                 crawled_count=crawl_count,
+                                 current_page=total_pages, total_pages=total_pages)
+
         except Exception as e:
             logger.error("%s爬虫任务执行失败: %s", PLATFORM_NAME, e, exc_info=True)
         time.sleep(idle_seconds)