import os import time import requests from tools.log import logger from tools.utils import Utils class FileDownloader: def __init__(self, url, local_filename, max_retries=1): self.url = url self.local_filename = local_filename self.max_retries = max_retries self.chunk_size = 8192 # 每块数据大小 (8 KB) self.timer = Utils.Timer() self.last_logged_time = 0 self.last_logged_size = 0 self.total_size = 0 def download(self, resume=True): retry_count = 0 retry_delay = 2 while retry_count <= self.max_retries: try: return self._download(resume) except requests.RequestException as e: retry_count += 1 logger.error(f"Download failed: {e}. Retrying {retry_count}/{self.max_retries} after {retry_delay}s...") time.sleep(retry_delay) retry_delay *= 2 # 指数退避 raise Exception(f"Failed to download {self.url} after {self.max_retries} retries") def _download(self, resume): resume_header = {} downloaded_size = 0 # 检查是否需要断点续传 if resume and os.path.exists(self.local_filename): downloaded_size = os.path.getsize(self.local_filename) resume_header = {'Range': f'bytes={downloaded_size}-'} logger.info(f"Resuming download from byte: {downloaded_size}") else: logger.info("Starting new download.") # 发起请求 with requests.get(self.url, headers=resume_header, stream=True, timeout=30) as response: response.raise_for_status() self.total_size = int(response.headers.get('Content-Length', 0)) + downloaded_size with open(self.local_filename, 'ab') as f: start_time = time.time() previous_time = start_time for chunk in response.iter_content(chunk_size=self.chunk_size): if not chunk: # 忽略空数据块 continue f.write(chunk) downloaded_size += len(chunk) current_time = time.time() time_elapsed = int(current_time - previous_time) # 每隔 1 秒更新日志 if time_elapsed >= 1 or self.timer.timer("download", 5): self._log_progress(downloaded_size, self.total_size, start_time, previous_time) previous_time = current_time logger.info(f"Download completed: {self.local_filename}") def _log_progress(self, downloaded_size, total_size, start_time, previous_time): """ 输出下载进度日志,包含当前进度和下载速度。 :param downloaded_size: 已下载大小(字节) :param total_size: 文件总大小(字节) :param start_time: 下载开始时间 :param previous_time: 上一次记录日志的时间 """ percent = (downloaded_size / total_size) * 100 elapsed_time = time.time() - start_time interval_time = time.time() - previous_time # 计算平均速度和瞬时速度 average_speed = downloaded_size / elapsed_time if elapsed_time > 0 else 0 instant_speed = (downloaded_size - self.last_logged_size) / interval_time if interval_time > 0 else 0 # 转换为 MB/s average_speed_mb = average_speed / 1024 / 1024 instant_speed_mb = instant_speed / 1024 / 1024 logger.info(f"{os.path.basename(self.local_filename)} - Downloaded: " f"{downloaded_size / 1024 / 1024:.1f}MB of {total_size / 1024 / 1024:.1f}MB " f"({percent:.2f}%) | {instant_speed_mb:.2f} MB/s") # 更新记录的时间和大小 self.last_logged_time = time.time() self.last_logged_size = downloaded_size