当前位置: 首页 > news >正文

读取文件夹内的pdf装换成npg给vlm分类人工确认然后填入excel vlmapi速度挺快 qwen3-vl-plus webbrowser.open

调用同义百炼需要的三个key获取-CSDN博客

import os
import json
from pathlib import Path
import fitz  # PyMuPDF
from PIL import Image
import pandas as pd
import time
import logging
from dotenv import load_dotenv
import http.client
from urllib.parse import urlparse
import base64
from flask import Flask, request, jsonify, send_file
import threading
import webbrowser
import io
import re# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)class PDFToImageConverter:@staticmethoddef convert_pdf_to_pil_images(pdf_path):"""将PDF文件转换为PIL图像对象列表(不保存到磁盘)Args:pdf_path (str): PDF文件路径Returns:list: PIL图像对象列表"""logger.info(f"正在转换PDF为内存图像: {pdf_path}")try:# 打开PDF文件pdf_document = fitz.open(pdf_path)pil_images = []# 遍历每一页for page_num in range(len(pdf_document)):try:# 获取页面page = pdf_document[page_num]# 渲染页面为图像mat = fitz.Matrix(2.0, 2.0)  # 2x zoom for better qualitypix = page.get_pixmap(matrix=mat)# 直接转换为PIL图像而不保存到磁盘img_data = pix.tobytes("ppm")pil_image = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)pil_images.append(pil_image)logger.info(f"已转换PDF第 {page_num + 1} 页为内存图像")except Exception as page_error:logger.error(f"处理PDF第 {page_num + 1} 页时出错: {str(page_error)}")continue  # 继续处理其他页面pdf_document.close()return pil_imagesexcept Exception as e:logger.error(f"打开PDF文件 {pdf_path} 时出错: {str(e)}")return []class VLMClassifier:def __init__(self):"""初始化VLM分类器"""dotenv_path = r'E:\code\apikey\.env'load_dotenv(dotenv_path)self.api_url = os.getenv('VLM_OPENAI_API_URL')self.model_name = os.getenv('VLM_MODEL_NAME')self.api_key = os.getenv('VLM_OPENAI_API_KEY')if not all([self.api_url, self.model_name, self.api_key]):raise ValueError("缺少VLM API配置,请检查.env文件")def encode_pil_image(self, pil_image):"""将PIL图像编码为base64Args:pil_image (PIL.Image): PIL图像对象Returns:str: base64编码的图像"""# 将PIL图像保存到内存中的字节流buffer = io.BytesIO()pil_image.save(buffer, format='PNG')# 编码为base64return base64.b64encode(buffer.getvalue()).decode('utf-8')def classify_image(self, pil_image):"""使用VLM对图像进行分类Args:pil_image (PIL.Image): PIL图像对象Returns:dict: 分类结果"""logger.info("正在分类内存中的图像")# 编码图像base64_image = self.encode_pil_image(pil_image)# 构造消息messages = [{"role": "user","content": [{"type": "text","text": "这张图是折弯,卷圆还是组装图,回答格式json{类别:折弯}"},{"type": "image_url","image_url": {"url": f"data:image/png;base64,{base64_image}"}}]}]# 解析URLparsed = urlparse(f"{self.api_url}/chat/completions")host, path = parsed.hostname, parsed.path# 创建连接conn = http.client.HTTPSConnection(host)# 构造请求体request_body = {"model": self.model_name,"messages": messages,"temperature": 0.7}# 发送请求headers = {"Content-Type": "application/json","Authorization": f"Bearer {self.api_key}"}conn.request("POST",path,body=json.dumps(request_body),headers=headers)# 获取响应response = conn.getresponse()if response.status != 200:error_msg = response.read().decode('utf-8')conn.close()raise Exception(f"VLM服务器错误: {response.status} - {error_msg}")# 解析响应response_data = response.read().decode('utf-8')data = json.loads(response_data)conn.close()# 提取分类结果try:content = data['choices'][0]['message']['content']# 处理可能的Markdown包装if content.startswith("```json"):content = content[7:]  # 移除 ```jsonif content.endswith("```"):content = content[:-3]  # 移除 ```result = json.loads(content)logger.info(f"分类结果: {result}")return resultexcept Exception as e:logger.error(f"解析VLM响应失败: {e}")logger.error(f"原始响应: {content}")raiseclass ExcelUpdater:@staticmethoddef extract_drawing_number(filename):"""从文件名中提取图号(由数字和横杠组成)Args:filename (str): 文件名Returns:str: 提取的图号,如果没有找到则返回原文件名(不含扩展名)"""# 使用正则表达式查找由数字和横杠组成的图号match = re.search(r'[\d-]+', filename)if match:drawing_number = match.group()logger.info(f"从文件名 '{filename}' 中提取图号: {drawing_number}")return drawing_number# 如果没有找到匹配的图号,则返回文件名(不含扩展名)drawing_number = Path(filename).stemlogger.info(f"未找到标准图号,使用文件名作为图号: {drawing_number}")return drawing_number@staticmethoddef update_excel(excel_path, file_name, category):"""更新Excel文件中的类别信息Args:excel_path (str): Excel文件路径file_name (str): PDF文件名(不含扩展名)category (str): 分类结果"""logger.info(f"正在更新Excel: {excel_path}")# 提取PDF文件的图号pdf_drawing_number = ExcelUpdater.extract_drawing_number(file_name)logger.info(f"准备更新Excel,图号: {pdf_drawing_number}, 文件名: {file_name}, 类别: {category}")# 读取Excel文件if os.path.exists(excel_path):df = pd.read_excel(excel_path)logger.info(f"成功读取Excel文件,现有 {len(df)} 行数据")else:# 如果文件不存在,创建新的DataFramedf = pd.DataFrame()logger.info("Excel文件不存在,创建新的DataFrame")# 确保有文件名列filename_column = Noneif len(df.columns) > 0:filename_column = df.columns[0]  # 总是使用第一列logger.info(f"使用第一列作为文件名列: {filename_column}")else:# 如果没有找到合适的文件名列,创建一个filename_column = '文件名'if df.empty:df[filename_column] = []logger.info(f"未找到文件名列,使用列名: {filename_column}")# 确保有类别列category_column = '类别'if category_column not in df.columns:df[category_column] = ''logger.info(f"创建类别列: {category_column}")else:logger.info(f"找到类别列: {category_column}")# 查找对应行 - 基于图号匹配而不是完整文件名匹配row_index = Nonefor idx, row in df.iterrows():if filename_column in row:excel_filename = str(row[filename_column])# 提取Excel行中文件的图号excel_drawing_number = ExcelUpdater.extract_drawing_number(Path(excel_filename).stem)# 如果图号匹配,则认为是同一图纸的不同文件if excel_drawing_number == pdf_drawing_number:row_index = idxlogger.info(f"在Excel中找到图号匹配行,索引: {idx}, 图号: {pdf_drawing_number}")break# 如果找不到对应行,创建新行if row_index is None:new_row = pd.Series({filename_column: file_name + '.pdf', category_column: ''})df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True)row_index = len(df) - 1logger.info(f"未找到匹配行,创建新行,索引: {row_index}")# 更新类别old_category = df.at[row_index, category_column]df.at[row_index, category_column] = categorylogger.info(f"更新类别信息: 行 {row_index}, 原类别: {old_category}, 新类别: {category}")# 保存Excel文件try:df.to_excel(excel_path, index=False)logger.info(f"已成功更新Excel文件,保存路径: {excel_path}")except Exception as e:logger.error(f"保存Excel文件失败: {str(e)}")raiselogger.info(f"已更新Excel文件,行索引: {row_index}, 类别: {category}")@staticmethoddef load_existing_categories(excel_path):"""从Excel文件中加载已有的类别信息Args:excel_path (str): Excel文件路径Returns:dict: 图号到类别的映射"""existing_categories = {}if not os.path.exists(excel_path):return existing_categoriestry:df = pd.read_excel(excel_path)if df.empty or len(df.columns) == 0:return existing_categoriesfilename_column = df.columns[0]category_column = '类别'if category_column not in df.columns:return existing_categoriesfor _, row in df.iterrows():if filename_column in row and pd.notna(row[filename_column]):filename = str(row[filename_column])# 提取图号drawing_number = ExcelUpdater.extract_drawing_number(Path(filename).stem)# 获取类别category = row[category_column] if category_column in row and pd.notna(row[category_column]) else Noneif category:existing_categories[drawing_number] = categorylogger.info(f"从Excel中读取已有类别: {drawing_number} -> {category}")except Exception as e:logger.error(f"读取Excel文件时出错: {str(e)}")return existing_categories# 全局变量存储当前状态
current_state = {'pdf_files': [],'current_index': 0,'current_pdf': None,'current_image': None,'current_pil_image': None,'processing': False,'converter': None,'classifier': None,'updater': None,'pdf_directory': '','excel_path': '','image_paths': {},'pil_images_cache': {},'existing_categories': {}  # 存储从Excel中读取的现有类别
}app = Flask(__name__)@app.route('/')
def index():"""Web界面主页"""return '''<!DOCTYPE html><html><head><title>PDF审核工具</title><meta charset="utf-8"><style>body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }.container { max-width: 1200px; margin: 0 auto; background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }button { padding: 12px 24px; margin: 10px; font-size: 16px; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.3s; }.btn-bend { background-color: #4CAF50; color: white; }.btn-bend:hover { background-color: #45a049; }.btn-roll { background-color: #2196F3; color: white; }.btn-roll:hover { background-color: #0b7dda; }.btn-assembly { background-color: #9C27B0; color: white; }.btn-assembly:hover { background-color: #7B1FA2; }.btn-custom { background-color: #FF9800; color: white; }.btn-custom:hover { background-color: #e68a00; }.btn-skip { background-color: #f44336; color: white; }.btn-skip:hover { background-color: #d32f2f; }.btn-reset { background-color: #607D8B; color: white; }.btn-reset:hover { background-color: #455A64; }button:disabled { background-color: #cccccc; cursor: not-allowed; }#image-container { margin-top: 20px; text-align: center; min-height: 300px; display: flex; align-items: center; justify-content: center; }img { max-width: 100%; max-height: 70vh; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); }.status { margin: 20px 0; padding: 15px; background-color: #e3f2fd; border-radius: 4px; }.controls { margin: 20px 0; text-align: center; }h1 { color: #333; text-align: center; }.progress-bar { height: 20px; background-color: #e0e0e0; border-radius: 10px; overflow: hidden; margin: 10px 0; }.progress-fill { height: 100%; background-color: #4CAF50; transition: width 0.3s; }.classification-result { margin: 15px 0; padding: 10px; background-color: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px; text-align: center;font-weight: bold;font-size: 18px;}/* 模态框样式 */.modal {position: fixed;z-index: 1000;left: 0;top: 0;width: 100%;height: 100%;background-color: rgba(0,0,0,0.4);}.modal-content {background-color: #fefefe;margin: 15% auto;padding: 20px;border: 1px solid #888;width: 300px;border-radius: 5px;text-align: center;}.close {color: #aaa;float: right;font-size: 28px;font-weight: bold;cursor: pointer;}.close:hover,.close:focus {color: black;text-decoration: none;cursor: pointer;}#custom-category {width: 100%;padding: 10px;margin: 10px 0;border: 1px solid #ccc;border-radius: 4px;box-sizing: border-box;}#custom-category:focus {outline: none;border-color: #2196F3;}</style></head><body><div class="container"><h1>PDF审核工具</h1><div class="status"><p>当前文件: <strong><span id="current-file">无</span></strong></p><p>进度: <span id="progress">0/0</span></p><div class="progress-bar"><div class="progress-fill" id="progress-bar-fill" style="width: 0%"></div></div></div><div class="classification-result" id="classification-result" style="display: none;">分类结果: <span id="classification-text"></span></div><div class="controls"><button onclick="classifyAs('折弯')" class="btn-bend" id="bend-btn" disabled>折弯</button><button onclick="classifyAs('卷圆')" class="btn-roll" id="roll-btn" disabled>卷圆</button><button onclick="classifyAs('组装图')" class="btn-assembly" id="assembly-btn" disabled>组装图</button><button onclick="openCustomModal()" class="btn-custom" id="custom-btn" disabled>自定义</button><button onclick="skipFile()" class="btn-skip" id="skip-btn" disabled>跳过</button><button onclick="resetProcessing()" class="btn-reset" id="reset-btn">重置</button></div><!-- 自定义分类输入模态框 --><div id="custom-modal" class="modal" style="display: none;"><div class="modal-content"><span class="close" onclick="closeCustomModal()">&times;</span><h2>自定义分类</h2><input type="text" id="custom-category" placeholder="请输入分类名称"><button onclick="confirmCustomCategory()" class="btn-custom">确认</button></div></div><div id="image-container"><p id="no-image-msg">尚未开始处理</p><img id="preview-image" src="" alt="PDF预览" style="display:none;"></div></div><script>let currentCustomCategory = '';// 页面加载后自动开始处理window.onload = function() {startProcessing();};function startProcessing() {fetch('/start_processing', {method: 'POST'}).then(response => response.json()).then(data => {updateStatus(data);if (data.current_file) {enableButtons();loadImage();}}).catch(error => {alert('处理过程中出现错误: ' + error.message);});}function classifyAs(category) {fetch('/confirm_and_next', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({classification: category})}).then(response => response.json()).then(data => {updateStatus(data);if (data.current_file) {loadImage();} else {hideImage();disableButtons();}}).catch(error => {alert('处理过程中出现错误: ' + error.message);});}function skipFile() {fetch('/reject_and_next', {method: 'POST'}).then(response => response.json()).then(data => {updateStatus(data);if (data.current_file) {loadImage();} else {hideImage();disableButtons();}}).catch(error => {alert('处理过程中出现错误: ' + error.message);});}function resetProcessing() {fetch('/reset_processing', {method: 'POST'}).then(response => response.json()).then(data => {updateStatus(data);hideImage();disableButtons();hideClassification();});}function openCustomModal() {const modal = document.getElementById('custom-modal');const input = document.getElementById('custom-category');if (modal && input) {modal.style.display = 'block';input.focus();}}function closeCustomModal() {const modal = document.getElementById('custom-modal');const input = document.getElementById('custom-category');if (modal) {modal.style.display = 'none';}if (input) {input.value = '';}}function confirmCustomCategory() {const input = document.getElementById('custom-category');if (input) {const customCategory = input.value.trim();if (customCategory) {classifyAs(customCategory);closeCustomModal();input.value = '';} else {alert('请输入分类名称');}}}function loadImage() {const timestamp = new Date().getTime(); // 防止缓存const img = document.getElementById('preview-image');const msg = document.getElementById('no-image-msg');if (img && msg) {img.src = '/image/' + timestamp;img.style.display = 'inline';msg.style.display = 'none';}}function hideImage() {const img = document.getElementById('preview-image');const msg = document.getElementById('no-image-msg');if (img && msg) {img.style.display = 'none';msg.style.display = 'block';msg.textContent = '处理已完成';}}function updateStatus(data) {const currentFileEl = document.getElementById('current-file');const progressEl = document.getElementById('progress');const progressBarFill = document.getElementById('progress-bar-fill');if (currentFileEl) {currentFileEl.textContent = data.current_file || '无';}if (progressEl) {progressEl.textContent = data.progress || '0/0';}if (progressBarFill && data.total > 0) {const percentage = (data.processed / data.total) * 100;progressBarFill.style.width = percentage + '%';} else if (progressBarFill) {progressBarFill.style.width = '0%';}// 显示分类结果if (data.classification) {showClassification(data.classification);} else {hideClassification();}}function showClassification(classification) {const textEl = document.getElementById('classification-text');const resultEl = document.getElementById('classification-result');if (textEl && resultEl) {textEl.textContent = classification;resultEl.style.display = 'block';}}function hideClassification() {const resultEl = document.getElementById('classification-result');if (resultEl) {resultEl.style.display = 'none';}}function enableButtons() {const buttons = ['bend-btn', 'roll-btn', 'assembly-btn', 'custom-btn', 'skip-btn'];buttons.forEach(id => {const btn = document.getElementById(id);if (btn) btn.disabled = false;});}function disableButtons() {const buttons = ['bend-btn', 'roll-btn', 'assembly-btn', 'custom-btn', 'skip-btn'];buttons.forEach(id => {const btn = document.getElementById(id);if (btn) btn.disabled = true;});}// 添加键盘事件监听器,支持回车确认自定义分类document.addEventListener('keydown', function(event) {const modal = document.getElementById('custom-modal');if (event.key === 'Enter' && modal && modal.style.display === 'block') {confirmCustomCategory();}});</script></body></html>'''# 添加一个新的路由来提供图片,使用时间戳作为参数
@app.route('/image/<timestamp>')
def serve_image(timestamp):"""提供当前图像文件"""logger.info(f"请求图片服务: current_pdf={current_state['current_pdf']}")if current_state['current_pdf'] and current_state['current_pil_image']:# 将PIL图像对象转换为响应img_io = io.BytesIO()current_state['current_pil_image'].save(img_io, 'PNG')img_io.seek(0)return send_file(img_io, mimetype='image/png')logger.warning("没有当前图片可提供")return '', 404# 添加缩略图路由
@app.route('/thumb/<filename>')
def serve_thumbnail(filename):"""提供处理过的PDF文件的缩略图"""logger.info(f"请求缩略图: {filename}")try:# 重新生成缩略图而不是使用缓存的图像pdf_path = os.path.join(current_state['pdf_directory'], filename)if os.path.exists(pdf_path):# 转换PDF第一页为图像pil_images = PDFToImageConverter.convert_pdf_to_pil_images(pdf_path)if pil_images and len(pil_images) > 0:img_io = io.BytesIO()# 调整图像大小作为缩略图thumbnail = pil_images[0].copy()thumbnail.thumbnail((100, 100))thumbnail.save(img_io, 'PNG')img_io.seek(0)return send_file(img_io, mimetype='image/png')else:logger.warning(f"无法从PDF生成图像: {filename}")else:logger.warning(f"PDF文件不存在: {pdf_path}")except Exception as e:logger.error(f"生成缩略图时出错: {str(e)}")logger.warning(f"没有找到文件 {filename} 的缩略图")# 返回一个占位符图像return '', 404@app.route('/start_processing', methods=['POST'])
def start_processing():"""开始处理PDF文件"""global current_state# 初始化配置dotenv_path = r'E:\code\apikey\.env'load_dotenv(dotenv_path)current_state['pdf_directory'] = os.getenv('PDF_EXCEL_pdf_directory')current_state['excel_path'] = os.getenv('PDF_EXCEL_excel_path')logger.info(f"开始处理,PDF目录: {current_state['pdf_directory']}")logger.info(f"Excel文件路径: {current_state['excel_path']}")# 清空之前的缓存current_state['pil_images_cache'] = {}current_state['image_paths'] = {}current_state['existing_categories'] = {}  # 清空现有类别缓存# 初始化组件current_state['converter'] = PDFToImageConverter()current_state['classifier'] = VLMClassifier()current_state['updater'] = ExcelUpdater()# 获取所有PDF文件if not os.path.exists(current_state['pdf_directory']):logger.error(f"PDF目录不存在: {current_state['pdf_directory']}")return jsonify({'error': f'PDF目录不存在: {current_state["pdf_directory"]}'})current_state['pdf_files'] = [f for f in os.listdir(current_state['pdf_directory']) if f.lower().endswith('.pdf')]current_state['current_index'] = 0logger.info(f"找到 {len(current_state['pdf_files'])} 个PDF文件")for i, pdf_file in enumerate(current_state['pdf_files']):logger.info(f"  {i+1}. {pdf_file}")if not current_state['pdf_files']:return jsonify({'error': '目录中没有找到PDF文件'})# 读取Excel中的现有类别信息current_state['existing_categories'] = ExcelUpdater.load_existing_categories(current_state['excel_path'])# 处理第一个文件return process_next_file()@app.route('/confirm_and_next', methods=['POST'])
def confirm_and_next():"""确认当前文件并处理下一个"""global current_state# 获取分类结果data = request.get_json()classification = data.get('classification', '未知')logger.info(f"确认分类: {classification}")# 更新Excelif current_state['current_pdf']:file_name = Path(current_state['current_pdf']).stemlogger.info(f"准备更新Excel,文件名: {file_name}, 分类: {classification}")try:current_state['updater'].update_excel(current_state['excel_path'], file_name, classification)logger.info("Excel更新完成")except Exception as e:logger.error(f"更新Excel时出错: {str(e)}")# 移动到下一个文件current_state['current_index'] += 1return process_next_file()@app.route('/reject_and_next', methods=['POST'])
def reject_and_next():"""拒绝当前文件并处理下一个"""global current_statelogger.info("跳过当前文件")# 移动到下一个文件(不执行任何操作)current_state['current_index'] += 1return process_next_file()@app.route('/reset_processing', methods=['POST'])
def reset_processing():"""重置处理状态"""global current_statelogger.info("重置处理状态")current_state['pdf_files'] = []current_state['current_index'] = 0current_state['current_pdf'] = Nonecurrent_state['current_image'] = Nonecurrent_state['current_pil_image'] = Nonecurrent_state['processing'] = Falsecurrent_state['image_paths'] = {}current_state['pil_images_cache'] = {}current_state['existing_categories'] = {}return jsonify({'current_file': None,'progress': '0/0','processed': 0,'total': 0})def process_next_file():"""处理下一个PDF文件"""global current_statewhile current_state['current_index'] < len(current_state['pdf_files']):# 获取当前PDF文件current_state['current_pdf'] = current_state['pdf_files'][current_state['current_index']]pdf_path = os.path.join(current_state['pdf_directory'], current_state['current_pdf'])# 提取图号file_name = Path(current_state['current_pdf']).stemdrawing_number = ExcelUpdater.extract_drawing_number(file_name)# 检查Excel中是否已有类别existing_category = current_state['existing_categories'].get(drawing_number)if existing_category:logger.info(f"文件 {current_state['current_pdf']} 已有类别 '{existing_category}',跳过处理")current_state['current_index'] += 1continue  # 跳过这个文件,处理下一个logger.info(f"开始处理PDF文件 ({current_state['current_index']+1}/{len(current_state['pdf_files'])}): {pdf_path}")logger.info(f"当前文件图号: {drawing_number}")try:# 检查是否已经转换过该PDFpdf_name = Path(current_state['current_pdf']).stemif pdf_name in current_state['pil_images_cache']:# 如果已经转换过,直接使用已有的图像对象pil_images = current_state['pil_images_cache'][pdf_name]logger.info(f"使用已缓存的图像对象,共 {len(pil_images)} 页")else:# 转换PDF为图像对象pil_images = current_state['converter'].convert_pdf_to_pil_images(pdf_path)# 缓存图像对象current_state['pil_images_cache'][pdf_name] = pil_imageslogger.info(f"新生成图像对象并缓存,共 {len(pil_images)} 页")classification = None# 使用第一页作为预览if pil_images and len(pil_images) > 0:current_state['current_pil_image'] = pil_images[0]logger.info("设置当前PIL图像对象用于预览")# 同时进行分类try:result = current_state['classifier'].classify_image(current_state['current_pil_image'])classification = result.get('类别', '未知')logger.info(f"分类结果: {classification}")except Exception as e:logger.error(f"分类失败: {str(e)}")classification = "分类失败"else:current_state['current_pil_image'] = Nonelogger.warning(f"未生成图像对象 for {current_state['current_pdf']}")return jsonify({'current_file': current_state['current_pdf'],'progress': f'{current_state["current_index"] + 1}/{len(current_state["pdf_files"])}','processed': current_state["current_index"],'total': len(current_state["pdf_files"]),'classification': classification})except Exception as e:logger.error(f"处理文件 {current_state['current_pdf']} 时出错: {str(e)}")# 即使出错也继续处理下一个文件current_state['current_index'] += 1# 已经处理完所有文件logger.info("所有文件处理完成")current_state['current_pdf'] = Nonecurrent_state['current_image'] = Nonecurrent_state['current_pil_image'] = Nonereturn jsonify({'current_file': None,'progress': f'{len(current_state["pdf_files"])}/{len(current_state["pdf_files"])}','processed': len(current_state["pdf_files"]),'total': len(current_state["pdf_files"])})def run_web_interface():"""运行Web界面"""app.run(host='127.0.0.1', port=5000, debug=False, use_reloader=False)def open_browser():"""打开浏览器"""webbrowser.open_new('http://127.0.0.1:5000/')if __name__ == "__main__":print("启动PDF审核工具...")# 启动Web服务线程web_thread = threading.Thread(target=run_web_interface)web_thread.daemon = Trueweb_thread.start()# 等待一秒确保服务器启动后再打开浏览器time.sleep(1)open_browser()print("Web界面已在 http://127.0.0.1:5000 启动")print("请在浏览器中查看界面")print("按 Ctrl+C 退出程序")# 主线程继续运行try:while True:time.sleep(1)except KeyboardInterrupt:print("\n程序已退出")

