文章总结: 文档提出营业执照真伪识别程序设想,通过本地库交叉校验OCR识别结果,实现统一社会信用代码、名称、住所、法定代表人等字段比对,并设计Web服务可视化展示异常数据,支持图片预览与人工核对,提升执照审核效率与准确性。 综合评分: 72 文章分类: 安全工具,安全开发,数据安全
营业执照(统一信用代码)的真伪识别程序设想(三)
老皮的碎碎念念
2026年9月6日 16:06 安徽
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
对营业执照进行本地库校验:
将本地营业执照主体库导出为 REG_MARPRIPINFO.csv (字段名根据总局标准定义)
根据REG_MARPRIPINFO.csv校验ocr_result.csv,具体如下,将ocr_result.csv的数据逐个依次校验:首先统一社会信用代码是否包含在UNISCID中,如是,继续校验entname对名称,dom对住所,lerep对法定代表人,是否一致,输出不一致的,(如果统一社会信用代码是15位那么在reg_no中搜索,兼容老个体执照)
# -*- coding: utf-8 -*-"""交叉校验: ocr_result.csv vs REG_MARPRIPINFO.csv
逐条读取 ocr_result.csv 的统一社会信用代码, 在 REG_MARPRIPINFO.csv 中查找: 1. 优先匹配 UNISCID 字段 2. 未命中且为 15 位纯数字时, 回退匹配 REGNO 字段(老版执照注册号)命中后逐项比对: ENTNAME↔名称, DOM↔住所, LEREP↔法定代表人输出: 不一致明细 CSV + 控制台摘要"""import csvimport osimport sys
def norm(s): """归一化: 去首尾空白、去尾部 \t、合并连续空白""" if not s: return "" return " ".join(s.replace("\t", "").strip().split())
def main(): base = r"C:\Users\Administrator\AppData\Roaming\TRAE SOLO CN\ModularData\ai-agent\work-mode-projects\6a956215a09016f81bdfcb06" reg_csv = os.path.join(base, "REG_MARPRIPINFO.csv") ocr_csv = os.path.join(base, "ocr_result.csv") out_dir = base
# 1) 加载 REG_MARPRIPINFO.csv, 构建 UNISCID 索引 + REGNO 索引 uscc_map = {} # UNISCID(大写) -> (ENTNAME, DOM, LEREP) regno_map = {} # REGNO -> (ENTNAME, DOM, LEREP)
with open(reg_csv, "r", encoding="utf-8-sig", newline="") as f: rd = csv.reader((ln.replace("\x00", "") for ln in f)) header = next(rd) idx = {name: i for i, name in enumerate(header)} i_ent = idx["ENTNAME"] i_dom = idx["DOM"] i_lerep = idx["LEREP"] i_regno = idx["REGNO"] i_uniscid = idx["UNISCID"]
for row in rd: if len(row) <= max(i_ent, i_dom, i_lerep, i_regno, i_uniscid): continue rec = ( norm(row[i_ent]), norm(row[i_dom]), norm(row[i_lerep]), ) uscc = norm(row[i_uniscid]).upper() if uscc: uscc_map[uscc] = rec regno = norm(row[i_regno]) if regno: regno_map[regno] = rec
print("REG_MARPRIPINFO.csv 加载完成: UNISCID 索引 %d 条, REGNO 索引 %d 条" % (len(uscc_map), len(regno_map)))
# 2) 逐条校验 ocr_result.csv results = [] # (图片, 信用代码, 匹配方式, 名称_ocr, 名称_reg, 名称_一致?, 住所_ocr, 住所_reg, 住所_一致?, 法定代表人_ocr, 法定代表人_reg, 法定代表人_一致?) matched = 0 not_found = 0 mismatch_cnt = 0
with open(ocr_csv, "r", encoding="utf-8-sig", newline="") as f: rd = csv.DictReader(f) for row in rd: img = row.get("图片文件", "") code_raw = row.get("统一社会信用代码", "").strip() code_up = code_raw.upper() ocr_name = norm(row.get("名称", "")) ocr_addr = norm(row.get("住所", "")) ocr_lerep = norm(row.get("法定代表人", ""))
# 查找: 先 UNISCID, 再 REGNO rec = None match_by = "" if code_up in uscc_map: rec = uscc_map[code_up] match_by = "UNISCID" elif code_raw in regno_map: rec = regno_map[code_raw] match_by = "REGNO"
if rec is None: not_found += 1 results.append((img, code_raw, "未找到", ocr_name, "", "", ocr_addr, "", "", ocr_lerep, "", "")) continue
matched += 1 reg_name, reg_dom, reg_lerep = rec
name_ok = (ocr_name == reg_name) if ocr_name and reg_name else None dom_ok = (ocr_addr == reg_dom) if ocr_addr and reg_dom else None lerep_ok = (ocr_lerep == reg_lerep) if ocr_lerep and reg_lerep else None
has_mismatch = False for ok in (name_ok, dom_ok, lerep_ok): if ok is False: has_mismatch = True break if has_mismatch: mismatch_cnt += 1
def status_str(ok): if ok is True: return "一致" if ok is False: return "不一致" return "OCR为空"
results.append(( img, code_raw, match_by, ocr_name, reg_name, status_str(name_ok), ocr_addr, reg_dom, status_str(dom_ok), ocr_lerep, reg_lerep, status_str(lerep_ok), ))
# 3) 输出不一致明细 report = os.path.join(out_dir, "ocr_cross_check_report.csv") with open(report, "w", encoding="utf-8-sig", newline="") as f: w = csv.writer(f) w.writerow([ "图片文件", "信用代码", "匹配方式", "名称(OCR)", "名称(库)", "名称比对", "住所(OCR)", "住所(库)", "住所比对", "法定代表人(OCR)", "法定代表人(库)", "法定代表人比对", ]) # 只输出未找到 + 不一致的记录 for r in results: if r[2] == "未找到" or "不一致" in r[5] or "不一致" in r[8] or "不一致" in r[11]: w.writerow(r)
# 4) 全量明细(含一致的) full_report = os.path.join(out_dir, "ocr_cross_check_full.csv") with open(full_report, "w", encoding="utf-8-sig", newline="") as f: w = csv.writer(f) w.writerow([ "图片文件", "信用代码", "匹配方式", "名称(OCR)", "名称(库)", "名称比对", "住所(OCR)", "住所(库)", "住所比对", "法定代表人(OCR)", "法定代表人(库)", "法定代表人比对", ]) w.writerows(results)
# 5) 控制台摘要 sys.stdout.reconfigure(encoding="utf-8", errors="replace") print() print("=== 交叉校验报告 ===") print("ocr_result.csv 总条数 : %d" % len(results)) print("成功匹配库中记录 : %d" % matched) print("未找到 : %d" % not_found) print("存在不一致 : %d" % mismatch_cnt) print() print("--- 逐条明细 ---") for img, code, mby, n_ocr, n_reg, n_st, d_ocr, d_reg, d_st, l_ocr, l_reg, l_st in results: flag = "✗" if (mby == "未找到" or "不一致" in n_st or "不一致" in d_st or "不一致" in l_st) else "✓" print(" %s %s" % (flag, img)) if mby == "未找到": print(" 信用代码 %s 未在库中找到" % code) continue print(" 匹配方式: %s 信用代码: %s" % (mby, code)) print(" 名称 : [%s] vs [%s] → %s" % (n_ocr, n_reg, n_st)) print(" 住所 : [%s] vs [%s] → %s" % (d_ocr, d_reg, d_st)) print(" 法定代表: [%s] vs [%s] → %s" % (l_ocr, l_reg, l_st)) print() print("不一致明细: %s" % report) print("全量明细 : %s" % full_report)
if __name__ == "__main__": main()
写一个python服务,网页访问展示校验输出的错误数据,点击图片文件名称可以显示照片,便于人工核对未校验通过的执照
页面功能
-
汇总卡片
:总记录数 / 有异常 / 正常
-
筛选按钮
:全部 / 仅异常 / 仅正常
-
数据表格
:每行展示图片文件名、信用代码、USCC 校验结果(GB 32100-2015)、名称/住所/法定代表人(含交叉比对详情)、匹配方式、备注
-
图片预览
:点击图片文件名,弹出原图浮窗,按 Esc 或点击空白处关闭
-
异常记录自动排在最前面
判定逻辑
以下情况标记为异常:
- USCC 校验:校验码不符 / 长度异常 / 非法字符
- 交叉校验:未找到 / 字段不一致
# -*- coding: utf-8 -*-"""OCR 执照识别结果校验可视化服务
启动后访问 http://127.0.0.1:5000展示 ocr_result.csv + USCC 校验 + 交叉校验的合并结果点击图片文件名可弹出原图预览"""import csvimport osimport sys
from flask import Flask, abort, send_file, render_template_string
BASE = os.path.dirname(os.path.abspath(__file__))IMG_DIR = r"D:\Personal\Desktop\新建文件夹 (2)"
app = Flask(__name__)
def norm(s): return (s or "").replace("\t", "").strip()
def load_ocr_result(): path = os.path.join(BASE, "ocr_result.csv") data = {} if not os.path.exists(path): return data with open(path, "r", encoding="utf-8-sig", newline="") as f: for row in csv.DictReader(f): img = norm(row.get("图片文件", "")) if img: data[img] = { "code": norm(row.get("统一社会信用代码", "")), "name": norm(row.get("名称", "")), "address": norm(row.get("住所", "")), "legal_rep": norm(row.get("法定代表人", "")), "note": norm(row.get("备注", "")), } return data
def load_uscc_check(): path = os.path.join(BASE, "ocr_uscc_check_report.csv") data = {} if not os.path.exists(path): return data with open(path, "r", encoding="utf-8-sig", newline="") as f: for row in csv.DictReader(f): img = norm(row.get("图片文件", "")) if img: data[img] = { "uscc_status": norm(row.get("校验结果", "")), "uscc_reason": norm(row.get("说明", "")), } return data
def load_cross_check(): path = os.path.join(BASE, "ocr_cross_check_full.csv") data = {} if not os.path.exists(path): return data with open(path, "r", encoding="utf-8-sig", newline="") as f: for row in csv.DictReader(f): img = norm(row.get("图片文件", "")) if img: data[img] = { "match_by": norm(row.get("匹配方式", "")), "name_ocr": norm(row.get("名称(OCR)", "")), "name_reg": norm(row.get("名称(库)", "")), "name_cmp": norm(row.get("名称比对", "")), "addr_ocr": norm(row.get("住所(OCR)", "")), "addr_reg": norm(row.get("住所(库)", "")), "addr_cmp": norm(row.get("住所比对", "")), "lerep_ocr": norm(row.get("法定代表人(OCR)", "")), "lerep_reg": norm(row.get("法定代表人(库)", "")), "lerep_cmp": norm(row.get("法定代表人比对", "")), } return data
def merge_data(): ocr = load_ocr_result() uscc = load_uscc_check() cross = load_cross_check() rows = [] for img, o in ocr.items(): u = uscc.get(img, {}) c = cross.get(img, {}) # 判断是否有错误 has_error = False if u.get("uscc_status") in ("校验码不符", "长度异常", "非法字符", "首位异常"): has_error = True if c.get("match_by") == "未找到": has_error = True if "不一致" in c.get("name_cmp", "") or "不一致" in c.get("addr_cmp", "") or "不一致" in c.get("lerep_cmp", ""): has_error = True
rows.append({ "img": img, "code": o.get("code", ""), "name": o.get("name", ""), "address": o.get("address", ""), "legal_rep": o.get("legal_rep", ""), "note": o.get("note", ""), "uscc_status": u.get("uscc_status", ""), "uscc_reason": u.get("uscc_reason", ""), "match_by": c.get("match_by", ""), "name_ocr": c.get("name_ocr", ""), "name_reg": c.get("name_reg", ""), "name_cmp": c.get("name_cmp", ""), "addr_ocr": c.get("addr_ocr", ""), "addr_reg": c.get("addr_reg", ""), "addr_cmp": c.get("addr_cmp", ""), "lerep_ocr": c.get("lerep_ocr", ""), "lerep_reg": c.get("lerep_reg", ""), "lerep_cmp": c.get("lerep_cmp", ""), "has_error": has_error, }) rows.sort(key=lambda r: (not r["has_error"], r["img"])) return rows
HTML = r"""<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>OCR 执照校验结果</title><style>:root { --bg: #0f1117; --card: #1a1d27; --border: #2a2d3a; --text: #e0e0e0; --text-dim: #888; --accent: #4a9eff; --green: #4caf50; --red: #f44336; --orange: #ff9800; --yellow: #ffd54f;}* { box-sizing: border-box; margin: 0; padding: 0; }body { background: var(--bg); color: var(--text); font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif; padding: 24px; font-size: 16px;}h1 { font-size: 28px; margin-bottom: 4px; }.subtitle { color: var(--text-dim); font-size: 16px; margin-bottom: 20px; }.summary { display: flex; gap: 16px; margin-bottom: 24px; flex-wrap: wrap;}.summary .card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px 24px; min-width: 120px; text-align: center;}.summary .num { font-size: 36px; font-weight: 700; }.summary .label { font-size: 15px; color: var(--text-dim); margin-top: 4px; }
table { width: 100%; border-collapse: collapse; background: var(--card); border-radius: 8px; overflow: hidden;}th, td { padding: 14px 16px; text-align: left; border-bottom: 1px solid var(--border); font-size: 16px; vertical-align: top;}th { background: #212530; color: var(--text-dim); font-weight: 600; white-space: nowrap; position: sticky; top: 0; z-index: 1;}tr:hover td { background: #1e2230; }.img-link { color: var(--accent); cursor: pointer; text-decoration: underline; text-underline-offset: 2px;}.img-link:hover { text-decoration: none; opacity: .8; }.badge { display: inline-block; padding: 4px 12px; border-radius: 4px; font-size: 14px; font-weight: 600; white-space: nowrap;}.badge-ok { background: rgba(76,175,80,.15); color: var(--green); }.badge-err { background: rgba(244,67,54,.15); color: var(--red); }.badge-warn { background: rgba(255,152,0,.15); color: var(--orange); }.badge-info { background: rgba(74,158,255,.15); color: var(--accent); }.cmp-ok { color: var(--green); }.cmp-bad { color: var(--red); font-weight: 600; }.cmp-empty { color: var(--text-dim); }
#overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.85); z-index: 999; justify-content: center; align-items: center;}#overlay.show { display: flex; }#overlay img { max-width: 90vw; max-height: 90vh; border-radius: 8px; box-shadow: 0 8px 32px rgba(0,0,0,.5);}#overlay .close { position: fixed; top: 16px; right: 24px; color: #fff; font-size: 32px; cursor: pointer; user-select: none;}.filter-bar { margin-bottom: 16px; display: flex; gap: 8px; flex-wrap: wrap;}.filter-btn { background: var(--card); border: 1px solid var(--border); color: var(--text-dim); padding: 8px 20px; border-radius: 6px; cursor: pointer; font-size: 16px; transition: all .2s;}.filter-btn:hover { border-color: var(--accent); color: var(--text); }.filter-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }</style></head><body><h1>OCR 执照识别校验结果</h1><p class="subtitle">USCC 校验(GB 32100-2015) + REG_MARPRIPINFO 交叉比对 · 点击图片文件名预览原图</p>
<div class="summary"> <div class="card"><div class="num" id="s-total">0</div><div class="label">总记录</div></div> <div class="card"><div class="num" id="s-err" style="color:var(--red)">0</div><div class="label">有异常</div></div> <div class="card"><div class="num" id="s-ok" style="color:var(--green)">0</div><div class="label">正常</div></div></div>
<div class="filter-bar"> <button class="filter-btn active" data-filter="all">全部</button> <button class="filter-btn" data-filter="err">仅异常</button> <button class="filter-btn" data-filter="ok">仅正常</button></div>
<table id="tbl"><thead><tr> <th>图片文件</th> <th>统一社会信用代码</th> <th>USCC 校验</th> <th>名称</th> <th>住所</th> <th>法定代表人</th> <th>交叉校验</th> <th>备注</th></tr></thead><tbody id="tbody"></tbody></table>
<div id="overlay"> <span class="close" onclick="closeOverlay()">×</span> <img id="overlay-img" src="" alt=""></div>
<script>const DATA = __DATA__;const IMG_PREFIX = "/img/";
function badge(text, type) { return `<span class="badge badge-${type}">${esc(text)}</span>`;}
function esc(s) { if (!s) return ''; return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
function cmpCell(ocr, reg, cmp) { if (!cmp) return '<span class="cmp-empty">—</span>'; if (cmp === '一致') return `<span class="cmp-ok">一致</span>`; if (cmp === 'OCR为空') return '<span class="cmp-empty">OCR为空</span>'; // 不一致 let html = '<span class="cmp-bad">不一致</span><br>'; if (ocr) html += `<span style="font-size:14px;color:var(--text-dim)">OCR: ${esc(ocr)}</span><br>`; if (reg) html += `<span style="font-size:14px;color:var(--text-dim)">库: ${esc(reg)}</span>`; return html;}
function renderRows(filter) { const tbody = document.getElementById('tbody'); tbody.innerHTML = ''; let errCount = 0, okCount = 0; DATA.forEach((r, i) => { if (r.has_error) errCount++; else okCount++; if (filter === 'err' && !r.has_error) return; if (filter === 'ok' && r.has_error) return;
// USCC badge let usccBadge = ''; const st = r.uscc_status; if (st === '有效') usccBadge = badge('有效', 'ok'); else if (st === '校验码不符') usccBadge = badge('校验码不符', 'err'); else if (st === '注册号(15位)') usccBadge = badge('注册号', 'info'); else if (st) usccBadge = badge(st, 'warn');
// 交叉校验 let crossBadge = ''; if (r.match_by === '未找到' || !r.match_by) { crossBadge = badge('未找到', 'warn'); } else { crossBadge = badge(r.match_by, 'info'); }
const tr = document.createElement('tr'); tr.innerHTML = ` <td><span class="img-link" onclick="showImg('${esc(r.img)}')">${esc(r.img)}</span></td> <td><code style="font-size:15px">${esc(r.code)}</code></td> <td>${usccBadge}${r.uscc_reason ? '<br><span style="font-size:14px;color:var(--text-dim)">'+esc(r.uscc_reason)+'</span>':''}</td> <td>${esc(r.name)}${r.name_cmp ? '<hr style="border:none;border-top:1px solid var(--border);margin:4px 0">'+cmpCell(r.name_ocr, r.name_reg, r.name_cmp):''}</td> <td>${esc(r.address)}${r.addr_cmp ? '<hr style="border:none;border-top:1px solid var(--border);margin:4px 0">'+cmpCell(r.addr_ocr, r.addr_reg, r.addr_cmp):''}</td> <td>${esc(r.legal_rep)}${r.lerep_cmp ? '<hr style="border:none;border-top:1px solid var(--border);margin:4px 0">'+cmpCell(r.lerep_ocr, r.lerep_reg, r.lerep_cmp):''}</td> <td>${crossBadge}</td> <td style="font-size:15px;color:var(--text-dim)">${esc(r.note)}</td> `; tbody.appendChild(tr); }); document.getElementById('s-total').textContent = DATA.length; document.getElementById('s-err').textContent = errCount; document.getElementById('s-ok').textContent = okCount;}
function showImg(filename) { fetch(IMG_PREFIX + encodeURIComponent(filename)) .then(resp => { if (!resp.ok) throw new Error('not found'); return resp.blob(); }) .then(blob => { const url = URL.createObjectURL(blob); document.getElementById('overlay-img').src = url; document.getElementById('overlay').classList.add('show'); }) .catch(() => { alert('图片未找到: ' + filename + '\n请确认图片目录: ' + IMG_DIR); });}
function closeOverlay() { document.getElementById('overlay').classList.remove('show'); document.getElementById('overlay-img').src = '';}
document.getElementById('overlay').addEventListener('click', function(e) { if (e.target === this) closeOverlay();});
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeOverlay();});
document.querySelectorAll('.filter-btn').forEach(btn => { btn.addEventListener('click', function() { document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); this.classList.add('active'); renderRows(this.dataset.filter); });});
renderRows('all');</script></body></html>"""
@app.route("/")def index(): rows = merge_data() import json html = HTML.replace("__DATA__", json.dumps(rows, ensure_ascii=False)) return html
@app.route("/img/<path:filename>")def serve_img(filename): from urllib.parse import unquote filename = unquote(filename) # 优先在 IMG_DIR 找 if IMG_DIR and os.path.exists(os.path.join(IMG_DIR, filename)): return send_file(os.path.join(IMG_DIR, filename)) # 回退到附件目录 attach = r"c:\Users\Administrator\.trae-cn\attachments\6a956215a09016f81bdfcb09" if os.path.exists(os.path.join(attach, filename)): return send_file(os.path.join(attach, filename)) abort(404)
if __name__ == "__main__": print("=" * 50) print(" OCR 执照校验结果可视化服务") print(" 访问 http://127.0.0.1:5000") print(" 图片目录: %s" % IMG_DIR) print(" 按 Ctrl+C 停止") print("=" * 50) app.run(host="127.0.0.1", port=5000, debug=False)
至此程序的基本框架和功能验证已经完成
注:本文中的营业执照图片数据来源于百度搜索,为不盈利引用,仅作为试验学习使用,不证明其合法性、真伪性,未进行商业性利用。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:老皮的碎碎念念 《营业执照(统一信用代码)的真伪识别程序设想(三)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论