微信图片批量保存的办法

已经给官方提建议了,目前还没在最新版看到相关功能。

解决办法:python 脚本(通过pywinauto 控制点击下一张和下载按钮,然后通过判断文件夹里的文件md5值重复阈值来间接达到判断没有下一张的目的)

操作步骤,登录电脑版微信,打开相关聊天记录,打开图片查看功能,界面是这个样子的才能用这个脚本(忽略第二个按钮置灰,刚跑完一次)。

a

 

 

 

 

 

此时是可以点击左上角向右箭头(从左数第二个按钮:鼠标放上去提示文字为下一张)和另存为按钮(顶部最右侧的按钮)的,先点即下载按钮,选择保存路径

比如我选择的是 C:\Users\e3724\Desktop\a 
那么将这个路径在下面python脚本中有用到

from pywinauto import Application
import time
import os
import hashlib
import threading
from pywinauto.keyboard import send_keys
from collections import defaultdict

# 配置参数
SAVE_FOLDER = r"C:\Users\e3724\Desktop\a"  # 目标保存路径
MAX_REPEAT = 2  # MD5重复阈值(达到此次数则停止)
WAIT_DELAY = 0.01  # 操作延迟(秒),如果在电脑上图片没被看过,则此数字要调大至1.0~3.0左右,因为第一次用网络加载需要时间
MONITOR_INTERVAL = 1  # 监控线程扫描间隔(秒)

# 全局变量:文件名→MD5映射(线程安全)
file_md5_map = {}
# 全局变量:MD5出现次数统计
md5_count = defaultdict(int)
# 线程锁
map_lock = threading.Lock()

def calculate_md5(file_path):
    """计算文件MD5值"""
    if not os.path.exists(file_path):
        return None
    try:
        hash_md5 = hashlib.md5()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_md5.update(chunk)
        return hash_md5.hexdigest()
    except Exception as e:
        print(f"计算MD5失败:{str(e)}")
        return None

def folder_monitor():
    """后台线程:监控文件夹并更新MD5映射"""
    global file_md5_map, md5_count
    while True:
        if os.path.exists(SAVE_FOLDER):
            for filename in os.listdir(SAVE_FOLDER):
                if filename.endswith(".jpg") and filename.split(".")[0].isdigit():
                    file_path = os.path.join(SAVE_FOLDER, filename)
                    with map_lock:
                        current_md5 = calculate_md5(file_path)
                        if current_md5:
                            # 处理文件更新(如覆盖保存)
                            if filename in file_md5_map:
                                old_md5 = file_md5_map[filename]
                                md5_count[old_md5] -= 1
                                if md5_count[old_md5] <= 0:
                                    del md5_count[old_md5]
                            # 更新映射和计数
                            file_md5_map[filename] = current_md5
                            md5_count[current_md5] += 1
        time.sleep(MONITOR_INTERVAL)

def wait_for_file(filename, timeout=10):
    """等待文件被监控线程捕获"""
    start_time = time.time()
    while time.time() - start_time < timeout:
        with map_lock:
            if filename in file_md5_map:
                return True
        time.sleep(0.5)
    return False

def final_dedup():
    """流程结束后统一删除重复文件(只保留第一个出现的)"""
    with map_lock:
        # 按文件名排序(确保按顺序保留第一个)
        sorted_files = sorted(file_md5_map.keys(), key=lambda x: int(x.split(".")[0]))
        md5_first_occurrence = {}  # 记录MD5首次出现的文件名
        duplicate_files = []       # 记录重复文件

        for filename in sorted_files:
            md5 = file_md5_map[filename]
            if md5 not in md5_first_occurrence:
                md5_first_occurrence[md5] = filename
            else:
                duplicate_files.append(filename)

        # 删除重复文件
        for filename in duplicate_files:
            file_path = os.path.join(SAVE_FOLDER, filename)
            if os.path.exists(file_path):
                try:
                    os.remove(file_path)
                    print(f"最终去重:删除重复文件 {filename}")
                except Exception as e:
                    print(f"删除重复文件失败 {filename}:{str(e)}")

        return len(sorted_files) - len(duplicate_files), len(duplicate_files)

