PP-DocLayoutV3项目实战:从零搭建一个MySQL驱动的文档管理系统

张开发
2026/4/8 7:26:52 15 分钟阅读

分享文章

PP-DocLayoutV3项目实战:从零搭建一个MySQL驱动的文档管理系统
PP-DocLayoutV3项目实战从零搭建一个MySQL驱动的文档管理系统你是不是经常遇到一堆PDF、Word文档想找里面的某个信息却只能一个个打开、一页页翻看或者作为一个开发者想把手里的文档内容结构化方便后续分析和检索却不知道从何下手今天我们就来动手解决这个问题。我将带你从零开始搭建一个结合了AI文档解析和数据库管理的智能文档系统。核心思路很简单用户上传文档系统自动调用PP-DocLayoutV3模型像人一样“看懂”文档的布局把标题、段落、表格这些元素连同它们在页面上的位置信息都提取出来然后整整齐齐地存进MySQL数据库里。最后我们还能基于这些内容实现一个简单的关键词搜索功能。整个过程我们会一步步来从安装MySQL、设计数据库表到用Python Flask写后端接口再到处理文件上传和调用AI模型。即使你之前没怎么接触过全栈开发跟着走一遍也能把这个小项目跑起来真正体验一次从AI模型到数据落地的完整链路。1. 环境准备与项目初始化在开始敲代码之前我们得先把“厨房”收拾好把需要的“食材”和“工具”备齐。这个项目主要需要三样东西Python环境、MySQL数据库以及PP-DocLayoutV3模型。1.1 安装与配置MySQL数据库数据库是我们的“仓库”所有解析出来的文档信息都要存在这里。我们选择MySQL因为它应用广泛学习资源也多。首先你需要安装MySQL。如果你用的是Windows可以去MySQL官网下载安装程序一路“下一步”就行。如果你用的是macOS用Homebrew安装会更方便打开终端输入brew install mysql。Linux用户通常可以用系统自带的包管理器比如Ubuntu下用sudo apt install mysql-server。安装完成后需要启动MySQL服务并设置一个密码。以macOS为例在终端里执行brew services start mysql mysql -u root进入MySQL命令行后设置root用户的密码这里以‘yourpassword’为例实际使用时请换成强密码ALTER USER rootlocalhost IDENTIFIED BY yourpassword; FLUSH PRIVILEGES; exit;现在让我们创建一个专门用于本项目的数据库比如叫smart_doc_dbmysql -u root -p输入密码后执行CREATE DATABASE smart_doc_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE smart_doc_db;看到Database changed的提示说明我们已经切换到这个新数据库里了。1.2 创建Python虚拟环境与安装依赖为了避免不同项目的Python包版本冲突我们为这个项目创建一个独立的虚拟环境。打开终端进入你打算存放项目的目录然后运行# 创建项目文件夹 mkdir ppdoclayout-mysql-system cd ppdoclayout-mysql-system # 创建Python虚拟环境假设你已安装Python3 python3 -m venv venv # 激活虚拟环境 # 在 macOS/Linux 上 source venv/bin/activate # 在 Windows 上 # venv\Scripts\activate # 激活后命令行提示符前通常会出现 (venv) 字样接下来我们需要安装一系列Python包。把下面的内容保存到一个叫requirements.txt的文件里flask2.3.3 flask-cors4.0.0 pymysql1.1.0 werkzeug2.3.7 paddlepaddle2.5.1 paddleocr2.7.1.1 pillow10.0.0 python-multipart0.0.6然后在激活的虚拟环境中运行以下命令来安装它们pip install -r requirements.txt这个过程可能会花点时间特别是安装PaddlePaddle飞桨深度学习框架的时候因为它是PP-DocLayoutV3运行的基础。耐心等待它完成。1.3 获取PP-DocLayoutV3模型PP-DocLayoutV3是百度飞桨团队开源的文档版面分析模型能识别文档中的各种元素。我们通过PaddleOCR来使用它而PaddleOCR在我们上一步安装依赖时已经装好了。模型文件通常会在第一次运行时自动下载但为了确保顺利我们可以先手动初始化一下。在你的项目目录下创建一个简单的Python脚本test_model.pyfrom paddleocr import PPStructure # 这行代码会触发模型下载如果本地没有的话 table_engine PPStructure(recoveryTrue, layoutTrue, show_logFalse) print(“模型初始化成功”)运行这个脚本python test_model.py。如果网络通畅它会开始下载模型文件看到“模型初始化成功”就说明准备好了。模型文件会下载到用户主目录下的.paddleocr文件夹里。2. 设计数据库我们的“数据仓库”我们的系统要存储两种主要信息一是文档本身的元信息比如文件名、上传时间二是文档被解析后的详细内容标题、段落、表格等。所以我们至少需要两张表。回到MySQL命令行确保在smart_doc_db数据库中我们来创建这两张表。2.1 文档信息表 (documents)这张表记录文档的“身份信息”。CREATE TABLE documents ( id INT AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(255) NOT NULL COMMENT ‘上传的文件名’, file_path VARCHAR(500) NOT NULL COMMENT ‘服务器上存储的文件路径’, file_type VARCHAR(20) COMMENT ‘文件类型如pdf, docx’, upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT ‘上传时间’, status VARCHAR(20) DEFAULT ‘uploaded’ COMMENT ‘状态: uploaded, parsing, parsed, failed’ );id是主键每上传一个文档就自动生成一个唯一ID。file_path很重要它告诉系统上传的文件具体放在服务器的哪个位置。status字段用来跟踪文档处理到了哪一步方便我们管理。2.2 文档内容表 (document_contents)这是核心表存储解析出来的所有内容块。CREATE TABLE document_contents ( id INT AUTO_INCREMENT PRIMARY KEY, doc_id INT NOT NULL COMMENT ‘关联的文档ID’, content_type VARCHAR(50) NOT NULL COMMENT ‘内容类型: title, text, table, figure等’, content_text TEXT COMMENT ‘提取的文本内容’, bbox TEXT COMMENT ‘边界框坐标格式为“x1,y1,x2,y2”’, page_num INT COMMENT ‘所在页码’, confidence FLOAT COMMENT ‘模型识别置信度’, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (doc_id) REFERENCES documents(id) ON DELETE CASCADE );doc_id关联到documents表指明这个内容块属于哪个文档。content_type表示这是标题、正文还是表格。bbox保存了这个内容块在页面上的位置左上角和右下角坐标这对于还原版面布局很有用。我们为doc_id和content_type加了索引后面按文档或类型查询时会更快。CREATE INDEX idx_doc_id ON document_contents(doc_id); CREATE INDEX idx_content_type ON document_contents(content_type);创建好后可以执行DESCRIBE documents;和DESCRIBE document_contents;看看表结构确认无误。3. 搭建后端用Flask编写API数据库准备好了现在我们来搭建系统的“大脑”——后端服务。我们用Flask因为它轻量、灵活适合快速构建API。在项目根目录下创建一个名为app.py的文件这就是我们后端的主程序。3.1 初始化Flask应用与数据库连接首先引入必要的模块并设置好Flask应用和数据库连接。from flask import Flask, request, jsonify from flask_cors import CORS import pymysql import os from werkzeug.utils import secure_filename from datetime import datetime import json # 初始化Flask应用 app Flask(__name__) # 允许跨域请求方便前后端分离开发 CORS(app) # 数据库配置 - 请根据你的MySQL设置修改 db_config { ‘host’: ‘localhost’, ‘user’: ‘root’, ‘password’: ‘yourpassword’, # 换成你设置的密码 ‘database’: ‘smart_doc_db’, ‘charset’: ‘utf8mb4’ } # 文件上传配置 UPLOAD_FOLDER ‘./uploads’ ALLOWED_EXTENSIONS {‘pdf’, ‘png’, ‘jpg’, ‘jpeg’, ‘bmp’} app.config[‘UPLOAD_FOLDER’] UPLOAD_FOLDER os.makedirs(UPLOAD_FOLDER, exist_okTrue) def get_db_connection(): “”“获取数据库连接”“” return pymysql.connect(**db_config) def allowed_file(filename): “”“检查文件扩展名是否允许”“” return ‘.’ in filename and filename.rsplit(‘.’, 1)[1].lower() in ALLOWED_EXTENSIONS3.2 实现文件上传API第一个关键API是处理用户上传文档。app.route(‘/api/upload’, methods[‘POST’]) def upload_file(): “”“处理文件上传”“” if ‘file’ not in request.files: return jsonify({‘error’: ‘没有选择文件’}), 400 file request.files[‘file’] if file.filename ‘’: return jsonify({‘error’: ‘文件名为空’}), 400 if file and allowed_file(file.filename): # 生成安全的文件名并保存 filename secure_filename(file.filename) filepath os.path.join(app.config[‘UPLOAD_FOLDER’], filename) file.save(filepath) # 将文档信息存入数据库 conn get_db_connection() cursor conn.cursor() try: sql “““INSERT INTO documents (filename, file_path, file_type, status) VALUES (%s, %s, %s, %s)””” file_type filename.rsplit(‘.’, 1)[1].lower() if ‘.’ in filename else ‘’ cursor.execute(sql, (filename, filepath, file_type, ‘uploaded’)) doc_id cursor.lastrowid # 获取新插入文档的ID conn.commit() # 立即触发解析任务这里简化处理实际可放入队列 # 我们先返回成功解析通过另一个接口异步触发 return jsonify({ ‘message’: ‘文件上传成功’, ‘doc_id’: doc_id, ‘filename’: filename }), 200 except Exception as e: conn.rollback() return jsonify({‘error’: f’数据库错误: {str(e)}‘}), 500 finally: cursor.close() conn.close() else: return jsonify({‘error’: ‘不支持的文件类型’}), 400这个接口做了几件事接收文件、检查类型、安全保存、把文档记录存入documents表并返回新文档的ID。3.3 实现文档解析API上传完成后我们需要另一个API来触发解析。这里会调用PP-DocLayoutV3模型。from paddleocr import PPStructure # 全局初始化一次模型即可避免重复加载 print(“正在加载PP-DocLayoutV3模型首次加载较慢…”) table_engine PPStructure(recoveryTrue, layoutTrue, show_logFalse) print(“模型加载完成”) app.route(‘/api/parse/int:doc_id’, methods[‘POST’]) def parse_document(doc_id): “”“解析指定ID的文档”“” conn get_db_connection() cursor conn.cursor(pymysql.cursors.DictCursor) try: # 1. 查询文档信息 cursor.execute(“SELECT * FROM documents WHERE id %s”, (doc_id,)) doc cursor.fetchone() if not doc: return jsonify({‘error’: ‘文档不存在’}), 404 filepath doc[‘file_path’] # 2. 更新状态为解析中 cursor.execute(“UPDATE documents SET status ‘parsing’ WHERE id %s”, (doc_id,)) conn.commit() # 3. 调用PP-DocLayoutV3进行解析 # 注意这里以图片格式为例。如果是PDF需要先转换为图片可使用pdf2image库 result table_engine(filepath) # 4. 处理解析结果存入数据库 saved_items 0 for page_idx, page in enumerate(result): for region in page: # region结构包含type, bbox, text, confidence等 content_type region.get(‘type’, ‘’).lower() content_text region.get(‘text’, ‘’) bbox region.get(‘bbox’, []) bbox_str ‘,’.join(map(str, bbox)) if bbox else None confidence region.get(‘confidence’, 0.0) # 只存储我们关心的类型如标题、正文、表格 if content_type in [‘title’, ‘text’, ‘table’, ‘figure’] and content_text: sql “““INSERT INTO document_contents (doc_id, content_type, content_text, bbox, page_num, confidence) VALUES (%s, %s, %s, %s, %s, %s)””” cursor.execute(sql, (doc_id, content_type, content_text, bbox_str, page_idx1, confidence)) saved_items 1 # 5. 更新文档状态为解析完成 cursor.execute(“UPDATE documents SET status ‘parsed’ WHERE id %s”, (doc_id,)) conn.commit() return jsonify({ ‘message’: f’文档解析完成共提取 {saved_items} 个内容块’, ‘doc_id’: doc_id, ‘items_saved’: saved_items }), 200 except Exception as e: # 出错时更新状态 cursor.execute(“UPDATE documents SET status ‘failed’ WHERE id %s”, (doc_id,)) conn.commit() return jsonify({‘error’: f’解析过程出错: {str(e)}‘}), 500 finally: cursor.close() conn.close()这段代码是核心。它先根据文档ID找到文件路径然后调用table_engine即PP-DocLayoutV3对文档图片进行分析。模型会返回一个结构化的结果我们遍历这个结果把每一页里的每一个识别区域标题、段落等的信息依次存入document_contents表中。3.4 实现内容检索API存进去的数据最终是为了能方便地查出来。我们实现一个简单的关键词搜索接口。app.route(‘/api/search’, methods[‘GET’]) def search_contents(): “”“根据关键词搜索文档内容”“” keyword request.args.get(‘q’, ‘’).strip() page int(request.args.get(‘page’, 1)) per_page int(request.args.get(‘per_page’, 10)) if not keyword: return jsonify({‘error’: ‘请输入搜索关键词’}), 400 conn get_db_connection() cursor conn.cursor(pymysql.cursors.DictCursor) try: # 计算偏移量 offset (page - 1) * per_page # 构建查询在文本内容中模糊匹配关键词 # 同时联表查询文档信息方便前端展示 sql “““ SELECT dc.*, d.filename, d.upload_time FROM document_contents dc JOIN documents d ON dc.doc_id d.id WHERE dc.content_text LIKE %s ORDER BY dc.doc_id, dc.page_num, dc.id LIMIT %s OFFSET %s ”“” search_pattern f’%{keyword}%’ cursor.execute(sql, (search_pattern, per_page, offset)) results cursor.fetchall() # 获取总数 count_sql “SELECT COUNT(*) as total FROM document_contents WHERE content_text LIKE %s” cursor.execute(count_sql, (search_pattern,)) total cursor.fetchone()[‘total’] return jsonify({ ‘results’: results, ‘total’: total, ‘page’: page, ‘per_page’: per_page, ‘keyword’: keyword }), 200 except Exception as e: return jsonify({‘error’: f’搜索失败: {str(e)}‘}), 500 finally: cursor.close() conn.close()这个接口接收一个关键词q在document_contents表的content_text字段里进行模糊匹配LIKE %keyword%并支持分页。返回的结果里包含了内容块本身的信息以及它所属文档的文件名这样前端展示起来就更完整了。3.5 添加辅助API与启动应用我们再添加两个简单的API一个用于列出所有文档另一个用于获取某个文档的所有解析内容方便测试和前端调用。app.route(‘/api/documents’, methods[‘GET’]) def list_documents(): “““获取所有文档列表””” conn get_db_connection() cursor conn.cursor(pymysql.cursors.DictCursor) try: cursor.execute(“SELECT * FROM documents ORDER BY upload_time DESC”) docs cursor.fetchall() return jsonify(docs), 200 finally: cursor.close() conn.close() app.route(‘/api/document/int:doc_id/contents’, methods[‘GET’]) def get_document_contents(doc_id): “““获取指定文档的所有解析内容””” conn get_db_connection() cursor conn.cursor(pymysql.cursors.DictCursor) try: cursor.execute(“““ SELECT * FROM document_contents WHERE doc_id %s ORDER BY page_num, CAST(SUBSTRING_INDEX(bbox, ‘,’, 1) AS UNSIGNED) ”“”, (doc_id,)) # 按页码和bbox的x1坐标排序近似还原阅读顺序 contents cursor.fetchall() return jsonify(contents), 200 finally: cursor.close() conn.close() if __name__ ‘__main__’: # 确保上传目录存在 os.makedirs(UPLOAD_FOLDER, exist_okTrue) print(f“文件上传目录: {os.path.abspath(UPLOAD_FOLDER)}“) print(“服务启动访问 http://localhost:5000“) # 调试模式运行 app.run(debugTrue, host‘0.0.0.0’, port5000)最后在终端里运行python app.py看到“模型加载完成”和“服务启动”的提示我们的后端服务就跑起来了。4. 测试与使用让系统动起来后端跑起来了我们怎么知道它工作正常呢这里我们用最直接的命令行工具curl来测试你也可以用Postman这类图形化工具。4.1 测试文件上传打开一个新的终端窗口准备一个测试图片比如test_doc.jpg执行curl -X POST -F “file./test_doc.jpg” http://localhost:5000/api/upload如果成功你会收到一个JSON响应里面包含doc_id比如{“doc_id”: 1, “message”: “文件上传成功”, …}。记下这个ID。同时你可以在项目目录下的uploads/文件夹里看到上传的文件也可以在MySQL里查询documents表看到一条状态为uploaded的新记录。4.2 测试文档解析用上一步获取的doc_id假设是1触发解析curl -X POST http://localhost:5000/api/parse/1这个请求会耗时几秒到几十秒取决于文档复杂度和模型加载情况。成功后会返回提取到的内容块数量。此时再查数据库会发现documents表中该记录的状态变成了parsed而document_contents表里则插入了很多条记录每条对应文档中的一个标题、段落或表格。4.3 测试内容检索现在我们可以试试搜索功能了。假设我们想找包含“项目”这个词的所有内容块curl “http://localhost:5000/api/search?q项目page1per_page5”系统会返回一个JSON其中results数组里包含了所有匹配的内容块每个块都有文本、类型、所属文档等信息。total字段则告诉你有多少条匹配结果。4.4 一个简单的HTML前端示例可选为了让体验更直观你可以在项目根目录创建一个templates文件夹里面放一个简单的index.html文件用HTML和JavaScript写一个极简的前端页面实现上传、解析列表和搜索功能。这里限于篇幅不展开代码但思路是用Fetch API调用我们写好的/api/upload,/api/documents,/api/search这几个接口然后把结果动态展示在页面上。这样一个具备完整流程上传-解析-存储-检索的文档管理系统雏形就真正可视化了。5. 总结与后续思考跟着走完这一趟你应该已经成功地把PP-DocLayoutV3这个AI模型和MySQL数据库给“撮合”到一起了。我们不仅搭建了一个能跑起来的系统更重要的是你实践了如何将一个AI能力文档解析封装成服务如何设计数据库来存储非结构化的解析结果以及如何提供API让数据能被方便地使用。这个简单的系统已经具备了核心功能但把它看作一个起点更合适。在实际应用中你可能会考虑更多东西。比如现在解析是同步的上传大文档时页面会一直等体验不好。你可以引入一个任务队列比如Celery Redis让上传和解析异步进行用户上传完就可以离开解析好了再通知他。再比如现在的搜索是简单的文本模糊匹配效果有限。你可以尝试接入更专业的全文检索引擎像Elasticsearch它支持分词、相关性评分、高亮显示搜索体验会好很多。数据库表结构也可以扩展比如增加用户表来实现多用户文档隔离或者增加标签表来支持对文档的人工分类。从技术学习角度看这个项目串联起了AI模型调用、Web后端开发、数据库操作等多个环节是一个很好的全栈练习。希望它能给你带来启发不仅仅是完成了一个教程更能成为你探索更多可能性的跳板。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章