python(60) : 考试助手-WEB版

 1.说明

考完试查看正确答案(错题解析)截图, 进行ocr提取文字用于再次考试快速检索题目

WEB版说明 : 运行后会打开启动web服务, 2秒后打开浏览器跳转页面(局域网内可分享地址打开), 在界面搜索关键字即可

需要先提取图片中的文字, 参考: python(59) : 多线程调用大模型ocr提取图片文本-优快云博客

功能特性

  • 🔍 智能搜索:根据图片关联的文本内容进行搜索
  • 🎯 高级搜索:支持使用 &(与)和 |(或)操作符进行复杂查询
  • 🖼️ 图片浏览:支持在搜索结果中前后翻页浏览图片
  • 🌐 Web 界面:现代化的响应式 Web 界面,支持移动端访问
  • 📊 统计信息:显示数据库中的图片总数
  • 🚀 自动启动:启动后自动打开浏览器访问

项目结构

考试助手/
├── answer_search_web.py    # Flask Web 应用主文件
├── templates/
│   └── index.html          # 前端页面
├── resource/               # 资源目录
│   └── [目录名]/
│       ├── imgs/           # 图片目录
│       │   └── *.png
│       └── texts/          # 文本目录
│           └── *.txt
├── requirements.txt        # Python 依赖
└── README.md              # 项目说明文档

安装说明

1. 环境要求

  • Python 3.7+
  • pip

2. 安装依赖

pip install -r requirements.txt

或者手动安装:

pip install Flask==3.0.0 flask_cors==5.0.0 Pillow==10.1.0

使用方法

1. 准备资源文件

确保项目根目录下有 resource 文件夹,结构如下:

resource/
├── 目录1/
│   ├── imgs/          # 存放图片文件(.png格式)
│   └── texts/         # 存放对应的文本文件(.txt格式,文件名与图片对应)
├── 目录2/
│   ├── imgs/
│   └── texts/
└── ...

