start_run_ybm.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. """
  2. 药帮忙 (YBM) 平台采集入口
  3. 使用 commons1 调度框架 —— 从内部 API 拉取任务,由 YbmCrawler 执行采集。
  4. """
  5. import random
  6. import time
  7. import re
  8. from datetime import datetime
  9. import requests
  10. from commons.Logger import get_spider_logger
  11. from commons.feishu_webhook import send_text, send_error_card
  12. from spiders.ybm.ybm_crawl import (
  13. YbmCrawler,
  14. PLATFORM_ID,
  15. YBM_DEVICE_ID,
  16. TASK_PULL_URL,
  17. TASK_HEARTBEAT_URL,
  18. TASK_API_TOKEN,
  19. load_city_mapping,
  20. )
  21. logger = get_spider_logger("ybm_runner")
  22. PLATFORM_NAME = "药帮忙"
  23. IDLE_SECONDS_MIN = 180
  24. IDLE_SECONDS_MAX = 300
  25. def pull_task():
  26. """从内部调度 API 拉取任务"""
  27. headers = {'X-Crawler-Token': TASK_API_TOKEN}
  28. params = {'platform': PLATFORM_ID, 'username': YBM_DEVICE_ID}
  29. try:
  30. response = requests.get(TASK_PULL_URL, params=params, headers=headers, timeout=15)
  31. result = response.json()
  32. logger.info("拉取任务 API 返回: %s", result)
  33. if result.get('code') == 'success':
  34. task = result.get('data', {}).get('task') or {}
  35. if task and task.get("id"):
  36. logger.info("拉取任务成功 platform=%s task_id=%s", PLATFORM_ID, task.get("id"))
  37. return task
  38. else:
  39. logger.info("拉取任务成功但无有效任务(task=%s),视为暂无任务", task)
  40. return {}
  41. else:
  42. logger.warning("拉取任务 API 返回非 success:code=%s, msg=%s", result.get('code'), result.get('msg'))
  43. return {}
  44. except Exception as e:
  45. logger.error("拉取任务 API 请求失败:%s", e)
  46. return {}
  47. def send_heartbeat():
  48. """向调度 API 上报心跳,告知服务器爬虫在线"""
  49. headers = {'X-Crawler-Token': TASK_API_TOKEN}
  50. payload = {"platform": str(PLATFORM_ID), "username": YBM_DEVICE_ID}
  51. try:
  52. resp = requests.post(TASK_HEARTBEAT_URL, json=payload, headers=headers, timeout=10)
  53. logger.debug("心跳上报: HTTP %s, %s", resp.status_code, resp.text[:200])
  54. except Exception as e:
  55. logger.warning("心跳上报失败: %s", e)
  56. def notify_result(task, crawl_count, success):
  57. """发送飞书通知"""
  58. try:
  59. drug_name = task.get("product_name", "")
  60. spec = task.get("product_specs", "") or ""
  61. spec_items = [item.strip() for item in re.split(r"[|、,,\n\r]+", spec) if item.strip()]
  62. display_spec = "、".join(spec_items)
  63. notice_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  64. text_msg = (
  65. f"{notice_time} 通知:\n"
  66. f"平台: {PLATFORM_NAME}, 药品: {drug_name}, 品规: {display_spec}, 爬取数据: {crawl_count}条"
  67. )
  68. if success:
  69. send_text(text_msg)
  70. else:
  71. send_error_card(
  72. task_name=drug_name,
  73. err_msg=f"task_id={task.get('id')}, company_id={task.get('company_id')}, crawl_count={crawl_count}",
  74. mention_all=False,
  75. )
  76. except Exception as e:
  77. logger.warning("飞书通知发送失败:%s", e)
  78. def main_loop():
  79. """循环拉取任务并执行采集"""
  80. load_city_mapping()
  81. idle_seconds = random.randint(IDLE_SECONDS_MIN, IDLE_SECONDS_MAX)
  82. logger.info("药帮忙采集启动,每轮间隔 %s 秒", idle_seconds)
  83. while True:
  84. try:
  85. # 先上报心跳,告诉服务器爬虫在线
  86. send_heartbeat()
  87. task = pull_task()
  88. if not task:
  89. logger.info("%s 暂无任务,等待 %s 秒后重试", PLATFORM_NAME, idle_seconds)
  90. time.sleep(idle_seconds)
  91. continue
  92. logger.info("开始执行%s爬虫任务 task_id=%s", PLATFORM_NAME, task.get("id"))
  93. crawler = YbmCrawler(task)
  94. crawl_count, is_success = crawler.run()
  95. logger.info("%s爬虫任务执行完成 task_id=%s count=%s success=%s",
  96. PLATFORM_NAME, task.get("id"), crawl_count, is_success)
  97. notify_result(task, crawl_count, is_success)
  98. except Exception as e:
  99. logger.error("%s 爬虫任务执行失败: %s", PLATFORM_NAME, e, exc_info=True)
  100. time.sleep(idle_seconds)
  101. if __name__ == '__main__':
  102. main_loop()