前几日因为删除了某一个分类,数据是已经删掉了,但是本地的图片数据还存在的,但是又不想把所有的图片全删了重建图片,于是想了个办法。
从数据库里把所有的封面图数据全部取出来,打印在一个txt文本里,然后跟图片目录下的图片文件做一个匹配筛选,如果不匹配则删除,这样就不会影响内容还在的图片内容。
使用的Python处理,直接把SQL下载到本地处理即可。
Python代码:
import os
import re
import urllib.parse
import configparser
import argparse
# 默认配置信息
DEFAULT_CONFIG = {
'SQL_FILE_PATH': 'f:\\code\\新建文件夹\\xxxx.sql', # SQL文件路径
'LOCAL_IMAGE_ROOT': 'f:\\code\\新建文件夹\\vod', # 本地图片根目录
'OUTPUT_TXT_PATH': 'f:\\code\\新建文件夹\\image_urls.txt', # 输出的TXT文件路径
'URL_DOMAIN': '123.com', # URL域名
'URL_PREFIX': 'mac://', # URL前缀
'URL_PATH_PREFIX': '/upload/vod/', # URL路径前缀
'LOCAL_PATH_PREFIX': '/vod' # 本地路径前缀
}
# 全局配置变量
CONFIG = {}
def load_config(config_file=None):
"""
加载配置文件,如果配置文件不存在,则使用默认配置
"""
global CONFIG
CONFIG = DEFAULT_CONFIG.copy()
if config_file and os.path.exists(config_file):
try:
parser = configparser.ConfigParser()
parser.read(config_file, encoding='utf-8')
if 'Settings' in parser:
for key in DEFAULT_CONFIG:
if key in parser['Settings']:
CONFIG[key] = parser['Settings'][key]
print(f"已从配置文件加载配置: {config_file}")
except Exception as e:
print(f"加载配置文件失败: {e},将使用默认配置")
else:
print("未指定配置文件或配置文件不存在,将使用默认配置")
# 打印当前配置
print("当前配置:")
for key, value in CONFIG.items():
print(f" {key}: {value}")
def create_default_config(config_file):
"""
创建默认配置文件
"""
try:
parser = configparser.ConfigParser()
parser['Settings'] = DEFAULT_CONFIG
with open(config_file, 'w', encoding='utf-8') as f:
parser.write(f)
print(f"已创建默认配置文件: {config_file}")
return True
except Exception as e:
print(f"创建默认配置文件失败: {e}")
return False
def extract_urls_from_sql_file():
"""
从SQL文件中提取图片URL数据
"""
urls = []
try:
sql_file_path = CONFIG['SQL_FILE_PATH']
url_domain = CONFIG['URL_DOMAIN']
url_prefix = CONFIG['URL_PREFIX']
url_path_prefix = CONFIG['URL_PATH_PREFIX']
print(f"开始解析SQL文件: {sql_file_path}")
# 使用正则表达式直接查找所有符合特定格式的URL
# 动态构建正则表达式,使用配置中的域名和路径前缀
domain_escaped = url_domain.replace('.', '\\.')
pattern = f"{url_prefix}{domain_escaped}{url_path_prefix}[^'\"]+"
with open(sql_file_path, 'r', encoding='utf-8', errors='ignore') as file:
content = file.read()
matches = re.findall(pattern, content)
for match in matches:
# 将URL前缀替换为http://以便后续处理
url = match.replace(url_prefix, 'http://')
urls.append(url)
print(f"从SQL文件提取到 {len(urls)} 个URL")
# 如果没有找到任何URL,尝试使用更宽松的正则表达式
if len(urls) == 0:
print("使用备用方法提取URL...")
backup_pattern = f"['\"]((mac|http)://{domain_escaped}{url_path_prefix}[^'\"]+)['\"]"
matches = re.findall(backup_pattern, content)
for match in matches:
url = match[0].replace(url_prefix, 'http://')
urls.append(url)
print(f"备用方法提取到 {len(urls)} 个URL")
return urls
except Exception as e:
print(f"解析SQL文件失败: {e}")
return []
def extract_file_paths(urls):
"""
从URL中提取文件路径,并删除前缀部分
例如: http://123.com/upload/vod/9123.jpg
提取为: /vod/9123.jpg
"""
file_paths = []
url_path_prefix = CONFIG['URL_PATH_PREFIX']
local_path_prefix = CONFIG['LOCAL_PATH_PREFIX']
for url in urls:
try:
# 解析URL
parsed_url = urllib.parse.urlparse(url)
# 获取路径部分
path = parsed_url.path
if path and url_path_prefix in path: # 确保路径不为空且包含配置的路径前缀
# 删除前缀部分,只保留本地路径前缀及之后的部分
clean_path = path.replace(url_path_prefix, local_path_prefix)
file_paths.append(clean_path)
except Exception as e:
print(f"解析URL失败: {url}, 错误: {e}")
print(f"成功提取 {len(file_paths)} 个文件路径")
return file_paths
def save_urls_to_txt(urls, file_paths):
"""
将URL和文件路径保存到TXT文件中
"""
try:
output_txt_path = CONFIG['OUTPUT_TXT_PATH']
with open(output_txt_path, 'w', encoding='utf-8') as f:
# 写入原始URL
f.write("=== 原始URL ===\n")
for url in urls:
f.write(f"{url}\n")
# 写入提取的文件路径
f.write("\n=== 提取的文件路径 ===\n")
for path in file_paths:
f.write(f"{path}\n")
print(f"已将URL和文件路径保存到: {output_txt_path}")
return True
except Exception as e:
print(f"保存到TXT文件失败: {e}")
return False
def get_all_local_images(root_dir):
"""
获取本地所有图片文件的路径,并确保路径格式与数据库路径一致
"""
local_images = []
local_path_prefix = CONFIG['LOCAL_PATH_PREFIX']
try:
print(f"开始扫描本地图片目录: {root_dir}")
for root, _, files in os.walk(root_dir):
for file in files:
# 检查是否为图片文件(简单判断扩展名)
if file.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')):
# 获取相对于根目录的路径
rel_path = os.path.join(root, file).replace(root_dir, '')
# 标准化路径分隔符为 /
rel_path = rel_path.replace('\\', '/')
if not rel_path.startswith('/'):
rel_path = '/' + rel_path
# 确保路径格式与数据库路径一致(以配置的本地路径前缀开头)
# 添加本地路径前缀,使本地路径格式与数据库路径一致
rel_path = local_path_prefix + rel_path
local_images.append(rel_path)
print(f"本地找到 {len(local_images)} 个图片文件")
return local_images
except Exception as e:
print(f"扫描本地图片目录失败: {e}")
return []
def compare_files(local_images, db_file_paths):
"""
比较本地文件和数据库文件路径,输出匹配和不匹配的情况
现在比较的是标准化后的路径,确保更准确的匹配
"""
if not db_file_paths:
print("警告: 数据库文件路径列表为空,无法进行比对")
return None, None
matched_files = []
unmatched_files = []
# 打印一些路径示例,帮助调试
if db_file_paths:
print(f"数据库路径示例: {db_file_paths[0]}")
if local_images:
print(f"本地路径示例: {local_images[0]}")
try:
for local_image in local_images:
# 标准化比较,确保路径格式一致
if local_image in db_file_paths:
matched_files.append(local_image)
else:
unmatched_files.append(local_image)
print(f"匹配的文件数量: {len(matched_files)}")
print(f"不匹配的文件数量: {len(unmatched_files)}")
# 将匹配和不匹配的文件保存到TXT文件
output_txt_path = CONFIG['OUTPUT_TXT_PATH']
with open(output_txt_path, 'a', encoding='utf-8') as f:
f.write("\n=== 匹配的文件 ===\n")
for file in matched_files:
f.write(f"{file}\n")
f.write("\n=== 不匹配的文件 ===\n")
for file in unmatched_files:
f.write(f"{file}\n")
print(f"已将匹配和不匹配的文件信息追加到: {output_txt_path}")
return matched_files, unmatched_files
except Exception as e:
print(f"比对文件过程中发生错误: {e}")
return None, None
def delete_unmatched_files(unmatched_files):
"""
删除不匹配的文件
"""
if not unmatched_files:
print("没有不匹配的文件需要删除")
return
print(f"发现 {len(unmatched_files)} 个不匹配的文件")
print("是否删除这些文件? (y/n)")
choice = input().strip().lower()
if choice != 'y':
print("已取消删除操作")
return
deleted_count = 0
error_count = 0
local_image_root = CONFIG['LOCAL_IMAGE_ROOT']
local_path_prefix = CONFIG['LOCAL_PATH_PREFIX']
# 打印一些路径示例,帮助调试
if unmatched_files:
print(f"不匹配文件路径示例: {unmatched_files[0]}")
print(f"本地图片根目录: {local_image_root}")
try:
for i, file_path in enumerate(unmatched_files):
# 将相对路径转换为绝对路径
# 注意:文件路径格式为 /vod/xxx,需要去掉前面的 /vod 并添加 LOCAL_IMAGE_ROOT
# 确保路径分隔符正确(Windows使用反斜杠)
rel_path = file_path.replace(local_path_prefix, '', 1)
if rel_path.startswith('/'):
rel_path = rel_path[1:]
# 使用正确的路径连接方式
abs_path = os.path.normpath(os.path.join(local_image_root, rel_path))
# 打印前10个文件的详细路径信息,帮助调试
if i < 10:
print(f"处理文件 {i+1}:")
print(f" 原始路径: {file_path}")
print(f" 相对路径: {rel_path}")
print(f" 绝对路径: {abs_path}")
print(f" 文件存在: {os.path.exists(abs_path)}")
try:
if os.path.exists(abs_path):
os.remove(abs_path)
deleted_count += 1
else:
error_count += 1
# 只打印前100个不存在的文件,避免输出过多
if error_count <= 100:
print(f"文件不存在: {abs_path}")
except Exception as e:
error_count += 1
# 只打印前100个错误,避免输出过多
if error_count <= 100:
print(f"删除文件失败: {abs_path}, 错误: {e}")
# 每删除100个文件显示一次进度
if (i + 1) % 100 == 0 or i == len(unmatched_files) - 1:
print(f"进度: {i+1}/{len(unmatched_files)} ({((i+1)/len(unmatched_files))*100:.2f}%)")
print(f"删除完成。成功: {deleted_count}, 失败: {error_count}")
except Exception as e:
print(f"删除文件过程中发生错误: {e}")
def parse_arguments():
"""
解析命令行参数
"""
parser = argparse.ArgumentParser(description='VOD图片处理工具 - 比对SQL文件中的图片URL与本地图片文件')
parser.add_argument('-c', '--config', help='配置文件路径')
parser.add_argument('--create-config', help='创建默认配置文件', action='store_true')
parser.add_argument('--sql', help='SQL文件路径')
parser.add_argument('--vod', help='本地图片根目录')
parser.add_argument('--output', help='输出TXT文件路径')
parser.add_argument('--domain', help='URL域名')
parser.add_argument('--url-prefix', help='URL前缀')
parser.add_argument('--url-path', help='URL路径前缀')
parser.add_argument('--local-path', help='本地路径前缀')
return parser.parse_args()
def main():
# 解析命令行参数
args = parse_arguments()
# 创建默认配置文件
if args.create_config:
config_file = args.config or 'vod_pic_config.ini'
create_default_config(config_file)
return
# 加载配置
load_config(args.config)
# 命令行参数覆盖配置文件
if args.sql:
CONFIG['SQL_FILE_PATH'] = args.sql
if args.vod:
CONFIG['LOCAL_IMAGE_ROOT'] = args.vod
if args.output:
CONFIG['OUTPUT_TXT_PATH'] = args.output
if args.domain:
CONFIG['URL_DOMAIN'] = args.domain
if args.url_prefix:
CONFIG['URL_PREFIX'] = args.url_prefix
if args.url_path:
CONFIG['URL_PATH_PREFIX'] = args.url_path
if args.local_path:
CONFIG['LOCAL_PATH_PREFIX'] = args.local_path
try:
# 从SQL文件获取URL
urls = extract_urls_from_sql_file()
print(f"提取到的URL数量: {len(urls)}")
if urls and len(urls) > 0:
print(f"URL示例: {urls[0]}")
# 从URL提取文件路径
db_file_paths = extract_file_paths(urls)
print(f"提取到的数据库文件路径数量: {len(db_file_paths)}")
if db_file_paths and len(db_file_paths) > 0:
print(f"数据库文件路径示例: {db_file_paths[0]}")
# 保存URL和文件路径到TXT文件
save_urls_to_txt(urls, db_file_paths)
# 获取本地所有图片文件
local_images = get_all_local_images(CONFIG['LOCAL_IMAGE_ROOT'])
print(f"本地图片文件数量: {len(local_images)}")
if local_images and len(local_images) > 0:
print(f"本地图片路径示例: {local_images[0]}")
# 比较文件并输出结果
matched_files, unmatched_files = compare_files(local_images, db_file_paths)
# 删除不匹配的文件
if unmatched_files and len(unmatched_files) > 0:
delete_unmatched_files(unmatched_files)
print("处理完成")
except Exception as e:
print(f"执行过程中发生错误: {e}")
if __name__ == "__main__":
# 显示欢迎信息
print("=== VOD图片处理工具 ===")
print("功能:比对SQL文件中的图片URL与本地图片文件,删除不匹配的本地文件")
print("使用 -h 或 --help 查看帮助信息")
print("使用 --create-config 创建默认配置文件")
print("")
# 解析命令行参数
args = parse_arguments()
# 如果是创建配置文件,直接执行
if args.create_config:
config_file = args.config or 'vod_pic_config.ini'
create_default_config(config_file)
print("请编辑配置文件后再次运行程序")
else:
# 否则,询问用户是否继续执行
# 加载配置
load_config(args.config)
# 命令行参数覆盖配置文件
if args.sql:
CONFIG['SQL_FILE_PATH'] = args.sql
if args.vod:
CONFIG['LOCAL_IMAGE_ROOT'] = args.vod
if args.output:
CONFIG['OUTPUT_TXT_PATH'] = args.output
if args.domain:
CONFIG['URL_DOMAIN'] = args.domain
if args.url_prefix:
CONFIG['URL_PREFIX'] = args.url_prefix
if args.url_path:
CONFIG['URL_PATH_PREFIX'] = args.url_path
if args.local_path:
CONFIG['LOCAL_PATH_PREFIX'] = args.local_path
print("\n当前配置:")
print(f"SQL文件路径: {CONFIG['SQL_FILE_PATH']}")
print(f"本地图片根目录: {CONFIG['LOCAL_IMAGE_ROOT']}")
print(f"输出TXT文件路径: {CONFIG['OUTPUT_TXT_PATH']}")
print(f"URL域名: {CONFIG['URL_DOMAIN']}")
print(f"URL前缀: {CONFIG['URL_PREFIX']}")
print(f"URL路径前缀: {CONFIG['URL_PATH_PREFIX']}")
print(f"本地路径前缀: {CONFIG['LOCAL_PATH_PREFIX']}")
print("\n是否继续执行? (y/n)")
choice = input().strip().lower()
if choice == 'y':
main()
else:
print("已取消执行")
VOD图片处理工具使用说明
项目背景
在视频点播(VOD)系统维护过程中,经常需要对本地存储的图片文件与数据库中记录的图片URL进行比对和清理。本工具旨在解决这一问题,通过从SQL文件中提取图片URL,并与本地图片文件进行比对,找出不匹配的文件并提供删除功能,以便维护存储空间的整洁和高效。
功能需求
- 从SQL备份文件中提取图片URL数据
- 将URL转换为标准化的文件路径格式
- 扫描本地图片目录,获取所有图片文件路径
- 比对数据库中的图片路径与本地图片文件路径
- 输出匹配和不匹配的文件列表
- 提供删除不匹配文件的功能
- 支持配置文件和命令行参数,方便灵活使用
使用方法
安装依赖
本工具使用Python 3编写,无需安装额外的第三方库,仅使用Python标准库。
配置方式
工具支持三种配置方式,优先级从高到低为:
- 命令行参数
- 配置文件
- 默认配置
工作流程
- 加载配置(从命令行参数、配置文件或默认配置)
- 从SQL文件中提取图片URL
- 将URL转换为标准化的文件路径格式
- 扫描本地图片目录,获取所有图片文件路径
- 比对数据库中的图片路径与本地图片文件路径
- 输出匹配和不匹配的文件列表到TXT文件
- 询问用户是否删除不匹配的文件
- 如果用户确认,删除不匹配的文件
注意事项
- 在运行删除操作前,请确保已备份重要文件
- 配置文件中的路径请使用绝对路径,避免路径解析错误
- Windows系统下路径分隔符使用双反斜杠
\
- 如果SQL文件较大,提取URL可能需要较长时间
- 删除操作不可逆,请谨慎确认
常见问题
Q: 为什么没有找到任何URL?
A: 请检查配置中的URL域名、前缀和路径前缀是否与SQL文件中的URL格式匹配。工具会尝试使用宽松模式再次提取,如果仍然失败,可能是SQL文件中的URL格式与预期不符。
Q: 为什么有些文件显示不存在?
A: 可能是路径转换过程中出现问题,请检查配置中的本地路径前缀是否正确,以及本地图片根目录是否正确。
Q: 如何只查看匹配结果而不删除文件?
A: 在询问是否删除文件时,输入n
即可跳过删除操作,结果已保存在输出TXT文件中。
扩展与改进
- 添加图形用户界面(GUI),提升用户体验
- 支持更多的URL格式和文件类型
- 添加文件移动功能,将不匹配的文件移动到备份目录而非直接删除
- 添加日志记录功能,记录详细的操作过程
- 支持多线程处理,提高大量文件处理的效率
- All rights reserved.
- No part of this website, including text and images, may be reproduced, modified, distributed, or transmitted in any form or by any means, without the prior written permission of the author.
- Unauthorized commercial use is strictly prohibited.
- Unauthorized personal use is strictly prohibited.