代码如下
import os
import time
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from concurrent.futures import ThreadPoolExecutor, as_completed
from tqdm import tqdm
base_url = "" # 要下载文件的基础URL
download_dir = "" # 保存下载文件的目录
max_workers = 10 # 最大并发数
# 下载单个文件
def download_file(file_url, download_dir):
if not os.path.exists(download_dir):
os.makedirs(download_dir)
file_name = file_url.split('/')[-1] # 提取文件名
file_path = os.path.join(download_dir, file_name)
if os.path.isdir(file_path):
print(f"错误:路径 {file_path} 是一个目录,无法作为文件保存!")
return
# 如果文件不存在则开始下载
if not os.path.exists(file_path):
with requests.get(file_url, stream=True) as r:
r.raise_for_status() # 确保请求成功
total_size = int(r.headers.get('content-length', 0)) # 获取文件大小
with open(file_path, 'wb') as f, tqdm(
desc=file_path,
total=total_size,
unit='B',
unit_scale=True,
unit_divisor=1024
) as bar:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
bar.update(len(chunk))
else:
print(f"文件 {file_path} 已存在,跳过下载。")
# 获取目录中的所有文件和子目录链接
def get_links(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
files = [] # 存放文件链接
subdirs = [] # 存放子目录链接
for link in soup.find_all('a'):
href = link.get('href')
if href not in ['../', '/']: # 排除上级目录链接
full_url = urljoin(url, href) # 构建完整URL
if href.endswith('/'):
subdirs.append(full_url) # 是子目录则添加到子目录列表
else:
files.append(full_url) # 是文件则添加到文件列表
return files, subdirs
# 并发下载文件和子目录中的文件
def download_concurrently(base_url, download_dir):
files, subdirs = get_links(base_url)
if not subdirs: # 如果没有子目录,只顺序下载文件
print(f"检测到目录下全是文件,顺序下载...")
for file_url in files:
download_file(file_url, download_dir)
else:
print(f"检测到有子目录,进行并发下载...")
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for subdir in subdirs:
subdir_name = subdir.split('/')[-2]
subdir_path = os.path.join(download_dir, subdir_name)
# 避免文件和目录同名冲突
if os.path.isfile(subdir_path):
print(f"错误:路径 {subdir_path} 已存在且是文件,无法创建为目录!")
continue
futures.append(executor.submit(download_files, subdir, subdir_path))
# 顺序下载当前目录下的文件
for file_url in files:
futures.append(executor.submit(download_file, file_url, download_dir))
# 显示并发任务的进度
for future in as_completed(futures):
try:
future.result()
except Exception as e:
print(f"下载出错: {e}")
# 递归下载子目录中的文件
def download_files(url, download_dir):
files, subdirs = get_links(url)
if not os.path.exists(download_dir):
os.makedirs(download_dir)
for file_url in files:
download_file(file_url, download_dir)
for subdir in subdirs:
subdir_name = subdir.split('/')[-2]
subdir_path = os.path.join(download_dir, subdir_name)
if os.path.isfile(subdir_path):
print(f"错误:路径 {subdir_path} 已存在且是文件,无法创建为目录!")
continue
download_files(subdir, subdir_path)
if __name__ == "__main__":
start_time = time.time() # 记录开始时间
download_concurrently(base_url, download_dir)
end_time = time.time() # 记录结束时间
total_time = end_time - start_time
print(f"总下载时间: {total_time:.2f} 秒")
说明
脚本用于从一个目录中下载文件,并且支持递归处理子目录。它使用了concurrent.futures
库来实现多线程并发下载,从而提高下载速度。脚本的主要功能包括:
- 下载单个文件:使用
requests
模块获取文件,配合tqdm
显示进度条。 - 获取目录链接:通过解析HTML页面,提取当前目录下的所有文件和子目录。
- 并发下载:如果有子目录,则创建线程池并发处理子目录和文件。
- 递归下载:对于每个子目录,递归下载其中的文件。