scheduler.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import time
  2. import requests
  3. import threading
  4. import urllib3
  5. # 禁用SSL警告(https 自签名证书)
  6. urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
  7. def _log(msg):
  8. print(f"[scheduler] {msg}")
  9. class CrawlerScheduler:
  10. """爬虫任务调度器"""
  11. def __init__(self, DEVICE_ID, platform, heartbeat_interval=30):
  12. """
  13. 初始化调度器
  14. Args:
  15. platform: 平台名称
  16. heartbeat_url: 心跳上报URL
  17. heartbeat_interval: 心跳间隔时间(秒)
  18. """
  19. self.username = DEVICE_ID
  20. self.platform = platform
  21. # 回告接口返回 code=error(如:该账号今日爬取量已达平台限额,任务已释放)
  22. # → 置位标志,采集主循环(step3)每批检查,立即停止采集
  23. self.limit_reached = False
  24. self.limit_msg = ""
  25. self._lock = threading.Lock()
  26. # ⭐ 必须用 https:http 会被 nginx 301 重定向,POST 会被降级成 GET 导致 405(美团同款坑)
  27. self.heartbeat_url = 'https://pricesys.kailin.com.cn:8082/api/collect_task/heartbeat'
  28. self.heartbeat_interval = heartbeat_interval
  29. self.end = False
  30. self.heartbeat_thread = None
  31. def _heartbeat_reporter(self):
  32. """守护线程:只负责上报心跳"""
  33. headers = {'X-Crawler-Token': 'zhijiayun_crawler_2026'}
  34. while True:
  35. try:
  36. json_data = {
  37. "platform": self.platform,
  38. "username": self.username
  39. }
  40. _log(f'上传心跳数据:{json_data},链接:{self.heartbeat_url}')
  41. response = requests.post(
  42. self.heartbeat_url,
  43. json=json_data,
  44. headers=headers,
  45. timeout=3,
  46. verify=False # 忽略SSL证书验证
  47. )
  48. print(f"[心跳] 发送成功: {response.status_code}")
  49. result = response.json()
  50. if self.end:
  51. break
  52. if result.get('code') != 'success':
  53. _log(f'心跳回传异常:{result}')
  54. else:
  55. _log(f'心跳回传:{result}')
  56. time.sleep(self.heartbeat_interval)
  57. except Exception as e:
  58. print(e)
  59. _log(e)
  60. time.sleep(5)
  61. def start(self):
  62. """启动调度器"""
  63. self.set_flag(False)
  64. if self.heartbeat_thread and self.heartbeat_thread.is_alive():
  65. return
  66. self.heartbeat_thread = threading.Thread(
  67. target=self._heartbeat_reporter,
  68. daemon=True
  69. )
  70. self.heartbeat_thread.start()
  71. def stop(self):
  72. """停止调度器"""
  73. self.set_flag(True)
  74. _log('心跳停止')
  75. def get_task(self):
  76. try:
  77. # ⭐ 必须用 https(原因同上:http 会被 301 重定向降级)
  78. task_api = "https://pricesys.kailin.com.cn:8082/api/collect_task/pull"
  79. headers = {'X-Crawler-Token': 'zhijiayun_crawler_2026'}
  80. params = {
  81. 'platform': self.platform,
  82. 'username': self.username
  83. }
  84. _log(f'拉取任务参数:{params},链接:{task_api}')
  85. response = requests.get(task_api, params=params, headers=headers, timeout=10, verify=False)
  86. result = response.json()
  87. _log(f'拉取任务:{result}')
  88. if result.get('code') == 'success':
  89. return result.get('data').get('task')
  90. print('拉取任务返回', result)
  91. except Exception as e:
  92. _log('获取任务报错', e)
  93. def post_report(self, data):
  94. try:
  95. # ⭐ 必须用 https(原因同上:http 会被 301 重定向降级)
  96. url = "https://pricesys.kailin.com.cn:8082/api/collect_task/report"
  97. print('传给返回接口的数据', data)
  98. _log(f'report上传数据:{data},链接:{url}')
  99. headers = {'X-Crawler-Token': 'zhijiayun_crawler_2026'}
  100. response = requests.post(url, json=data, headers=headers, timeout=5, verify=False)
  101. # 记录日志
  102. result = response.json()
  103. _log(f"report回传数据{result}")
  104. if (result.get('code') != 'success'):
  105. _log(f'翻页回传结果不成功:{result}')
  106. self.stop()
  107. if result.get('code') == 'error':
  108. # 平台限额/任务已释放等错误 → 置位标志,采集主循环立即停止
  109. self.limit_msg = str(result.get('msg', ''))
  110. self.limit_reached = True
  111. _log(f'回告返回error,置位停止采集标志:{self.limit_msg}')
  112. print(f'任务进度上传 {result}')
  113. except Exception as e:
  114. _log(e)
  115. def set_flag(self, value):
  116. with self._lock:
  117. self.end = value