图片自动化下载
本文最后更新于 2025-12-26,文章内容可能已经过时。
一个基于Python的自动化图片下载工具,可以定时从指定链接下载图片,并自动避免重复下载相同内容的图片。
功能特点
自动下载:定时从指定的网络链接下载图片
去重机制:通过MD5哈希值检测重复图片,避免重复下载
灵活配置:可自定义下载间隔时间和保存路径
SSL支持:可选择是否验证SSL证书
图形界面:直观的图形用户界面,易于操作
日志记录:详细的操作日志,便于跟踪下载状态
文件说明
tuku.py:主程序文件,包含图形界面和下载逻辑downloaded_hashes.txt:已下载图片的MD5哈希值记录文件,用于去重tuku/:默认图片保存文件夹
使用方法
确保已安装Python环境
安装依赖包:
pip install requests运行程序:
python tuku.py在图形界面中进行配置:
输入图片链接
选择图片保存路径(默认为程序目录下的tuku文件夹)
设置下载时间间隔(默认3秒)
选择是否验证SSL证书
点击"开始下载"按钮启动自动下载
可通过"停止下载"按钮停止下载过程
可通过"清除历史"按钮重置下载记录,允许重新下载之前的图片
工作原理
程序启动时会加载
downloaded_hashes.txt文件中的哈希值记录点击"开始下载"后,程序会按设定的时间间隔不断从指定链接下载图片
每次下载图片时,会计算图片的MD5哈希值
检查哈希值是否已存在于记录中:
如果存在,则跳过下载
如果不存在,则保存图片并记录哈希值
所有操作都会在日志区域显示
注意事项
请确保网络连接正常
请确保保存路径有写入权限
部分网站可能需要关闭SSL验证才能正常下载
频繁下载可能对目标服务器造成压力,请合理设置下载间隔
许可证
© Halosb 2025 已开源
如有问题,请联系:i@halosb.com
GitHub
代码
import requests
import time
import os
import uuid
import threading
import hashlib
from datetime import datetime
import urllib3
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
import webbrowser
# 禁用SSL警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class WelcomeWindow:
"""程序启动时显示的欢迎和使用提示窗口"""
def __init__(self, root):
self.root = root
self.root.title("Halosb图片自动下载器")
self.root.geometry("600x430") # 增加高度以容纳版权信息
self.root.resizable(False, False)
self.root.configure(padx=20, pady=20)
# 设置中文字体
self.setup_fonts()
# 创建欢迎界面内容
self.create_widgets()
# 居中显示窗口
self.center_window()
def setup_fonts(self):
"""设置支持中文的字体"""
default_font = ('SimHei', 10)
self.root.option_add("*Font", default_font)
def center_window(self):
"""使窗口在屏幕中居中显示"""
self.root.update_idletasks()
width = self.root.winfo_width()
height = self.root.winfo_height()
x = (self.root.winfo_screenwidth() // 2) - (width // 2)
y = (self.root.winfo_screenheight() // 2) - (height // 2)
self.root.geometry('{}x{}+{}+{}'.format(width, height, x, y))
def create_widgets(self):
"""创建欢迎窗口的组件"""
# 标题
title_label = ttk.Label(
self.root,
text="欢迎使用Halosb图片自动下载器",
font=('SimHei', 16, 'bold')
)
title_label.pack(pady=(0, 20))
# 使用说明文本
instructions = """
图片自动下载器使用说明:
1. 功能介绍:
本程序可以定时从指定的网络链接下载图片,并自动避免重复下载相同内容的图片。
2. 使用步骤:
- 在"图片链接设置"中输入要下载图片的网络地址
- 在"存储路径设置"中选择图片保存的文件夹(默认为程序目录下的tuku文件夹)
- 在"下载设置"中设置下载时间间隔(默认3秒)
- 根据需要选择是否验证SSL证书(部分网站可能需要关闭验证)
- 点击"开始下载"按钮启动自动下载
3. 其他功能:
- 程序会自动记录已下载图片,避免重复下载
- 可通过"清除历史记录"按钮重置下载记录
- 日志区域会显示所有操作和下载状态
点击"开始使用"按钮进入程序主界面。
"""
# 创建带滚动条的文本区域显示说明
text_frame = ttk.Frame(self.root)
text_frame.pack(fill=tk.BOTH, expand=True)
self.info_text = scrolledtext.ScrolledText(
text_frame,
wrap=tk.WORD,
width=60,
height=12
)
self.info_text.pack(fill=tk.BOTH, expand=True)
self.info_text.insert(tk.END, instructions)
self.info_text.config(state=tk.DISABLED, padx=10, pady=10)
# 确认按钮
btn_frame = ttk.Frame(self.root)
btn_frame.pack(fill=tk.X, pady=10)
self.start_btn = ttk.Button(
btn_frame,
text="开始使用",
command=self.open_main_window,
width=15
)
self.start_btn.pack(side=tk.RIGHT)
# 版权信息区域
copyright_frame = ttk.Frame(self.root)
copyright_frame.pack(fill=tk.X, pady=10)
# 左侧版权信息
left_copyright = ttk.Frame(copyright_frame)
left_copyright.pack(side=tk.LEFT)
ttk.Label(left_copyright, text="©").pack(side=tk.LEFT)
halosb_link = ttk.Label(left_copyright, text="Halosb", foreground="blue", cursor="hand2")
halosb_link.pack(side=tk.LEFT, padx=(2, 2))
halosb_link.bind("<Button-1>", lambda e: webbrowser.open_new("https://halosb.com"))
ttk.Label(left_copyright, text="|").pack(side=tk.LEFT, padx=(2, 2))
doubao_link = ttk.Label(left_copyright, text="知栖", foreground="blue", cursor="hand2")
doubao_link.pack(side=tk.LEFT, padx=(2, 2))
doubao_link.bind("<Button-1>", lambda e: webbrowser.open_new("https://olny1.com"))
ttk.Label(left_copyright, text="版权所有 · 架构于Python").pack(side=tk.LEFT, padx=(2, 2))
# 右侧邮箱链接
email_link = ttk.Label(copyright_frame, text="联系作者:i@halosb.com", foreground="blue", cursor="hand2")
email_link.pack(side=tk.RIGHT)
email_link.bind("<Button-1>", lambda e: webbrowser.open_new("mailto:i@halosb.com"))
def open_main_window(self):
"""关闭欢迎窗口,打开主程序窗口"""
self.root.destroy() # 关闭欢迎窗口
main_root = tk.Tk()
app = ImageDownloaderGUI(main_root)
main_root.mainloop()
class ImageDownloaderGUI:
def __init__(self, root):
self.root = root
self.root.title("Halosb图片自动下载器")
# 窗口尺寸缩小约1/3(原750x600 → 500x400)
self.root.geometry("500x400")
self.root.resizable(True, True)
# 设置中文字体支持
self.setup_fonts()
# 下载状态控制
self.is_downloading = False
self.download_thread = None
# 已下载图片的哈希记录
self.downloaded_hashes = set()
self.hash_log_file = "downloaded_hashes.txt"
# 默认存储路径
self.default_path = os.path.join(os.getcwd(), "tuku")
# 先创建UI组件(包括log_text)
self.create_widgets()
# 再加载已下载的哈希记录(此时log_text已存在)
self.load_downloaded_hashes()
# 居中显示窗口
self.center_window()
def center_window(self):
"""使窗口在屏幕中居中显示"""
self.root.update_idletasks()
width = self.root.winfo_width()
height = self.root.winfo_height()
x = (self.root.winfo_screenwidth() // 2) - (width // 2)
y = (self.root.winfo_screenheight() // 2) - (height // 2)
self.root.geometry('{}x{}+{}+{}'.format(width, height, x, y))
def setup_fonts(self):
"""设置支持中文的字体"""
default_font = ('SimHei', 10)
self.root.option_add("*Font", default_font)
def create_widgets(self):
"""创建所有UI组件"""
# 创建主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 链接输入区域
url_frame = ttk.LabelFrame(main_frame, text="图片链接设置", padding="5")
url_frame.pack(fill=tk.X, pady=3)
ttk.Label(url_frame, text="图片链接:").grid(row=0, column=0, sticky=tk.W, pady=3)
self.url_entry = ttk.Entry(url_frame)
self.url_entry.grid(row=0, column=1, sticky=tk.EW, pady=3, padx=3)
self.url_entry.insert(0, "请输入图片链接") # 默认链接
# 存储路径设置
path_frame = ttk.LabelFrame(main_frame, text="存储路径设置", padding="5")
path_frame.pack(fill=tk.X, pady=3)
ttk.Label(path_frame, text="保存路径:").grid(row=0, column=0, sticky=tk.W, pady=3)
self.path_entry = ttk.Entry(path_frame)
self.path_entry.grid(row=0, column=1, sticky=tk.EW, pady=3, padx=3)
self.path_entry.insert(0, self.default_path)
self.browse_btn = ttk.Button(path_frame, text="浏览...", command=self.browse_path, width=8)
self.browse_btn.grid(row=0, column=2, padx=3)
# 设置区域
settings_frame = ttk.LabelFrame(main_frame, text="下载设置", padding="5")
settings_frame.pack(fill=tk.X, pady=3)
ttk.Label(settings_frame, text="时间间隔(秒):").grid(row=0, column=0, sticky=tk.W, pady=3)
self.interval_entry = ttk.Entry(settings_frame, width=8)
self.interval_entry.grid(row=0, column=1, sticky=tk.W, pady=3, padx=3)
self.interval_entry.insert(0, "3")
self.verify_ssl_var = tk.BooleanVar(value=False)
self.verify_ssl_check = ttk.Checkbutton(
settings_frame,
text="验证SSL证书",
variable=self.verify_ssl_var
)
self.verify_ssl_check.grid(row=0, column=2, sticky=tk.W, pady=3, padx=3)
# 按钮区域
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X, pady=3)
self.start_btn = ttk.Button(button_frame, text="开始下载", command=self.start_download)
self.start_btn.pack(side=tk.LEFT, padx=3)
self.stop_btn = ttk.Button(button_frame, text="停止下载", command=self.stop_download, state=tk.DISABLED)
self.stop_btn.pack(side=tk.LEFT, padx=3)
self.clear_log_btn = ttk.Button(button_frame, text="清空日志", command=self.clear_log)
self.clear_log_btn.pack(side=tk.RIGHT, padx=3)
self.clear_hashes_btn = ttk.Button(button_frame, text="清除历史", command=self.clear_downloaded_hashes)
self.clear_hashes_btn.pack(side=tk.RIGHT, padx=3)
# 日志区域
log_frame = ttk.LabelFrame(main_frame, text="下载日志", padding="5")
log_frame.pack(fill=tk.BOTH, expand=True, pady=3)
self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD)
self.log_text.pack(fill=tk.BOTH, expand=True)
self.log_text.config(state=tk.DISABLED)
# 配置网格权重,使控件可以随窗口大小调整
url_frame.columnconfigure(1, weight=1)
path_frame.columnconfigure(1, weight=1)
settings_frame.columnconfigure(1, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(4, weight=1) # 日志区域可扩展
def browse_path(self):
"""打开文件浏览器选择保存路径"""
selected_path = filedialog.askdirectory(title="选择图片保存目录")
if selected_path:
self.path_entry.delete(0, tk.END)
self.path_entry.insert(0, selected_path)
def log(self, message):
"""在日志区域显示消息"""
self.log_text.config(state=tk.NORMAL)
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
self.log_text.see(tk.END) # 滚动到最后一行
self.log_text.config(state=tk.DISABLED)
def clear_log(self):
"""清空日志区域"""
self.log_text.config(state=tk.NORMAL)
self.log_text.delete(1.0, tk.END)
self.log_text.config(state=tk.DISABLED)
def load_downloaded_hashes(self):
"""加载已下载图片的哈希记录"""
try:
if os.path.exists(self.hash_log_file):
with open(self.hash_log_file, 'r') as f:
for line in f:
line = line.strip()
if line:
self.downloaded_hashes.add(line)
self.log(f"已加载 {len(self.downloaded_hashes)} 条历史下载记录")
except Exception as e:
self.log(f"加载历史记录失败: {str(e)}")
def save_hash_to_log(self, hash_value):
"""将新下载图片的哈希值保存到日志"""
try:
with open(self.hash_log_file, 'a') as f:
f.write(f"{hash_value}\n")
self.downloaded_hashes.add(hash_value)
except Exception as e:
self.log(f"保存哈希记录失败: {str(e)}")
def clear_downloaded_hashes(self):
"""清除已下载图片的哈希记录"""
if messagebox.askyesno("确认", "确定要清除所有历史下载记录吗?这将允许重新下载之前的图片。"):
try:
if os.path.exists(self.hash_log_file):
os.remove(self.hash_log_file)
self.downloaded_hashes.clear()
self.log("已清除所有历史下载记录")
except Exception as e:
self.log(f"清除历史记录失败: {str(e)}")
def calculate_image_hash(self, image_data):
"""计算图片数据的MD5哈希值"""
md5_hash = hashlib.md5()
md5_hash.update(image_data)
return md5_hash.hexdigest()
def create_directory(self, path):
"""创建指定文件夹,如果不存在的话"""
if not os.path.exists(path):
try:
os.makedirs(path)
self.log(f"已创建文件夹: {path}")
except Exception as e:
self.log(f"创建文件夹失败: {str(e)}")
return False
return True
def save_image_from_url(self, url, verify_ssl, save_path):
"""从指定URL下载图片并保存到指定文件夹,检查是否重复"""
try:
response = requests.get(url, stream=True, timeout=10, verify=verify_ssl)
if response.status_code == 200:
# 读取图片数据
image_data = response.content
# 计算哈希值
image_hash = self.calculate_image_hash(image_data)
# 检查是否已下载过
if image_hash in self.downloaded_hashes:
self.log(f"图片已存在,跳过下载 (哈希值: {image_hash[:8]}...)")
return False
# 处理文件名和扩展名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
file_extension = url.split('.')[-1].split('?')[0].lower()
if len(file_extension) > 5 or file_extension not in ['jpg', 'jpeg', 'png', 'gif', 'bmp']:
content_type = response.headers.get('content-type', '')
if 'image/jpeg' in content_type:
file_extension = 'jpg'
elif 'image/png' in content_type:
file_extension = 'png'
elif 'image/gif' in content_type:
file_extension = 'gif'
else:
file_extension = 'jpg'
filename = f"image_{timestamp}_{uuid.uuid4().hex[:8]}.{file_extension}"
full_path = os.path.join(save_path, filename)
# 保存图片
with open(full_path, 'wb') as file:
file.write(image_data)
# 记录哈希值
self.save_hash_to_log(image_hash)
self.log(f"图片已保存: {full_path} (哈希值: {image_hash[:8]}...)")
return True
else:
self.log(f"无法下载图片,状态码: {response.status_code}")
return False
except Exception as e:
self.log(f"下载图片时出错,请尝试关掉SSL检测: {str(e)}")
return False
def download_loop(self, url, interval, verify_ssl, save_path):
"""下载循环,在单独线程中运行"""
if not self.create_directory(save_path):
self.is_downloading = False
self.update_button_states()
return
count = 1
while self.is_downloading:
self.log(f"第 {count} 次尝试下载...")
self.save_image_from_url(url, verify_ssl, save_path)
count += 1
# 等待指定时间,但允许中途停止
for _ in range(int(interval * 10)):
if not self.is_downloading:
break
time.sleep(0.1)
self.log("下载已停止")
self.update_button_states()
def start_download(self):
"""开始下载过程"""
url = self.url_entry.get().strip()
interval_text = self.interval_entry.get().strip()
verify_ssl = self.verify_ssl_var.get()
save_path = self.path_entry.get().strip()
# 验证输入
if not url:
messagebox.showerror("输入错误", "请输入图片链接")
return
if not save_path:
messagebox.showerror("输入错误", "请指定保存路径")
return
try:
interval = float(interval_text)
if interval <= 0:
raise ValueError
except ValueError:
messagebox.showerror("输入错误", "请输入有效的时间间隔(正数)")
return
# 开始下载
self.is_downloading = True
self.update_button_states()
self.log(f"开始每 {interval} 秒从 {url} 下载图片...")
self.log(f"图片将保存到: {save_path}")
self.log(f"当前已记录 {len(self.downloaded_hashes)} 张已下载图片,将自动跳过重复图片")
self.log("按 '停止下载' 按钮可以停止程序")
# 在新线程中运行下载循环,避免界面卡顿
self.download_thread = threading.Thread(
target=self.download_loop,
args=(url, interval, verify_ssl, save_path),
daemon=True
)
self.download_thread.start()
def stop_download(self):
"""停止下载过程"""
self.is_downloading = False
def update_button_states(self):
"""更新按钮状态"""
if self.is_downloading:
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
self.url_entry.config(state=tk.DISABLED)
self.interval_entry.config(state=tk.DISABLED)
self.verify_ssl_check.config(state=tk.DISABLED)
self.path_entry.config(state=tk.DISABLED)
self.browse_btn.config(state=tk.DISABLED)
self.clear_hashes_btn.config(state=tk.DISABLED)
else:
self.start_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.DISABLED)
self.url_entry.config(state=tk.NORMAL)
self.interval_entry.config(state=tk.NORMAL)
self.verify_ssl_check.config(state=tk.NORMAL)
self.path_entry.config(state=tk.NORMAL)
self.browse_btn.config(state=tk.NORMAL)
self.clear_hashes_btn.config(state=tk.NORMAL)
if __name__ == "__main__":
# 先显示欢迎窗口
welcome_root = tk.Tk()
welcome_window = WelcomeWindow(welcome_root)
welcome_root.mainloop()