image_downloader.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. """
  2. 图片下载模块
  3. - 卡牌标准图:从 cards_master_v2 的 img_url (腾讯云COS) 下载,HTTP 直接访问
  4. - 交易图:从 MinIO 下载(需配置账号密码),或从本地目录读取
  5. """
  6. import os
  7. import hashlib
  8. import requests
  9. import cv2
  10. import numpy as np
  11. def _url_to_filename(url):
  12. """URL 转本地文件名(md5 防冲突)"""
  13. return hashlib.md5(url.encode("utf-8")).hexdigest() + ".jpg"
  14. def download_card_image(url, save_dir, timeout=15, retries=2):
  15. """
  16. 下载卡牌标准图(COS,HTTP 公开访问)。
  17. 已存在则直接返回路径(断点续传)。
  18. Returns: 本地路径;失败返回 None
  19. """
  20. fname = _url_to_filename(url)
  21. path = os.path.join(save_dir, fname)
  22. if os.path.exists(path) and os.path.getsize(path) > 0:
  23. return path
  24. for attempt in range(retries + 1):
  25. try:
  26. resp = requests.get(url, timeout=timeout)
  27. if resp.status_code == 200 and len(resp.content) > 0:
  28. with open(path, "wb") as f:
  29. f.write(resp.content)
  30. return path
  31. except Exception:
  32. if attempt == retries:
  33. return None
  34. return None
  35. def download_card_images_batch(urls, save_dir, workers=8):
  36. """批量下载卡牌图,返回 {url: local_path or None}"""
  37. from concurrent.futures import ThreadPoolExecutor, as_completed
  38. results = {}
  39. with ThreadPoolExecutor(max_workers=workers) as pool:
  40. futures = {pool.submit(download_card_image, url, save_dir): url for url in urls}
  41. for fut in as_completed(futures):
  42. url = futures[fut]
  43. try:
  44. results[url] = fut.result()
  45. except Exception:
  46. results[url] = None
  47. return results
  48. def load_image_rgb(path):
  49. """读取图片为 RGB numpy 数组"""
  50. if path is None or not os.path.exists(path):
  51. return None
  52. img = cv2.imread(path)
  53. if img is None:
  54. return None
  55. return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  56. # ============ 交易图获取(MinIO / 本地) ============
  57. def get_minio_client(minio_config):
  58. """创建 MinIO 客户端"""
  59. from minio import Minio
  60. return Minio(
  61. minio_config["endpoint"],
  62. access_key=minio_config["access_key"],
  63. secret_key=minio_config["secret_key"],
  64. secure=minio_config.get("secure", False),
  65. )
  66. def download_transaction_image_minio(image_uri, save_dir, minio_config, bucket=None):
  67. """
  68. 从 MinIO 下载交易图。
  69. image_uri 形如 'ebay102/2026-03-25-11-2/xxx.jpg'
  70. bucket 为 None 时,取 image_uri 第一段作为 bucket 名
  71. Returns: 本地路径;失败返回 None
  72. """
  73. if not minio_config.get("access_key"):
  74. return None # 未配置账号密码
  75. parts = image_uri.split("/", 1)
  76. if bucket is None:
  77. bkt, obj = parts[0], parts[1] if len(parts) > 1 else ""
  78. else:
  79. bkt, obj = bucket, image_uri
  80. fname = _url_to_filename(image_uri)
  81. path = os.path.join(save_dir, fname)
  82. if os.path.exists(path) and os.path.getsize(path) > 0:
  83. return path
  84. try:
  85. client = get_minio_client(minio_config)
  86. client.fget_object(bkt, obj, path)
  87. return path if os.path.exists(path) else None
  88. except Exception:
  89. return None
  90. def get_transaction_image_local(image_uri, local_root):
  91. """
  92. 从本地目录读取交易图(若图片已下载到本地)。
  93. local_root: 本地根目录,image_uri 拼接其后
  94. Returns: 本地路径或 None
  95. """
  96. path = os.path.join(local_root, image_uri)
  97. return path if os.path.exists(path) else None