注意

  • 文本文件名和图片文件名需要对应(例如:1.txt 对应 1.png
  • 文本文件内容将用于搜索匹配

2. 运行应用

python answer_search_web.py

启动后,应用会:

  1. 自动加载 resource 目录下的所有图片和文本数据
  2. 在 2 秒后自动打开浏览器
  3. 默认访问地址:http://[本机IP]:5000/

3. 使用搜索功能

基本搜索

在搜索框中输入关键词,系统会自动搜索包含该关键词的图片。

高级搜索
  • 与操作(&)关键词1&关键词2

    • 表示同时包含两个关键词的图片
    • 示例:Python&Flask 查找同时包含 "Python" 和 "Flask" 的图片
  • 或操作(|)关键词1|关键词2

    • 表示包含任意一个关键词的图片
    • 示例:Python|Java 查找包含 "Python" 或 "Java" 的图片
浏览结果
  • 使用 上一张 / 下一张 按钮浏览搜索结果
  • 搜索结果会显示当前图片位置(如:共 10 张,当前第 3 张)

API 文档

1. 搜索接口

端点POST /api/search

请求体

{
  "keyword": "搜索关键词"
}

响应

{
  "success": true,
  "count": 5,
  "results": ["图片路径1", "图片路径2", ...],
  "message": "找到 5 张包含 \"关键词\" 的图片"
}

2. 根据路径获取图片

端点GET /api/image_by_path?path=图片路径

响应:返回图片文件(PNG 格式)

3. 获取统计信息

端点GET /api/stats

响应

{
  "total_images": 100
}

打包成可执行文件

使用 PyInstaller 将应用打包成独立的可执行文件:

# 安装 PyInstaller
pip install pyinstaller

# 打包(使用图标)
pyinstaller -F -i maomi.ico answer_search_web.py --clean --noconfirm

打包后的可执行文件位于 dist/ 目录下。

注意:打包时需要确保 templates 目录和 resource 目录与可执行文件在同一目录下。

技术栈

  • 后端:Flask 3.0.0
  • 跨域支持:Flask-CORS 5.0.0
  • 图片处理:Pillow 10.1.0
  • 前端:原生 HTML/CSS/JavaScript

注意事项

  1. 资源目录:确保 resource 目录存在且包含有效的图片和文本文件
  2. 端口占用:默认使用 5000 端口,如果被占用需要修改代码中的端口号
  3. 文件编码:文本文件需要使用 UTF-8 编码
  4. 图片格式:目前支持 PNG 格式图片
  5. 网络访问:应用默认监听 0.0.0.0,局域网内其他设备也可以访问

常见问题

Q: 启动后没有自动打开浏览器?

A: 可以手动访问控制台显示的地址,通常是 http://[本机IP]:5000/

Q: 搜索不到结果?

A: 检查以下几点:

  • resource 目录是否存在
  • 文本文件内容是否正确
  • 搜索关键词是否与文本内容匹配

Q: 图片无法显示?

A: 检查图片路径是否正确,确保图片文件存在且可读

许可证

本项目仅供学习和个人使用。

2.python代码(answer_search_web.py)

import os
import tkinter as tk
# from tkinter import messagebox

from PIL import Image, ImageTk

# 全局变量存储图片文本字典
img_text_dict = {}

# pip install pyinstaller Pillow 
# pyinstaller -F -i maomi.ico answer_search_gui.py --clean --noconfirm

def load_image_text_data():
    """加载图片和文本数据"""
    global img_text_dict
    
    # 目录配置
    current_dir = os.getcwd()
    print(current_dir)
    #current_dir = r'F:\my\python\mypy\考试\瓴羊高级数据研发工程师认证2'
    
    # 遍历资源目录
    ks_dirs = []
    resource_dir = os.path.join(current_dir, 'resource')
    
    if not os.path.exists(resource_dir):
        print(f"警告: 资源目录不存在 {resource_dir}")
        return
    
    for item in os.listdir(resource_dir):
        item_path = os.path.join(resource_dir, item)
        if os.path.isdir(item_path):
            ks_dirs.append(item_path)
    
    # 检索文件
    for path in ks_dirs:
        texts_path = os.path.join(path, 'texts')
        imgs_path = os.path.join(path, 'imgs')
        
        if not os.path.exists(texts_path):
            continue
            
        for fil in os.listdir(texts_path):
            text_file = os.path.join(texts_path, fil)
            try:
                with open(text_file, 'r', encoding='utf-8') as f:
                    content = f.read()
                    img_name = fil.split('.')[0] + '.png'
                    img_path = os.path.join(imgs_path, img_name)
                    img_text_dict[img_path] = content.strip().replace(' ', '')
            except Exception as e:
                print(f"读取文件错误 {text_file}: {e}")
    
    print(f"加载了 {len(img_text_dict)} 张图片数据")


def find_keys_by_value(dictionary, target_value):
    """
    根据目标值在字典中查找所有匹配的键。
    支持 &(与)和 |(或)操作符:
        - 如果 target_value 包含 '&',则要求所有分隔的值都包含在字典值中(与)。
        - 如果 target_value 包含 '|',则只要有一个分隔的值包含在字典值中即可(或)。
        - 如果都不包含,则进行普通包含匹配。

    Args:
        dictionary (dict): 要搜索的字典,值应为字符串或可迭代的容器(如列表、集合等)
        target_value (str): 要查找的目标值,支持 & 和 | 分隔

    Returns:
        list: 包含所有匹配键的列表
    """

    # 辅助函数:检查 value 是否包含 sub
    def contains(value, sub):
        if isinstance(value, str):
            return sub in value
        elif isinstance(value, (list, tuple, set)):
            return sub in value
        else:
            return str(sub) in str(value)

    result = []
    for key, value in dictionary.items():
        if '&' in target_value:
            # 使用 & 分割,所有部分都必须包含(与)
            parts = target_value.split('&')
            if all(contains(value, part.strip()) for part in parts):
                result.append(key)
        elif '|' in target_value:
            # 使用 | 分割,任意一个部分包含即可(或)
            parts = target_value.split('|')
            if any(contains(value, part.strip()) for part in parts):
                result.append(key)
        else:
            # 普通包含匹配
            if contains(value, target_value):
                result.append(key)
    return result


class ImageSearchApp:
    def __init__(self, root, img_text_dict):
        # 设置主窗口
        self.root = root
        self.img_text_dict = img_text_dict
        self.root.title("图片搜索器")
        self.root.geometry("1200x700")
        self.root.configure(bg="#f0f0f0")

        # 存储搜索结果
        self.search_results = []
        self.current_index = 0

        # 创建UI组件
        self.create_widgets()

    def create_widgets(self):
        # 创建搜索框和导航按钮框架(合并为一个框架)
        search_nav_frame = tk.Frame(self.root, bg="#f0f0f0", pady=20)
        search_nav_frame.pack(fill=tk.X, padx=20)

        # 搜索标签
        tk.Label(search_nav_frame, text="搜索关键字:", bg="#f0f0f0", font=("Arial", 12)).pack(side=tk.LEFT, padx=5)

        # 搜索输入框
        self.search_entry = tk.Entry(search_nav_frame, font=("Arial", 12), width=40)
        self.search_entry.pack(side=tk.LEFT, padx=5)
        self.search_entry.bind('<Return>', lambda event: self.perform_search())
        # 绑定输入事件,自动搜索
        self.search_entry.bind('<KeyRelease>', lambda event: self.perform_search())

        # 重置按钮
        reset_btn = tk.Button(
            search_nav_frame,
            text="重置",
            command=self.reset_search,
            bg="#FF9800",
            fg="white",
            font=("Arial", 10, "bold"),
            padx=10
        )
        reset_btn.pack(side=tk.LEFT, padx=5)

        # 搜索按钮
        search_btn = tk.Button(
            search_nav_frame,
            text="搜索",
            command=self.perform_search,
            bg="#4CAF50",
            fg="white",
            font=("Arial", 10, "bold"),
            padx=10
        )
        search_btn.pack(side=tk.LEFT, padx=5)

        # 搜索结果信息标签 - 新增部分
        self.result_info_label = tk.Label(
            search_nav_frame,
            text="",
            bg="#f0f0f0",
            font=("Arial", 10),
            fg="#666666"
        )
        self.result_info_label.pack(side=tk.LEFT, padx=15)

        # 导航按钮
        self.prev_btn = tk.Button(
            search_nav_frame,
            text="上一张",
            command=self.prev_image,
            state=tk.DISABLED,
            bg="#2196F3",
            fg="white",
            font=("Arial", 10),
            padx=15
        )
        self.prev_btn.pack(side=tk.LEFT, padx=5)

        self.next_btn = tk.Button(
            search_nav_frame,
            text="下一张",
            command=self.next_image,
            state=tk.DISABLED,
            bg="#2196F3",
            fg="white",
            font=("Arial", 10),
            padx=15
        )
        self.next_btn.pack(side=tk.LEFT, padx=5)

        # 搜索提示信息(放在搜索框和按钮的下一行)
        hint_label = tk.Label(
            self.root, 
            text="搜索提示: 支持使用 &(与)和 |(或)操作符。 例如:关键词1&关键词2 表示同时包含两个关键词, 关键词1|关键词2 表示包含任意一个关键词。",
            bg="#f0f0f0", 
            font=("Arial", 9),
            fg="#666666",
            wraplength=800,
            justify=tk.LEFT
        )
        hint_label.pack(anchor=tk.W, padx=20, pady=(0, 10))

        # 图片展示框架
        self.image_frame = tk.Frame(self.root, bg="white", bd=2, relief=tk.SUNKEN)
        self.image_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)

        # 图片标签
        self.image_label = tk.Label(self.image_frame, bg="white")
        self.image_label.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)

        # 图片信息标签
        self.info_label = tk.Label(self.root, text="", bg="#f0f0f0", font=("Arial", 10))
        self.info_label.pack(pady=5, anchor=tk.W, padx=20)

        # 初始提示
        self.image_label.config(text="请输入关键字并搜索图片")

        # 搜索初始化
        self.perform_search()

    def reset_search(self):
        """重置搜索:清空搜索框并执行搜索"""
        self.search_entry.delete(0, tk.END)
        self.perform_search()

    def perform_search(self):
        """执行搜索操作"""
        keyword = self.search_entry.get().strip()

        # if not keyword:
        #     messagebox.showwarning("提示", "请输入搜索关键字")
        #     return

        # 在图片列表中搜索包含关键字的项
        filelist = find_keys_by_value(self.img_text_dict, keyword)
        self.search_results = [(index, filename) for index, filename in enumerate(filelist)]

        # 更新UI
        if self.search_results:
            self.current_index = 0
            self.update_image_display()
            self.update_nav_buttons()
            self.info_label.config(text=f"找到 {len(self.search_results)} 张包含 '{keyword}' 的图片")
        else:
            self.image_label.config(text=f"没有找到包含 '{keyword}' 的图片", image='')  # 新增 image='' 清空当前图片
            self.prev_btn.config(state=tk.DISABLED)
            self.next_btn.config(state=tk.DISABLED)
            self.result_info_label.config(text="")
            self.info_label.config(text=f"未找到包含 '{keyword}' 的图片")

    def update_image_display(self):
        """更新当前显示的图片"""
        if not self.search_results:
            return

        index, image_name = self.search_results[self.current_index]

        # 实际应用中,这里应该是您的图片路径
        # 这里使用示例图片,实际使用时请替换为您的图片路径
        # 为了演示,我们使用Pillow创建一个简单的图像
        try:
            # 尝试打开真实图片(如果存在)
            print(os.path.basename(image_name))
            if os.path.exists(image_name):
                img = Image.open(image_name)
            else:
                # 如果图片不存在,创建一个带有文字的示例图片
                img = Image.new('RGB', (400, 300), color='lightgray')

                # 注意:实际使用时需要安装Pillow库,并取消下面的注释
                # from PIL import ImageDraw, ImageFont
                # draw = ImageDraw.Draw(img)
                # font = ImageFont.truetype("arial.ttf", 24)  # 可能需要调整字体路径
                # draw.text((50, 130), image_name, font=font, fill=(0, 0, 0))

            # 调整图片大小以适应窗口
            img.thumbnail((900, 500))
            photo = ImageTk.PhotoImage(img)

            # 更新图片标签
            self.image_label.config(image=photo)
            self.image_label.image = photo  # 保持引用
            self.result_info_label.config(
                text=f"共 {len(self.search_results)} 张,当前第 {self.current_index + 1} 张")


        except Exception as e:
            self.image_label.config(text=f"无法加载图片: {str(e)}")

    def update_nav_buttons(self):
        """更新导航按钮状态"""
        if len(self.search_results) <= 1:
            self.prev_btn.config(state=tk.DISABLED)
            self.next_btn.config(state=tk.DISABLED)
        else:
            self.prev_btn.config(state=tk.NORMAL if self.current_index > 0 else tk.DISABLED)
            self.next_btn.config(state=tk.NORMAL if self.current_index < len(self.search_results) - 1 else tk.DISABLED)
            self.result_info_label.config(text=f"共 {len(self.search_results)} 张,当前第 {self.current_index + 1} 张")

    def prev_image(self):
        """显示上一张图片"""
        if self.current_index > 0:
            self.current_index -= 1
            self.update_image_display()
            self.update_nav_buttons()

    def next_image(self):
        """显示下一张图片"""
        if self.current_index < len(self.search_results) - 1:
            self.current_index += 1
            self.update_image_display()
            self.update_nav_buttons()


if __name__ == "__main__":
    print("正在加载图片数据...")
    load_image_text_data()
    root = tk.Tk()
    app = ImageSearchApp(root, img_text_dict)
    root.mainloop()

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值