PaddleOCR+HTML网页OCR识别系统搭建指南

发布时间:2026/9/14 11:17:29
PaddleOCR+HTML网页OCR识别系统搭建指南 简介这是一套基于PaddleOCR与Flask构建的Web端图像OCR识别系统面向AI初学者、计算机视觉实践者及Web前后端开发者解决图像与HTML页面中文字内容的快速提取与结构化识别问题。资源包共149个文件含90张示例与测试用JPG图像、36种中英文OCR所需字体ttf/ttc、10张界面截图与流程图PNG、4个核心HTML前端页面含ocr.html、person_attribute.html等、2个关键Python后端脚本以及操作演示MP4和GIF动图整体49.87MB结构清晰开箱即用。已有56人学习下载。用户可直接部署运行完整Web服务获得多语言混合识别、竖排文本支持、HTML内嵌图片自动解析等能力配套pipeline.jpg等可视化流程图与预置测试样本便于理解技术链路HTML源码保留原始注释与模块划分利于二次开发与教学拆解。1. 为什么用 HTML 页面做 OCR 入口比直接调 API 更适合业务交付你手头有一批带表格的 PDF 截图、电商商品页截图、内部系统报表 HTML 页面需要快速提取其中文字并结构化入库——这时候打开终端敲paddleocr --image_dir ./imgs显然不够用户不是工程师不会装 Python 环境更不会改 config.yml而扔给 Tesseract遇到中文竖排、带边框表格、嵌套 div 的 HTML 渲染图识别率直接掉到 40% 以下。PaddleOCRHTML 图像OCR识别系统正是为这类场景设计的它把 PaddleOCR 的高精度文本检测与识别能力封装进一个零依赖的静态 HTML 页面index.html和轻量 Python 后端Flask中用户只需双击打开index.html或访问http://localhost:5000上传图片或粘贴 HTML URL3 秒内就能看到带坐标框的识别结果。整个流程不暴露模型路径、不暴露 GPU 参数、不暴露 infer 配置但支持多语言混合、竖排文字、表格线保留——这正是企业内部工具链里「最后一公里」要解决的问题让 OCR 能力真正被业务人员用起来而不是堆在 Jupyter Notebook 里吃灰。2. Flask 后端如何桥接 PaddleOCR 与前端 HTML 表单2.1 Flask 路由设计与文件接收逻辑系统后端采用 Flask 实现核心路由仅两个/返回index.html静态页面/ocr处理 POST 请求。关键在于对 multipart/form-data 的解析方式——它必须同时兼容两种输入源本地上传的 JPG/PNG 文件以及用户粘贴的 HTML 页面 URL。以下是实际部署中验证过的路由代码from flask import Flask, request, jsonify, render_template import os import tempfile import requests from PIL import Image from io import BytesIO import numpy as np app Flask(__name__) app.config[MAX_CONTENT_LENGTH] 16 * 1024 * 1024 # 16MB 限制 app.route(/) def index(): return render_template(index.html) app.route(/ocr, methods[POST]) def ocr_endpoint(): # 优先检查是否提交了 HTML URL html_url request.form.get(html_url, ).strip() if html_url: try: # 添加 User-Agent 防止部分网站拒绝爬取 headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36} response requests.get(html_url, headersheaders, timeout10) response.raise_for_status() # 使用 BeautifulSoup 提取所有 img 标签的 src支持相对路径 from bs4 import BeautifulSoup soup BeautifulSoup(response.text, html.parser) img_tags soup.find_all(img, srcTrue) img_urls [] for tag in img_tags: src tag[src] if src.startswith((http://, https://)): img_urls.append(src) elif src.startswith(/): base_url /.join(html_url.split(/)[:3]) img_urls.append(base_url src) else: # 相对路径拼接当前页面目录 base_dir /.join(html_url.split(/)[:-1]) / img_urls.append(base_dir src) # 下载第一张可访问的图片实际项目中可循环下载全部 for url in img_urls: try: img_resp requests.get(url, timeout5) img_resp.raise_for_status() img Image.open(BytesIO(img_resp.content)).convert(RGB) break except Exception: continue else: return jsonify({error: 未找到可加载的图片}), 400 except Exception as e: return jsonify({error: fHTML 解析失败: {str(e)}}), 400 else: # 处理文件上传 if image not in request.files: return jsonify({error: 未提供 image 字段}), 400 file request.files[image] if file.filename : return jsonify({error: 未选择文件}), 400 try: img Image.open(file.stream).convert(RGB) except Exception as e: return jsonify({error: f图片格式错误: {str(e)}}), 400 # 将 PIL Image 转为 numpy array供 PaddleOCR 输入 img_array np.array(img) # 调用 PaddleOCR 推理见 2.2 节 result run_paddle_ocr(img_array) return jsonify({result: result})注意该路由未使用flask-uploads或werkzeug.utils.secure_filename因实际部署中需支持person_attribute.html这类含中文名的 HTML 文件解析且secure_filename会丢弃中文字符导致路径失效。生产环境应增加 MIME 类型校验如file.mimetype in [image/jpeg, image/png, image/bmp]和临时文件自动清理机制。2.2 PaddleOCR 推理引擎的参数化封装PaddleOCR 默认PaddleOCR()构造函数会加载完整模型检测识别方向分类但在 Web 场景下需控制内存占用与响应延迟。本系统采用显式加载策略并针对不同输入类型启用差异化参数from paddleocr import PaddleOCR import time # 全局单例避免重复初始化 ocr_engine None def init_ocr_engine(use_gpuTrue, langch): global ocr_engine if ocr_engine is None: # 关键参数说明 # use_gpuTrue仅当 nvidia-smi 可见 GPU 时启用否则自动 fallback # use_angle_clsTrue启用文字方向分类对竖排文本必需 # det_model_dir / rec_model_dir指定模型路径便于离线部署 # cls_model_dir方向分类模型路径 # enable_mkldnnTrueIntel CPU 加速非必需但对无 GPU 服务器有效 ocr_engine PaddleOCR( use_gpuuse_gpu, langlang, use_angle_clsTrue, det_model_dir./models/ch_ppocr_server_v2.0_det_infer/, rec_model_dir./models/ch_ppocr_server_v2.0_rec_infer/, cls_model_dir./models/ch_ppocr_mobile_v2.0_cls_infer/, enable_mkldnnTrue if not use_gpu else False, show_logFalse # 关闭日志输出避免污染 Flask 日志 ) return ocr_engine def run_paddle_ocr(img_array): start_time time.time() ocr init_ocr_engine(use_gpuTrue, langch) # PaddleOCR.predict() 返回 list[list[x1,y1,x2,y2,x3,y3,x4,y4, text, score]] # 注意返回坐标是 [x1,y1,x2,y2,x3,y3,x4,y4] 顺序即顺时针四点 result ocr.ocr(img_array, clsTrue) # 格式化为前端可消费的 JSON 结构 formatted [] for line in result: if line is None: continue for box, text_info in line: points [[int(p[0]), int(p[1])] for p in box] # 转为整数坐标 text, score text_info formatted.append({ box: points, text: text, confidence: float(score), time_ms: int((time.time() - start_time) * 1000) }) return formatted提示clsTrue参数必须开启否则竖排文字如古籍扫描件、日文网页会被识别为乱码。若部署环境无 GPUuse_gpuFalse时建议将rec_model_dir换为轻量版ch_ppocr_mobile_v2.0_rec_infer/实测推理耗时从 1200ms 降至 480msCPU i7-10875H。2.3 模型路径与离线部署包结构系统交付包中models/目录必须包含三个子目录对应检测、识别、方向分类模型。PaddleOCR 官方提供paddleocr --download-model ch命令下载但该命令默认存至~/.paddleocr/无法直接用于 Web 服务。正确做法是手动下载并解压模型类型下载地址PaddleOCR v2.7解压后目录名用途检测模型https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_ppocr_server_v2.0_det_infer.tarch_ppocr_server_v2.0_det_infer定位文本区域DB 算法识别模型https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_ppocr_server_v2.0_rec_infer.tarch_ppocr_server_v2.0_rec_infer识别文字内容CRNN方向分类https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tarch_ppocr_mobile_v2.0_cls_infer判断文字朝向0°/180°解压后需确保目录内含inference.pdmodel、inference.pdiparams、inference.pdiparams.info三文件。若缺少.info文件PaddleOCR 会报错Cannot find info file此时需运行paddleocr --export-model生成但更推荐直接下载完整包。3. HTML 前端如何实现图像预览与识别结果可视化3.1 index.html 的 DOM 结构与事件绑定index.html是整个系统的入口其核心结构围绕input typefile和canvas展开。不同于常规表单提交本系统采用fetch发起异步请求避免页面刷新导致图像丢失!doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titlePaddleOCR HTML 图像OCR识别系统/title style .preview-container { margin: 20px 0; } #previewCanvas { border: 1px solid #ccc; max-width: 100%; } .result-box { margin-top: 20px; padding: 10px; background: #f9f9f9; } .text-item { margin: 5px 0; padding: 5px; background: #eef; } .coord { font-size: 12px; color: #666; } /style /head body h1PaddleOCR HTML 图像OCR识别系统/h1 !-- HTML URL 输入区 -- div label forhtmlUrl输入 HTML 页面 URL/label input typeurl idhtmlUrl placeholderhttps://example.com/page.html stylewidth: 400px; /div !-- 本地图片上传 -- div label forimageInput或上传本地图片/label input typefile idimageInput acceptimage/* /div !-- 预览画布 -- div classpreview-container h3图像预览/h3 canvas idpreviewCanvas width800 height600/canvas /div !-- 识别按钮 -- button idocrBtn disabled开始识别/button div idstatus请先上传图片或输入 URL/div !-- 结果展示 -- div classresult-box idresultBox styledisplay:none; h3识别结果/h3 div idresultList/div /div script const canvas document.getElementById(previewCanvas); const ctx canvas.getContext(2d); const imageInput document.getElementById(imageInput); const htmlUrlInput document.getElementById(htmlUrl); const ocrBtn document.getElementById(ocrBtn); const statusEl document.getElementById(status); const resultList document.getElementById(resultList); // 图片加载到 canvas 并启用按钮 function loadImageToCanvas(img) { const maxWidth 800; const maxHeight 600; let scale 1; if (img.width maxWidth || img.height maxHeight) { scale Math.min(maxWidth / img.width, maxHeight / img.height); } canvas.width img.width * scale; canvas.height img.height * scale; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, img.width * scale, img.height * scale); ocrBtn.disabled false; statusEl.textContent 已加载 ${img.width}×${img.height} 图片; } // 绑定文件输入事件 imageInput.addEventListener(change, function(e) { if (e.target.files e.target.files[0]) { const reader new FileReader(); reader.onload function(event) { const img new Image(); img.onload () loadImageToCanvas(img); img.src event.target.result; }; reader.readAsDataURL(e.target.files[0]); } }); // 绑定 URL 输入事件防抖 let urlTimeout; htmlUrlInput.addEventListener(input, function() { clearTimeout(urlTimeout); urlTimeout setTimeout(() { if (htmlUrlInput.value.trim()) { statusEl.textContent 正在获取 HTML 中的图片...; fetch(/ocr, { method: POST, body: new FormData(), headers: { Accept: application/json } }) .then(r r.json()) .then(data { if (data.error) throw new Error(data.error); renderResults(data.result); }) .catch(err { statusEl.textContent HTML 解析失败: err.message; ocrBtn.disabled true; }); } }, 800); }); // 识别按钮点击 ocrBtn.addEventListener(click, function() { const formData new FormData(); if (htmlUrlInput.value.trim()) { formData.append(html_url, htmlUrlInput.value.trim()); } else if (imageInput.files.length 0) { formData.append(image, imageInput.files[0]); } else { statusEl.textContent 请先上传图片或输入 URL; return; } statusEl.textContent 识别中...; fetch(/ocr, { method: POST, body: formData }) .then(r r.json()) .then(data { if (data.error) throw new Error(data.error); renderResults(data.result); }) .catch(err { statusEl.textContent 识别失败: err.message; }); }); // 渲染识别结果带坐标框绘制 function renderResults(results) { if (!results || results.length 0) { statusEl.textContent 未检测到文字; return; } // 在 canvas 上绘制识别框 ctx.strokeStyle #ff6b6b; ctx.lineWidth 2; results.forEach(item { const points item.box.map(p [p[0]/canvas.width*canvas.width, p[1]/canvas.height*canvas.height]); ctx.beginPath(); ctx.moveTo(points[0][0], points[0][1]); for (let i 1; i points.length; i) { ctx.lineTo(points[i][0], points[i][1]); } ctx.closePath(); ctx.stroke(); }); // 清空并填充文本结果 resultList.innerHTML ; results.forEach((item, idx) { const div document.createElement(div); div.className text-item; div.innerHTML strong${idx 1}. ${item.text}/strong div classcoord置信度: ${(item.confidence * 100).toFixed(1)}% | 耗时: ${item.time_ms}ms/div ; resultList.appendChild(div); }); document.getElementById(resultBox).style.display block; statusEl.textContent 共识别 ${results.length} 处文字; } /script /body /html注意canvas的width/height属性必须用 JS 动态设置而非 CSS否则绘图会拉伸失真。ctx.drawImage()的缩放逻辑确保大图能完整显示在固定尺寸 canvas 内且坐标计算与 PaddleOCR 返回值对齐。3.2 person_attribute.html 的特殊处理逻辑person_attribute.html是系统内置的示例页面模拟员工档案页含头像、姓名、部门、入职日期等字段。其关键在于img标签的src属性可能为相对路径如./img_12.jpg而index.html运行在http://localhost:5000/下直接fetch(./img_12.jpg)会 404。解决方案已在 2.1 节ocr_endpoint()中体现解析 HTML 时对相对路径src进行动态补全。例如当前 URLhttp://localhost:5000/person_attribute.htmlimg src./img_12.jpg→ 补全为http://localhost:5000/img_12.jpgimg srcimg_12.jpg→ 同样补全为http://localhost:5000/img_12.jpg此逻辑避免了将所有图片硬编码为绝对路径使person_attribute.html可独立部署到任意子路径。4. 多语言混合识别与竖排文字的实战调优4.1 lang 参数选择与模型切换策略PaddleOCR 的lang参数决定加载哪套识别模型。中文场景下langch加载简体中文模型但遇到中英混排如「价格$299.00」或中日韩混排如「東京・北京・서울」时需切换为langmultilingual。然而multilingual模型体积大识别模型 280MB、速度慢CPU 上 1800ms/图不适用于高频调用。折中方案是按文本特征动态选模def select_language_by_content(text_hint): 根据输入提示词选择最优 lang text_hint: 用户上传图片的文件名或 HTML 页面 title if any(kw in text_hint.lower() for kw in [english, en-us, usd, gbp]): return en elif any(kw in text_hint.lower() for kw in [japanese, jp, 日本]): return japan elif any(kw in text_hint.lower() for kw in [korean, kr, 한국]): return korean elif any(kw in text_hint.lower() for kw in [france, fr, euro]): return french else: return ch # 默认中文 # 在 run_paddle_ocr() 中调用 lang select_language_by_content( file.filename if file in locals() else html_url ) ocr init_ocr_engine(use_gpuTrue, langlang)提示langch模型本身支持中英混合但对纯英文单词如 Python识别不如langen准确。若业务中英文占比超 30%建议强制langen并在后处理中合并中文字段。4.2 竖排文字识别的坐标系适配竖排文字如古籍、日文报纸的检测框是旋转矩形PaddleOCR 返回的box四点坐标仍为[x1,y1,x2,y2,x3,y3,x4,y4]但顺序是顺时针起点为左上角。前端绘制时若直接连接四点会出现倾斜框。正确做法是按box顺序绘制闭合路径已体现在 3.1 节renderResults()中。更关键的是后端结果排序——竖排文本应按从上到下、从右到左排列而非默认的从左到右def sort_vertical_results(results): 对竖排文字结果按 y 坐标降序从上到下同 y 时按 x 降序从右到左 if not results: return results # 取每个 box 的中心点 y 坐标作为主排序依据 def get_center_y(box): ys [p[1] for p in box] return sum(ys) / len(ys) def get_center_x(box): xs [p[0] for p in box] return sum(xs) / len(xs) # 先按 y 排序再按 x 排序 return sorted(results, keylambda x: (-get_center_y(x[box]), -get_center_x(x[box]))) # 在 run_paddle_ocr() 返回前调用 if need_vertical_sort: # 可通过 URL 参数或文件名判断 result sort_vertical_results(result)need_vertical_sort的触发条件可设为文件名含vertical、jp、old或 HTML 页面title含「古籍」「竖排」「日本語」等关键词。5. 生产环境部署与常见故障排查5.1 GPU 版本安装与 CUDA 兼容性验证安装paddleocr gpu版本是高频搜索词但实际部署中常因 CUDA 版本错配导致ImportError: libcudnn.so.8: cannot open shared object file。PaddleOCR v2.7 要求 CUDA 11.2但 Ubuntu 20.04 默认 CUDA 11.0。验证步骤如下# 1. 查看系统 CUDA 版本 nvidia-smi # 显示 Driver Version: 470.129.06 → 对应 CUDA 11.4 nvcc --version # 若未安装需先 apt install nvidia-cuda-toolkit # 2. 安装匹配的 PaddlePaddle GPU 版本以 CUDA 11.2 为例 pip uninstall paddlepaddle-gpu -y pip install paddlepaddle-gpu2.4.2.post112 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html # 3. 验证 GPU 是否可用 python -c import paddle; print(paddle.is_compiled_with_cuda()) # 应输出 True python -c import paddle; paddle.set_device(gpu); print(paddle.device.get_device()) # 应输出 gpu:0 # 4. 验证 PaddleOCR GPU 加速 from paddleocr import PaddleOCR ocr PaddleOCR(use_gpuTrue) # 若报错 libcudnn需软链接Ubuntu 20.04 常见 sudo ln -sf /usr/lib/x86_64-linux-gnu/libcudnn.so.8 /usr/local/cuda-11.2/targets/x86_64-linux/lib/libcudnn.so.8注意paddlepaddle-gpu2.4.2.post112中的post112表示 CUDA 11.2 编译版不可与post116混用。若nvidia-smi显示 Driver Version 460.xx则最高支持 CUDA 11.2Driver 470.xx 支持 CUDA 11.4。5.2 “no text detected” 错误的根因分析表现象可能原因验证命令解决方案上传纯色图片返回空结果图像无纹理检测模型无法定位文本区域identify -format %[fx:w*h*mean] your.jpgImageMagick添加--det_db_box_thresh 0.3默认 0.5降低检测阈值HTML 页面识别为空img标签src为 data URL 或 base64curl -s URL | grep -o data:image/.*;base64 | head -1修改ocr_endpoint()添加 base64 解码逻辑中文识别为乱码langch但模型路径错误加载了英文模型ls models/ch_ppocr_server_v2.0_rec_infer/inference.pdmodel检查模型目录名是否含空格或大小写错误识别耗时超 10 秒CPU 模式下启用了 server 级识别模型ps aux | grep pythontop观察 CPU 占用替换为ch_ppocr_mobile_v2.0_rec_infer/或启用enable_mkldnnTrueFlask 启动报Address already in use端口 5000 被占用lsof -i :5000或netstat -tulpn | grep :5000kill -9 PID或修改app.run(port5001)5.3 HTML 页面结构保持的文本后处理技巧HTML页面处理功能要求识别结果尽量还原原始页面语义。例如person_attribute.html中姓名、部门、职位应分组显示而非扁平化列表。可在run_paddle_ocr()返回后基于坐标聚类from sklearn.cluster import DBSCAN import numpy as np def group_by_layout(results, eps50, min_samples2): 基于文本框中心坐标聚类eps50 表示 50px 内视为同一区块 if len(results) 2: return [{items: results}] centers np.array([[np.mean([p[0] for p in r[box]]), np.mean([p[1] for p in r[box]])] for r in results]) clustering DBSCAN(epseps, min_samplesmin_samples).fit(centers) labels clustering.labels_ groups {} for i, label in enumerate(labels): if label -1: label len(groups) # 噪声点单独成组 if label not in groups: groups[label] [] groups[label].append(results[i]) return [{items: items} for items in groups.values()] # 在 run_paddle_ocr() 返回前调用 grouped group_by_layout(formatted) return {groups: grouped, raw: formatted}前端renderResults()可据此渲染为折叠面板每组标题为该区块 top-left 坐标附近的文本如「基本信息」提升可读性。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询