适用场景与接口能力
在企业资质审核、合规风控、供应链管理中,经常需要批量核验营业执照信息。传统人工录入效率低且易出错,通过 OCR 识别接口可以自动提取统一社会信用代码、公司名称、法人、准备资本、经营期限等 12 个关键字段,直接对接数据库或审批流程。
本文使用的接口为「营业执照识别」,其 base URL 为https://v1.apizero.cn/api/business-license,单张图片 QPS 上限 2 次/秒,支持 JPG/PNG 格式,文件大小不超过 10 MB。图片需清晰完整、无遮挡且文字方向正确。
接口参数与鉴权
该接口通过 HTTP POST 请求调用,需要携带两个 Header:
Authorization: Bearer <你的 API Key>— 认证凭据,每个调用者需从服务商获取独立 Key。Content-Type: application/json— 请求体为 JSON 格式。
请求体是一个 JSON 对象,包含两个必填字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
input_type | string | 图片传入方式,固定值"url"或"base64" |
input_data | string | 图片 URL 或 base64 编码字符串(最大 10MB) |
第一步:用 curl 快速验证
在正式工程化之前,建议先用 curl 发送一次请求确认接口可用性、网络连通性以及返回结构是否符合预期。以下示例使用图片 URL 方式调用(请替换占位符):
curl -sS \ -X POST \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"input_type": "url", "input_data": "https://example.com/business-license.jpg"}' \ "https://v1.apizero.cn/api/business-license"成功响应将返回 200 状态码及类似以下 JSON:
{ "code": 0, "msg": "成功", "request_id": "req_abc123", "data": { "unified_social_credit_code": "91310000XXXXXXXXXX", "company_name": "某某科技有限公司", "legal_representative": "张三", "registered_capital": "100万人民币", "established_date": "2015-06-01至无固定期限", "business_scope": "软件开发;信息技术咨询服务", "domicile": "上海市浦东新区某某路123号", "company_type": "有限责任公司(自然人独资)", "approval_date": "2025-01-10", "certificate_type": "营业执照", "established_time": "2015-06-01", "website": "http://www.gsxt.gov.cn" } }如果返回code非零,可以根据msg字段排查常见问题(详见错误处理小节)。
第二步:从 curl 到 Python 工程封装
生产环境中不可能每次调用都手动执行 curl,必须通过代码将鉴权、请求、重试、异常处理、日志记录等逻辑封装为一个可复用的函数或类。下面以 Python 为例逐步构建。
2.1 基础请求函数
import requests import json class LicenseRecognizer: """营业执照识别客户端""" ENDPOINT = "https://v1.apizero.cn/api/business-license" def __init__(self, api_key: str): self.headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } def recognize(self, input_data: str, input_type: str = "url") -> dict: """ 识别营业执照 :param input_data: 图片URL或base64字符串 :param input_type: 传入方式,'url'或'base64' :return: 接口返回的完整JSON """ payload = { "input_type": input_type, "input_data": input_data } resp = requests.post( self.ENDPOINT, headers=self.headers, json=payload, timeout=15 ) resp.raise_for_status() # 非2xx状态码抛出异常 return resp.json()2.2 增加重试与速率控制
接口 QPS 为 2 次/秒,如果并发请求过高可能被限流。建议使用tenacity库实现指数退避重试,并使用time.sleep或ratelimit控制频率。
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type from requests.exceptions import RequestException, HTTPError from time import sleep class AdvancedRecognizer(LicenseRecognizer): @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10), retry=retry_if_exception_type((RequestException, HTTPError)), reraise=True ) def recognize_with_retry(self, input_data: str, input_type: str = "url") -> dict: # 先检查与本地上次请求间隔是否 >= 0.5 秒 now = time.time() if hasattr(self, '_last_call_time') and now - self._last_call_time < 0.5: sleep(0.5 - (now - self._last_call_time)) result = self.recognize(input_data, input_type) self._last_call_time = time.time() # 如果业务状态码非0,也视为需要重试的异常 if result.get("code") != 0: raise ValueError(f"业务错误: {result.get('msg','')}") return result2.3 将返回数据映射为结构化对象
为方便下游使用,可以将识别结果封装为数据类:
from dataclasses import dataclass from typing import Optional @dataclass class LicenseInfo: unified_social_credit_code: str company_name: str legal_representative: str registered_capital: str established_date: str business_scope: str domicile: str company_type: str approval_date: Optional[str] = None certificate_type: Optional[str] = None established_time: Optional[str] = None website: Optional[str] = None def parse_license_data(data: dict) -> LicenseInfo: return LicenseInfo( unified_social_credit_code=data.get("unified_social_credit_code", ""), company_name=data.get("company_name", ""), legal_representative=data.get("legal_representative", ""), registered_capital=data.get("registered_capital", ""), established_date=data.get("established_date", ""), business_scope=data.get("business_scope", ""), domicile=data.get("domicile", ""), company_type=data.get("company_type", ""), approval_date=data.get("approval_date"), certificate_type=data.get("certificate_type"), established_time=data.get("established_time"), website=data.get("website") )2.4 批量处理与异常隔离
当需要处理多张图片时,建议使用线程池控制并发数,避免超 QPS:
from concurrent.futures import ThreadPoolExecutor, as_completed def batch_recognize(recognizer: AdvancedRecognizer, image_urls: list[str], max_workers: int = 2): """批量识别,返回 {url: LicenseInfo} 字典""" results = {} with ThreadPoolExecutor(max_workers=max_workers) as executor: future_map = {executor.submit(recognizer.recognize_with_retry, url): url for url in image_urls} for future in as_completed(future_map): url = future_map[future] try: resp = future.result() info = parse_license_data(resp["data"]) results[url] = info except Exception as e: # 记录失败但继续处理其他图片 print(f"识别失败 {url}: {e}") results[url] = None return results返回值解读
从响应示例可以看到,data字段里包含了营业执照上几乎所有结构化信息。具体字段与含义如下:
| 字段 | 说明 |
|---|---|
unified_social_credit_code | 统一社会信用代码,18 位字母数字组合 |
company_name | 公司全称 |
legal_representative | 法定代表人姓名 |
registered_capital | 准备资本(含单位,如“100万人民币”) |
established_date | 成立日期至有效期,如“2015-06-01至无固定期限” |
business_scope | 经营范围 |
domicile | 住所(准备地址) |
company_type | 公司类型,如“有限责任公司(自然人独资)” |
approval_date | 核准日期(如无则返回空) |
certificate_type | 证件类型,固定为“营业执照” |
established_time | 成立日期(纯日期) |
website | 公示系统链接,固定为http://www.gsxt.gov.cn |
注意:code为 0 表示成功;非 0 值需检查msg和request_id(可用于向服务商排查问题)。
常见错误与排查
| HTTP 状态 | code 值 | possible reason | 处理方式 |
|---|---|---|---|
| 401 | — | Authorization 缺失或无效 | 检查 API Key 是否正确,确保前缀Bearer |
| 400 | 1000 | 图片无法下载或 base64 解析失败 | 确认input_data可访问且为有效图片 |
| 400 | 1001 | 图片格式不支持或超过 10MB | 转换格式或压缩图片 |
| 400 | 1002 | 图片内容不清晰,无法识别 | 选择光线均匀、文本无遮挡的图片 |
| 429 | — | 请求频率超过 QPS 2次/秒 | 加入 sleep 或使用令牌桶限流 |
| 500 | 9999 | 服务端异常 | 稍后重试,或联系技术支持并提供request_id |
在工程封装中,建议对非 200 状态码或code != 0都进行捕获并记录日志,同时提供友好的错误提示给上游调用方。
工程化注意事项
- API Key 管理:切勿将 API Key 硬编码在代码中。应通过环境变量、配置中心或密钥管理服务注入。
- 超时与连接池:使用
requests.Session并设置pool_connections与pool_maxsize,避免频繁创建连接。 - 日志记录:每个请求的
request_id是问题定位的关键线索,务必打印或存储。 - 图片预处理:若用户上传的图片方向不正或存在噪点,可先用 OpenCV 做角度校正或增强后再传入接口。
- 熔断降级:如果依赖的接口连续失败,应触发熔断,切换到备用方案(如人工审核队列)。
- 测试覆盖:建议针对返回的 12 个字段做字段完整性检查,并对常见错误场景编写单元测试。
参考文档
营业执照识别接口详情:https://apizero.cn/aidocs/business-license
原始 Markdown 文档:https://apizero.cn/aidocs/business-license/raw.md