我给自己写了一个本地二维码生成器:离线生成、批量导出、样式可控
目录
- 缘起:生成一个二维码为什么还要上传内容
- 需求拆解:能生成不代表真的能用
- 技术选型:Python 加两个依赖就够了
- 核心实现一:从文本到二维码矩阵
- 核心实现二:批量生成与文件导出
- UI 定制:所有设置都应该即时可见
- 三个影响扫码成功率的坑
- 结尾:工具离线之后,内容才真正属于自己
1. 缘起:生成一个二维码为什么还要上传内容
二维码是我工作中经常用到、却很少认真思考的东西。临时分享一个网址、制作设备编号、给文件下载地址配一张图,通常都会随手打开某个在线生成器。
用久之后,我开始在意几个问题:
- 有些网站会把输入内容发送到服务器后再生成图片;
- 页面上广告很多,下载按钮和推广按钮混在一起;
- 导出高清图片或透明背景时要求登录;
- 批量生成几十个编号,需要一条一条复制粘贴;
- 网站下线或规则变化后,以前的操作流程就失效了。
二维码的生成算法本来就可以在本地完成。我的内容既不需要云端保存,也不需要短链接跳转,那么把它交给第三方就没有必要。
于是我决定写一个离线工具:输入内容后立刻预览,确认无误就保存为 PNG;需要批量生成时读取 CSV,一次输出全部图片。
2. 需求拆解:能生成不代表真的能用
我没有把目标写成“生成二维码”,而是拆成了更容易验收的项目:
| 诉求(人话) | 拆解后(可验收) |
|---|---|
| 输入后马上看到结果 | 内容或参数变化后自动刷新预览 |
| 手机能够稳定识别 | 保留静区,限制低对比度颜色组合 |
| 图片能直接使用 | 支持 PNG、固定尺寸和透明背景 |
| 不同场景可调整 | 提供四种纠错级别和边距设置 |
| 大量编号一次完成 | 从 CSV 读取内容,按指定列批量生成 |
| 内容不外传 | 生成过程不发起任何网络请求 |
第一版不做“动态二维码”。所谓动态二维码通常依赖一个在线跳转地址,后续才能修改目标内容,这与完全离线的目标相冲突。我只生成静态二维码,图中编码的就是用户输入的原始内容。
心得:先弄清二维码里真正保存的是什么,才能判断工具是否真的离线。
3. 技术选型:Python 加两个依赖就够了
程序使用 Python 编写,界面依然选择标准库里的 tkinter,二维码生成和图片处理交给成熟库:
| 能力 | 方案 |
|---|---|
| 二维码编码 | qrcode |
| 图片处理 | Pillow |
| 桌面界面 | tkinter |
| 批量数据读取 | csv |
| 配置保存 | JSON |
我没有自己实现二维码编码算法。它包含版本选择、数据分段、掩码和纠错码等细节,自行实现不仅工作量大,也容易生成“看起来像二维码、部分设备却扫不出来”的结果。
这个工具真正需要定制的是输入流程、参数约束、批量命名和导出体验,而不是重新发明编码算法。
心得:成熟标准优先使用经过验证的库,把精力放在自己真正要解决的交互问题上。
4. 核心实现一:从文本到二维码矩阵
生成一个基础二维码并不复杂。内容统一使用 UTF-8 编码,库会根据数据长度自动选择合适的二维码版本。
import qrcode
from qrcode.constants import ERROR_CORRECT_M
def make_qr(text, box_size=10, border=4):
qr = qrcode.QRCode(
version=None,
error_correction=ERROR_CORRECT_M,
box_size=box_size,
border=border,
)
qr.add_data(text)
qr.make(fit=True)
return qr.make_image(
fill_color="#111111",
back_color="#ffffff",
)
version=None 配合 fit=True,可以让库按照内容量自动决定矩阵大小。用户不需要理解二维码有多少个版本,只要在内容过长时收到明确提示即可。
预览不会在每次按键后立即生成。我加了约 200 毫秒的防抖:用户连续输入时取消上一次刷新任务,短暂停顿后才重新编码。这样粘贴长文本或快速修改内容时,界面不会反复计算和闪烁。
预览区旁边还会显示三项信息:字符数、生成后的模块数量,以及当前纠错级别。它们比一个笼统的“生成成功”更有帮助,尤其是在二维码越来越密时,用户能及时缩短内容或增大导出尺寸。
5. 核心实现二:批量生成与文件导出
批量模式读取 CSV 中指定的一列,一行生成一张图片。文件名来自另一列,但必须先清理系统不允许的字符。
import re
INVALID_CHARS = re.compile(r'[\\/:*?"<>|]')
def safe_filename(value, fallback):
name = INVALID_CHARS.sub("_", value).strip().rstrip(".")
return name or fallback
批量导出前先建立完整计划,而不是边读取边保存:
def build_export_plan(rows, name_key, data_key, output_dir):
plan = []
for index, row in enumerate(rows, start=1):
data = row[data_key].strip()
filename = safe_filename(row[name_key], f"qr_{index:04d}")
plan.append((data, output_dir / f"{filename}.png"))
return plan
建立计划后,程序会检查空内容、重复文件名和已存在目标。只有所有项目都通过校验,才开始生成。这能避免处理到一半才发现两个编号生成了同名文件。
图片导出时不直接把小预览图放大,而是用相同内容重新按目标尺寸渲染。二维码的边缘需要保持清晰,使用普通插值缩放可能让黑白模块出现灰色过渡,反而降低识别率。
批量完成后,程序会输出一份 CSV 报告,记录原始行号、编码内容、目标文件和生成结果。失败项不会被悄悄跳过。
心得:批量功能最重要的不是速度,而是执行前可预见、执行后可核对。
6. UI 定制:所有设置都应该即时可见
界面左侧是内容输入和参数,右侧是二维码预览。常用操作始终在一个窗口完成:
- 文本框支持直接粘贴多行内容;
- 纠错级别使用简单下拉框,并显示适用说明;
- 前景色和背景色旁边实时显示对比度提示;
- 尺寸和边距变化会立即更新预览;
- “保存单张”和“批量生成”分成两个清晰入口。
我没有提供任意拖动定位点、渐变填充或复杂图案。二维码首先是机器读取的数据载体,其次才是一张视觉图片。装饰越自由,越容易破坏定位图形和模块边界。
保存按钮旁边会显示预计导出尺寸,例如 1200 × 1200 PNG。透明背景被单独标注风险,因为很多扫码场景最终会把透明区域叠到不可控的底色上。
界面底部还有一个“测试记录”区域。用户可以用手机扫码后手动标记通过,程序会记住本次使用的尺寸、颜色和纠错级别,便于之后重复使用经过验证的模板。
7. 三个影响扫码成功率的坑
坑一:边距太小。 二维码四周的空白区域叫静区,识别器需要它区分二维码与周围内容。为了让图片看起来更满而把边距裁掉,可能导致二维码在复杂背景上无法识别。工具默认保留 4 个模块宽度,并对过小设置给出警告。
坑二:颜色有差异不等于对比度足够。 深蓝配黑色、浅灰配白色肉眼能看出不同,摄像头在弱光和失焦情况下却可能分不清。我的工具会计算相对亮度,前景与背景对比不足时禁止批量导出,并建议使用深色前景和浅色背景。
坑三:中文内容能生成,不代表所有扫码器解释一致。 二维码保存的是字节,不同应用对纯文本编码和特殊协议的处理可能不同。包含中文、换行或特殊字符时,必须用实际目标设备测试;如果业务允许,编码一个规范的 HTTPS 地址通常兼容性更好。
另外,在中心加入 Logo 不能只依赖高纠错级别。Logo 尺寸、位置和周围留白都会影响结果。第一版干脆不提供 Logo 功能,避免给用户“选了高级纠错就一定安全”的错觉。
8. 结尾:工具离线之后,内容才真正属于自己
完成这个二维码生成器后,我最喜欢的不是多了几种颜色选项,而是生成过程变得可解释:输入什么,二维码里就保存什么;文件在哪里生成,完全由自己决定;没有短链接,也没有隐藏的跳转服务。
一个可靠的本地二维码工具应该做到:
- 生成过程不依赖网络;
- 默认参数优先保证可扫描性;
- 批量执行前检查全部目标;
- 明确区分静态二维码与在线跳转服务;
- 鼓励在真实设备和真实印刷尺寸下测试。
二维码并不神秘,它只是把一段数据编码成黑白模块。真正容易出问题的,是为了好看不断压缩边距、降低对比度,或者把关键内容交给无法控制的在线服务。
如果你也经常制作二维码,可以先做一个最小版本:输入文本、生成黑白二维码、保存 PNG。等实际遇到批量编号或固定模板需求时,再逐步加入 CSV 和样式设置。
工具越简单,数据流向越清楚;内容留在本地,使用时也会更踏实。
- 点赞
- 收藏
- 关注作者
评论(0)