http://www.dtcms.com/a/527932.html

相关文章:

  • 主流 AI IDE 之一的 CodeBuddy IDE 介绍
  • 展示型网站制作公司做网站好的网站建设公司哪家好
  • MySQL 大表查询优化、超大分页处理、SQL 慢查询优化、主键选择
  • Unity公共Mono模块:非继承脚本也能更新
  • 使用 Flownex 对发电厂周期进行建模
  • 前端基础:JS基础语法
  • 中山网站建设的企业深圳哪些公司做网站
  • 网站 验收访问的网页正在升级中
  • 29-机器学习与大模型开发数学教程-3-3 张量的运算(Einstein求和约定)
  • 天猫网站建设的优势有哪些室内装修3d动态演示效果图
  • 如何提升网站的搜索排名专业外贸网站建设公司价格
  • 【GESP】C++四级真题 luogu-B4361 [GESP202506 四级] 排序
  • 出版社类网站模板手机商城网站源码
  • 旅游网站建设实施方案湖南建筑工程集团
  • 投资融资理财网站模板如何网站专题策划
  • 淄博网站制作优化安装百度到手机桌面
  • 江苏和住房建设厅网站东莞樟木头网站建设公司
  • 内蒙古工程建设招投标中心网站网站编辑适不适合男生做
  • 网站开发的经费预算跨境电商是干嘛的
  • 高端网站定制设计公司果蔬网站规划建设方案
  • 网站开发怎么接入支付宝app管理系统
  • 如何查看网站权重百度下载安装app
  • 甜点网站要怎么做温州网页设计培训学校
  • 谷歌seo网站怎么做产品分类网站虚拟空间购买
  • wordpress全站伪静态电商网站合作
  • 寿光公司做网站wordpress 建网站 vpn
  • 小说阅读网站开发设计做网站的好处和坏处
  • 钓鱼网站制作天门网站
  • 黄冈手机网站建设律师的网站模板
  • 九江做网站如何创建自己的博客网站