def auto_download_wechat_images(window_title, next_btn_text="下一张", save_btn_text="另存为"):
    # 记录开始时间
    start_time = time.time()
    try:
        # 启动监控线程
        monitor_thread = threading.Thread(target=folder_monitor, daemon=True)
        monitor_thread.start()
        print("已启动文件夹监控线程...")
        time.sleep(2)

        # 连接窗口
        app = Application(backend="uia").connect(title_re=f".*{window_title}.*", timeout=10)
        main_window = app.window(title_re=f".*{window_title}.*")
        main_window.set_focus()
        print(f"已连接到窗口:{main_window.window_text()}")

        click_count = 1
        stop_flag = False

        while not stop_flag:
            # 查找下一张按钮
            next_btn = None
            for btn in main_window.descendants(control_type="Button"):
                btn_text = btn.window_text().strip()
                if next_btn_text in btn_text or "→" in btn_text:
                    next_btn = btn
                    break

            if not next_btn or not next_btn.is_enabled():
                print("未找到可用的下一张按钮,停止流程")
                break

            # 查找另存为按钮
            save_btn = None
            for btn in main_window.descendants(control_type="Button"):
                btn_text = btn.window_text().strip()
                if save_btn_text in btn_text or "保存" in btn_text:
                    save_btn = btn
                    break

            if not save_btn or not save_btn.is_enabled():
                print("未找到可用的另存为按钮,跳过")
                next_btn.click_input()
                click_count += 1
                time.sleep(WAIT_DELAY)
                continue

            # 保存当前图片
            current_filename = f"{click_count}.jpg"
            print(f"\n===== 处理第{click_count}张图片({current_filename})=====")
            save_btn.click_input()
            time.sleep(WAIT_DELAY )  # 等待对话框

            # 输入文件名(重试机制)
            save_success = False
            for _ in range(3):
                try:
                    send_keys(current_filename.split(".")[0])  # 输入数字
                    time.sleep(0.5)
                    send_keys("{ENTER}")
                    save_success = True
                    break
                except:
                    time.sleep(1)

            if not save_success:
                print("保存失败,跳过")
                next_btn.click_input()
                click_count += 1
                continue

            # 等待文件被监控线程捕获
            if not wait_for_file(current_filename):
                print(f"警告:{current_filename} 未被监控到,可能保存路径错误")
                next_btn.click_input()
                click_count += 1
                continue

            # 检查MD5重复情况
            with map_lock:
                current_md5 = file_md5_map[current_filename]
                current_md5_count = md5_count[current_md5]

            print(f"当前MD5:{current_md5},累计出现次数:{current_md5_count}")

            # 首次重复时输出日志
            if current_md5_count == 2:
                print(f"⚠️ MD5首次重复(当前文件:{current_filename})")
            # 达到重复阈值时停止流程
            elif current_md5_count >= MAX_REPEAT:
                print(f"⚠️ MD5重复达到阈值({MAX_REPEAT}次),停止流程")
                stop_flag = True
                break

            # 点击下一张
            next_btn.click_input()
            click_count += 1
            time.sleep(WAIT_DELAY * 2)

        # 流程结束后统一去重
        print("\n开始最终去重...")
        total_valid, total_duplicate = final_dedup()
        
        # 计算总耗时
        end_time = time.time()
        total_time = end_time - start_time
        minutes = int(total_time // 60)
        seconds = total_time % 60

        print(f"\n===== 结果统计 =====")
        print(f"总下载文件数:{len(file_md5_map)}")
        print(f"去重后保留:{total_valid} 张")
        print(f"删除重复文件:{total_duplicate} 张")
        print(f"总耗时:{minutes}分{seconds:.2f}秒")

    except Exception as e:
        # 异常时也统计耗时
        end_time = time.time()
        total_time = end_time - start_time
        print(f"流程出错:{str(e)},已运行{total_time:.2f}秒")

if __name__ == "__main__":
    input("请确认路径正确且手动测试过保存功能,按回车开始...")

    auto_download_wechat_images(
        window_title="图片查看",
        next_btn_text="下一张",
        save_btn_text="另存为"
    )

  ubuntu系统,需要这个括号里的图(save_button

import pyautogui
import time
import os
import subprocess
import hashlib

# 配置参数
SAVE_BUTTON_IMG = "save_button.png"  # 保存按钮截图
WAIT_TIME = 0.3  # 操作间隔隔(秒)
OUTPUT_DIR = os.path.expanduser("/home/swt/桌面/a")  # 保存目录
FILE_COUNTER = 1  # 文件名计数器(从1开始)
RECENT_FILES = []  # 最近保存的文件路径


def get_file_md5(file_path):
    """计算文件MD5"""
    if not os.path.exists(file_path):
        return None
    md5_hash = hashlib.md5()
    with open(file_path, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            md5_hash.update(chunk)
    return md5_hash.hexdigest()


def ensure_output_dir():
    """确保保存目录存在"""
    os.makedirs(OUTPUT_DIR, exist_ok=True)
    print(f"图片将保存到:{OUTPUT_DIR}")


def find_wechat_image_window():
    """通过wmctrl查找微信图片窗口"""
    try:
        result = subprocess.check_output(
            "wmctrl -l | grep -i '图片'",  # 替换为实际窗口标题关键词
            shell=True,
            text=True
        )
        return result.split()[0]  # 返回窗口ID
    except subprocess.CalledProcessError:
        print("未找到微信图片窗口,请先打开图片查看模式")
        return None


def activate_window(window_id):
    """激活窗口"""
    subprocess.run(f"wmctrl -i -a {window_id}", shell=True)
    time.sleep(WAIT_TIME)


def click_save_button():
    """点击保存按钮"""
    try:
        button_pos = pyautogui.locateOnScreen(
            SAVE_BUTTON_IMG,
            confidence=0.7,
            grayscale=True
        )
        if button_pos:
            pyautogui.click(pyautogui.center(button_pos))
            print("已点击保存按钮")
            return True
        print("未找到保存按钮")
        return False
    except Exception as e:
        print(f"按钮识别错误:{e}")
        return False


def handle_save_dialog():
    """处理保存对话框,直接输入纯数字文件名"""
    global FILE_COUNTER
    time.sleep(WAIT_TIME)  # 等待对话框弹出

    # 1. 全选默认文件名(确保覆盖)
    pyautogui.hotkey("ctrl", "a")
    #time.sleep(0.5)

    # 2. 直接输入当前计数器(纯数字,不带扩展名)
    pyautogui.typewrite(str(FILE_COUNTER))
    #time.sleep(0.5)

    # 3. 按Enter确认保存
    pyautogui.press("enter")
    print(f"已保存为:{FILE_COUNTER}")

    # 4. 记录文件路径并递增计数器
    # 注:实际保存的文件会自动带扩展名(微信默认添加)
    saved_path = os.path.join(OUTPUT_DIR, f"{FILE_COUNTER}")  # 后续系统会自动补全扩展名
    RECENT_FILES.append(saved_path)
    FILE_COUNTER += 1  # 下次保存+1
    time.sleep(WAIT_TIME)


def next_image():
    """切换到下一张图片"""
    pyautogui.press("right")
    print("切换到下一张图片")
    time.sleep(WAIT_TIME)


def check_md5_duplicate():
    """检查最近2张图片MD5是否一致"""
    if len(RECENT_FILES) >= 2:
        # 补全实际文件名(微信会自动添加扩展名,如.png)
        file1 = find_actual_file(RECENT_FILES[-2])
        file2 = find_actual_file(RECENT_FILES[-1])
        if not file1 or not file2:
            return False
        md5_1 = get_file_md5(file1)
        md5_2 = get_file_md5(file2)
        if md5_1 and md5_2 and md5_1 == md5_2:
            print(f"\n检测到连续2张图片MD5一致:{md5_1}")
            return True
    return False


def find_actual_file(base_name):
    """查找带扩展名的实际文件(如输入1,实际可能是1.png)"""
    for ext in [".png", ".jpg", ".jpeg"]:  # 常见图片扩展名
        candidate = f"{base_name}{ext}"
        if os.path.exists(candidate):
            return candidate
    return None  # 未找到对应文件


def batch_save_images(max_count=20):
    """批量保存图片,支持纯数字文件名和重复检测"""
    ensure_output_dir()
    window_id = find_wechat_image_window()
    if not window_id:
        return

    activate_window(window_id)

    for i in range(max_count):
        print(f"\n处理第{i+1}/{max_count}张图片")
        if click_save_button():
            handle_save_dialog()
            # 检查连续2张是否重复
            if check_md5_duplicate():
                print("程序终止:连续2张图片重复")
                return
        else:
            print("跳过当前图片")
        next_image()

    print("\n批量保存完成(已达最大数量)")


if __name__ == "__main__":
    input("请确保已打开微信图片查看窗口,按Enter开始...")
    batch_save_images(max_count=20)

  

posted @ 2025-10-03 22:14  漫漫人生路总会错几步  阅读(36)  评论(0)    收藏  举报