利用Python segno批量生成带编号的二维码图片
本文详细介绍如何利用Python轻量级二维码库segno结合Pillow,批量生成带有明文编号与自定义标签的高清二维码图片。包含画布扩展算法、文字居中渲染、完整批量脚本及工程落地避坑指南。
在仓库盘点、资产贴标、样品流转与活动签到等实际管理场景中,单纯的二维码图片往往无法直接满足人工核对需求——操作人员无法凭肉眼直接读取二维码内容,必须依赖扫码枪或移动设备。为了实现“人机协同”,业界通用的解决方案是在二维码下方或边缘拓展出一段带有醒目明文编号的标签区域。本文将介绍如何使用Python轻量级二维码生成库segno配合图像处理库Pillow,高效、规范地批量生成带有格式化编号的二维码图片。
一、技术选型与图像复合方案
生成带编号二维码图片的核心在于两个环节:一是标准二维码矩阵的高质量编码,二是图像画布的扩展与文字标绘。在Python生态中,常见的二维码库有qrcode与segno。相比于传统工具,segno纯基于Python标准库构建(不依赖庞杂的C底层编译),严格遵循ISO/IEC 18004规范,支持微型二维码(Micro QR),且原生具备将编码矩阵无缝转换为Pillow图像对象的能力(to_pil())。
通过segno生成纯二维码后,我们需要使用Pillow进行“画板扩充”:创建一个高度等于“二维码高度 + 预留文字区域高度”的新画布,将纯二维码粘贴于画布上方,再计算文字宽度使其水平居中绘制在下方空白区域。这种分层处理方式既保证了二维码静区(Quiet Zone)的完整性,又避免了文字侵入二维码识别区导致的解码失败。
二、环境依赖与基础配置
在开始编写代码前,需要在Python运行环境中安装两个核心库:segno与Pillow。打开终端并执行以下安装命令:
pip install segno pillow
安装完成后,建议进行一次最小可行性验证。下面的代码展示了如何使用segno生成一个标准二维码,并将其直接桥接为Pillow的Image对象:
import segno
# 创建一个内容为资产链接的二维码
qr = segno.make('https://example.com/asset/001', error='m')
# 使用 to_pil 将矩阵转为 PIL.Image 对象
# scale 指定每个模块对应的像素点数,border 指定静区宽度
qr_img = qr.to_pil(scale=8, border=4, dark='black', light='white')
# 转换为标准 RGB 模式方便后续绘制彩色或高质量灰度字体
qr_img = qr_img.convert('RGB')
print(f"生成的二维码尺寸为: {qr_img.size}")
确认基础流程正常后,我们可以将目光投向批量生成的核心业务逻辑。批量任务的关键不仅在于画图,更在于流水线的输入规划、异常兜底与输出规范。
三、单张复合二维码绘制核心函数
为了让批量脚本具有良好的结构性与可复用性,我们首先将“生成单张带编号二维码”的逻辑封装为一个独立函数。该函数接收二维码载荷内容、下方展示文本、输出文件路径以及相关尺寸与字体配置,完成从编码到排版的全部工作。
在此过程中,字体的测量与定位是保证排版工整的关键。自Pillow 8.0起,官方推荐使用draw.textbbox()替代已被弃用的draw.textsize()。textbbox返回的是文本在指定坐标下的外接矩形四元组(left, top, right, bottom),其宽度计算为right - left,高度为bottom - top。
import os
from pathlib import Path
from typing import Optional
from PIL import Image, ImageDraw, ImageFont
import segno
def generate_labeled_qr(
content: str,
label: str,
output_path: Path,
scale: int = 8,
border: int = 4,
error: str = "m",
font_path: Optional[str] = None,
font_size: int = 24,
text_color: str = "#1E293B",
bg_color: str = "#FFFFFF"
) -> None:
"""
生成单张带明文编号的二维码图片并保存
:param content: 二维码中编码的原始数据(URL 或资产编号)
:param label: 二维码底部展示的明文字符串
:param output_path: 保存的文件路径 (Path 对象)
:param scale: 二维码每个黑白模块占用的像素宽度
:param border: 二维码边缘静区模块数量
:param error: 纠错级别 ('l', 'm', 'q', 'h')
:param font_path: 本地 TTF/OTF 字体文件绝对路径
:param font_size: 明文字体字号
:param text_color: 文本颜色 HEX
:param bg_color: 背景颜色 HEX
"""
# 1. 使用 segno 编码生成二维码矩阵
qr = segno.make(content, error=error)
# 2. 转换成 PIL 图像对象并转为 RGB 模式
qr_img = qr.to_pil(scale=scale, border=border, dark="black", light=bg_color).convert("RGB")
qr_w, qr_h = qr_img.size
# 3. 字体加载(具备降级保护)
font: ImageFont.ImageFont
if font_path and os.path.exists(font_path):
try:
font = ImageFont.truetype(font_path, font_size)
except Exception:
font = ImageFont.load_default()
else:
font = ImageFont.load_default()
# 4. 创建临时画笔用于精确计算文本边界
temp_draw = ImageDraw.Draw(qr_img)
bbox = temp_draw.textbbox((0, 0), label, font=font)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
# 5. 计算下方预留区域的高度与留白(上下各预留一定 padding)
vertical_padding = 16
text_area_height = text_h + (vertical_padding * 2)
# 6. 创建复合画布
total_w = qr_w
total_h = qr_h + text_area_height
canvas = Image.new("RGB", (total_w, total_h), bg_color)
# 7. 贴合二维码到顶部
canvas.paste(qr_img, (0, 0))
# 8. 在拓展区域绘制居中文本
draw = ImageDraw.Draw(canvas)
text_x = (total_w - text_w) // 2
# 纵向居中对齐到拓展区
text_y = qr_h + (text_area_height - text_h) // 2
draw.text((text_x, text_y), label, fill=text_color, font=font)
# 9. 确保父级目录存在并持久化保存
output_path.parent.mkdir(parents=True, exist_ok=True)
canvas.save(output_path, "PNG", optimize=True)
代码实现解析:我们在贴合二维码与绘制文字时,采用的是纯内存对象流转。没有在磁盘上生成临时未带编号的图片再二次读取,这使得单张图片的构建开销极低。在大批量执行时,内存回收更为即时,执行速度相比临时文件方式提升数倍。
四、参数调优与规格权衡对照
在批量制作二维码标签前,根据物理印刷与扫码环境选择合适的参数至关重要。不同的纠错级别、模块像素放大系数以及静区尺寸直接决定了成品的识别成功率。
五、完整的批量生成执行脚本
在实际生产中,数据来源通常分为两类:一类是按照既定规则连续自增的序列编号(如固定前缀拼接流水号),另一类是从外部业务系统导出的结构化数据表格(如CSV文件)。以下提供一个完整的工业级脚本,涵盖路径管理、规则格式化、异常跳过与进度反馈机制。
import sys
import time
from pathlib import Path
from typing import List, Dict, Optional
from PIL import Image, ImageDraw, ImageFont
import segno
def create_labeled_qr_image(
data_payload: str,
display_code: str,
output_filepath: Path,
scale: int = 8,
border: int = 4,
error_level: str = "m",
font_filepath: Optional[str] = None,
font_size: int = 22,
) -> None:
"""生成带编号二维码图片的核心逻辑"""
qr = segno.make(data_payload, error=error_level)
qr_img = qr.to_pil(scale=scale, border=border, dark="black", light="white").convert("RGB")
qr_w, qr_h = qr_img.size
# 加载字体
if font_filepath and Path(font_filepath).exists():
try:
font = ImageFont.truetype(font_filepath, font_size)
except Exception:
font = ImageFont.load_default()
else:
font = ImageFont.load_default()
# 计算文本尺寸
temp_draw = ImageDraw.Draw(qr_img)
bbox = temp_draw.textbbox((0, 0), display_code, font=font)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
# 画布延展:底部预留高度为文字高度加上 20px 间距
bottom_margin = text_h + 20
canvas_w = qr_w
canvas_h = qr_h + bottom_margin
# 创建新画布并拼合
canvas = Image.new("RGB", (canvas_w, canvas_h), "white")
canvas.paste(qr_img, (0, 0))
draw = ImageDraw.Draw(canvas)
text_x = (canvas_w - text_w) // 2
text_y = qr_h + (bottom_margin - text_h) // 2
draw.text((text_x, text_y), display_code, fill="#111827", font=font)
# 确保存储目录存在
output_filepath.parent.mkdir(parents=True, exist_ok=True)
canvas.save(output_filepath, "PNG", optimize=True)
def batch_generate_by_sequence(
prefix: str,
start_num: int,
total_count: int,
output_directory: Path,
url_template: str = "https://assets.local/item/{code}",
font_path: Optional[str] = None
) -> None:
"""
根据自增编号批量生成二维码
:param prefix: 编号前缀,例如 'AST-2026-'
:param start_num: 起始数值序号,例如 1
:param total_count: 生成总数
:param output_directory: 图片输出目标目录
:param url_template: 写入二维码内部的载荷 URL 模板
:param font_path: 字体路径
"""
print(f"[*] 开始批量生成任务: 数量={total_count}, 目标路径={output_directory}")
success_count = 0
start_time = time.time()
for idx in range(total_count):
current_seq = start_num + idx
# 使用 4 位补零格式化编号
code_str = f"{prefix}{current_seq:04d}"
payload_str = url_template.format(code=code_str)
target_file = output_directory / f"{code_str}.png"
try:
create_labeled_qr_image(
data_payload=payload_str,
display_code=code_str,
output_filepath=target_file,
scale=8,
border=4,
error_level="m",
font_filepath=font_path,
font_size=20
)
success_count += 1
if (idx + 1) % 50 == 0 or (idx + 1) == total_count:
print(f" 进度: 已完成 {idx + 1}/{total_count} 张二维码生成")
except Exception as err:
print(f"[!] 生成条目 {code_str} 时发生异常: {err}", file=sys.stderr)
elapsed = time.time() - start_time
print(f"[+] 任务执行完毕: 成功={success_count}/{total_count}, 耗时={elapsed:.2f}秒")
if __name__ == "__main__":
# 示例执行入口
export_dir = Path("./asset_qrcodes_output")
# 可根据操作系统指定本地中英文字体路径,例如 macOS 下的 Arial 或 Windows 下的 msyh.ttc
sample_font = "/System/Library/Fonts/Supplemental/Arial.ttf"
batch_generate_by_sequence(
prefix="SN-2026-DEV-",
start_num=1,
total_count=100,
output_directory=export_dir,
url_template="https://mgmt.internal/device/{code}",
font_path=sample_font
)
六、工程落地中的易错点与应对方案
在实际批量生成和产线打印过程中,经常会遇到由于字体缺失、字符集编码或打印机驱动导致的异常情况。针对这些常见陷阱,需采取针对性的防御措施:
1. 字体跨平台部署与回退
在不同操作系统(Linux 服务器、macOS、Windows)之间迁移脚本时,字体路径极易失效。例如在容器化(Docker)环境下,基础镜像往往没有安装常规的中文字体。如果直接使用未做异常保护的ImageFont.truetype(),会导致整个批量任务抛出OSError中断。建议将标准开源字体(如阿里巴巴普惠体、思源黑体或Roboto)放置在项目仓库的assets/fonts目录下,采用相对路径加载;同时在代码中保留ImageFont.load_default()作为降级策略。
2. 二维码内容与明文编号的一致性
在资产标签中,二维码内部载荷通常是带有参数的完整访问地址(如 https://domain.com/q/AST0001),而下方印制的则是精简编号(如 AST0001)。在批量生成时,必须确保用于文件命名的字符串、载荷中编码的字符串与底部印制的字符串具有确定性的映射关系,建议使用统一的数据模型或字典对象驱动,杜绝因变量混淆出现“码与字不对应”的致命错误。
3. 热敏打印机的“静区被裁切”问题
部分工业条码打印机或标签纸可能存在机械走纸偏移。如果将二维码严格贴近标签顶边,极易被物理裁切掉部分静区,导致识别率断崖式下跌。在确定画布尺寸时,不仅要满足border=4的模块要求,必要时还可在二维码上方也留出 8~12 像素的额外保护留白。
七、总结
结合segno与Pillow批量生成带编号二维码图片,是一种兼顾轻量化、高可靠与优美排版的成熟方案。通过在纯内存环境中将二维码矩阵转换为图像对象,并在延展画布上精准标绘居中明文编号,开发者能够以清晰、模块化的代码解决仓储贴标与资产管理中的核心需求。
在工程化落地时,合理选择M级纠错与scale=8以上的模块放大系数、保持符合ISO标准的4模块静区、实施定长补零命名,并辅以跨平台字体降级方案,能够确保大批量生成的二维码标签无论在数字预览还是物理打印中均具备极佳的稳定性和识读体验。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













