苹果CMS一些数据处理记录

前几日因为删除了某一个分类,数据是已经删掉了,但是本地的图片数据还存在的,但是又不想把所有的图片全删了重建图片,于是想了个办法。

从数据库里把所有的封面图数据全部取出来,打印在一个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,并与本地图片文件进行比对,找出不匹配的文件并提供删除功能,以便维护存储空间的整洁和高效。

功能需求

  1. 从SQL备份文件中提取图片URL数据
  2. 将URL转换为标准化的文件路径格式
  3. 扫描本地图片目录,获取所有图片文件路径
  4. 比对数据库中的图片路径与本地图片文件路径
  5. 输出匹配和不匹配的文件列表
  6. 提供删除不匹配文件的功能
  7. 支持配置文件和命令行参数,方便灵活使用

使用方法

安装依赖

本工具使用Python 3编写,无需安装额外的第三方库,仅使用Python标准库。

配置方式

工具支持三种配置方式,优先级从高到低为:

  1. 命令行参数
  2. 配置文件
  3. 默认配置

工作流程

  1. 加载配置(从命令行参数、配置文件或默认配置)
  2. 从SQL文件中提取图片URL
  3. 将URL转换为标准化的文件路径格式
  4. 扫描本地图片目录,获取所有图片文件路径
  5. 比对数据库中的图片路径与本地图片文件路径
  6. 输出匹配和不匹配的文件列表到TXT文件
  7. 询问用户是否删除不匹配的文件
  8. 如果用户确认,删除不匹配的文件

注意事项

  1. 在运行删除操作前,请确保已备份重要文件
  2. 配置文件中的路径请使用绝对路径,避免路径解析错误
  3. Windows系统下路径分隔符使用双反斜杠\
  4. 如果SQL文件较大,提取URL可能需要较长时间
  5. 删除操作不可逆,请谨慎确认

常见问题

Q: 为什么没有找到任何URL?

A: 请检查配置中的URL域名、前缀和路径前缀是否与SQL文件中的URL格式匹配。工具会尝试使用宽松模式再次提取,如果仍然失败,可能是SQL文件中的URL格式与预期不符。

Q: 为什么有些文件显示不存在?

A: 可能是路径转换过程中出现问题,请检查配置中的本地路径前缀是否正确,以及本地图片根目录是否正确。

Q: 如何只查看匹配结果而不删除文件?

A: 在询问是否删除文件时,输入n即可跳过删除操作,结果已保存在输出TXT文件中。

扩展与改进

  1. 添加图形用户界面(GUI),提升用户体验
  2. 支持更多的URL格式和文件类型
  3. 添加文件移动功能,将不匹配的文件移动到备份目录而非直接删除
  4. 添加日志记录功能,记录详细的操作过程
  5. 支持多线程处理,提高大量文件处理的效率
  • 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.
0 comment A文章作者 M管理员
    No Comments Yet. Be the first to share what you think