meml_spdier.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. # -*- coding: utf-8 -*-
  2. # Author : Charley
  3. # Python : 3.12.10
  4. # Date : 2026/5/18 15:17
  5. """
  6. Memory Lane 增量爬虫(日调度)
  7. 逻辑:
  8. 1. GET 首页解析当前网站全部 auction id
  9. 2. 查库 select distinct auction_id from memory_lane_record,得到已爬过的 auction
  10. 3. 差集 = 新增 auction
  11. 4. 没有新增 → 本轮无数据可抓,结束
  12. 5. 对每个新增 auction:postback 切换 → 翻页 → 写库
  13. 6. 补抓 state != 1 的详情页
  14. 7. 补采 status='Open' 的 lot:拍卖结束后回写最终 bids/status/current_bid
  15. """
  16. import time
  17. import random
  18. import inspect
  19. import schedule
  20. from curl_cffi import requests
  21. from loguru import logger
  22. from tenacity import retry, stop_after_attempt, wait_fixed
  23. from mysql_pool import MySQLConnectionPool
  24. from meml_core import (
  25. client_identifier_list,
  26. crawl_one_auction,
  27. get_auction_list,
  28. update_details_for_pending,
  29. update_open_lots,
  30. after_log,
  31. )
  32. logger.remove()
  33. logger.add("./logs/{time:YYYYMMDD}.log", encoding='utf-8', rotation="00:00",
  34. format="[{time:YYYY-MM-DD HH:mm:ss.SSS}] {level} {message}",
  35. level="DEBUG", retention="7 day")
  36. def get_existing_auction_ids(log, sql_pool):
  37. """查库返回已爬过的 auction_id 集合"""
  38. rows = sql_pool.select_all(
  39. "select distinct auction_id from memory_lane_record where auction_id is not null"
  40. )
  41. ids = {str(r[0]) for r in rows} if rows else set()
  42. log.info(f"库中已存在 {len(ids)} 个 auction_id: {sorted(ids)}")
  43. return ids
  44. def diff_new_auctions(log, all_auctions, existing_ids):
  45. """从首页解析的全部 auctions 中筛出库里没有的"""
  46. new_list = [a for a in all_auctions if a["id"] not in existing_ids]
  47. log.info(f"新增待抓取 auction 数: {len(new_list)} -> {[(a['id'], a['name']) for a in new_list]}")
  48. return new_list
  49. def run_incremental(log, sql_pool):
  50. """增量抓取主流程"""
  51. impersonate = random.choice(client_identifier_list)
  52. with requests.Session() as session:
  53. try:
  54. all_auctions = get_auction_list(log, session, impersonate)
  55. except Exception as e:
  56. log.error(f"获取拍卖会列表失败: {e}")
  57. return
  58. existing_ids = get_existing_auction_ids(log, sql_pool)
  59. new_auctions = diff_new_auctions(log, all_auctions, existing_ids)
  60. if not new_auctions:
  61. log.info("本轮无新增 auction,跳过 list 抓取")
  62. return
  63. for idx, auc in enumerate(new_auctions, 1):
  64. aid, name = auc["id"], auc["name"]
  65. log.info(f"========== [{idx}/{len(new_auctions)}] 开始抓 auction={aid} ({name}) ==========")
  66. try:
  67. crawl_one_auction(log, sql_pool, session, impersonate,
  68. auction_id=aid, auction_name=name)
  69. except Exception as e:
  70. log.error(f"auction={aid} 抓取异常: {e}")
  71. continue
  72. @retry(stop=stop_after_attempt(100), wait=wait_fixed(3600), after=after_log)
  73. def meml_main(log):
  74. """日调度主函数:增量 list + 补详情"""
  75. log.info(f'开始运行 {inspect.currentframe().f_code.co_name} 增量爬虫任务 ...')
  76. sql_pool = MySQLConnectionPool(log=log)
  77. if not sql_pool:
  78. log.error("MySQL数据库连接失败")
  79. raise Exception("MySQL数据库连接失败")
  80. try:
  81. try:
  82. run_incremental(log, sql_pool)
  83. except Exception as e:
  84. log.error(f'增量抓取失败: {e}')
  85. try:
  86. update_details_for_pending(log, sql_pool)
  87. except Exception as e:
  88. log.error(f'详情补抓失败: {e}')
  89. try:
  90. # 补采:重访 status='Open' 的 lot,拍卖结束后回写最终 bids/status/current_bid
  91. update_open_lots(log, sql_pool)
  92. except Exception as e:
  93. log.error(f'拍卖结果补采失败: {e}')
  94. except Exception as e:
  95. log.error(f'{inspect.currentframe().f_code.co_name} error: {e}')
  96. finally:
  97. log.info(f'爬虫程序 {inspect.currentframe().f_code.co_name} 运行结束,等待下一轮采集 ...')
  98. def schedule_task():
  99. """每半个月跑一次增量"""
  100. # meml_main(log=logger)
  101. def run_semimonthly():
  102. # 每月 1 号和 15 号执行(半月一次)
  103. from datetime import date
  104. if date.today().day in (1, 15):
  105. meml_main(log=logger)
  106. schedule.every().day.at("05:00").do(run_semimonthly)
  107. while True:
  108. schedule.run_pending()
  109. time.sleep(1)
  110. if __name__ == "__main__":
  111. # meml_main(log=logger)
  112. schedule_task()