前阵子接了个需求,运营同事要把三百多个产品编号做成二维码,每个码扫出来是对应的详情链接,文件名还得是“产品名_编号”这种格式,方便他们直接归档。我一开始想用在线二维码生成器,可一个一个手动弄实在受不了,复制粘贴、命名、下载,弄了二十个就崩溃了。于是干脆花了两个晚上写了个PyQt桌面小工具,就是今天想聊的Q-Generator Pro:一键批量生成内容名称自定义二维码。这工具解决的就是一个很具体的痛点——当你有几十上百条内容需要变成二维码,并且对文件名、存放目录、图片尺寸都有要求时,不用再对着网页工具机械操作,而是把数据整理成表格,点一次按钮全部搞定。如果你也经常被批量生成二维码折磨,或者想找一个PyQt5实战项目练手,这篇内容值得看完。
先说个大概。Q-Generator Pro是一个基于PyQt5的桌面应用,核心功能有三个:支持从TXT、CSV、Excel导入批量内容,支持自定义文件名规则(比如用内容里的某个字段加编号),支持批量生成二维码并输出到指定文件夹。整个工具没有复杂的概念,操作路径就是“导入数据 -> 设置命名规则 -> 点生成”,5分钟内能上手。我会把背后的技术选型、界面设计思路、关键代码实现全部拆开讲,最后再分享我实际踩过的几个坑,希望能帮你避开同样的弯路。
1. 项目整体设计:为什么是PyQt5而不是Web工具
1.1 这个工具到底解决了什么问题
批量生成二维码这件事,网上不是没有工具,但大多卡在“批量”这两个字上。在线生成器通常只支持单条输入,偶尔支持批量导入的,又限制条数、限制图片尺寸,导出的文件名还是随机字符串,跟业务完全对不上。命令行工具倒是可以用脚本实现,但让运营同事打开终端跑Python脚本,这个门槛比手动复制粘贴还高。
所以我当时的目标很明确:做一个双击就能打开的桌面程序,让不懂技术的人也能操作。界面要直观,数据要能预览,生成过程要有进度条,出错了要能看清楚是哪一条出了问题。PyQt5在这个场景下几乎是唯一合理的选择,它本身就是为桌面GUI而生的,控件齐全、布局灵活、支持QThread做后台任务,还能用PyInstaller打包成单个exe发给别人用。
1.2 技术选型对比:PyQt5、Tkinter、Web方案
我在动手前对比过几种方案,这里直接说结论。
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| PyQt5 + qrcode + Pillow | 界面美观、控件丰富、离线运行、打包成熟 | 代码量稍大,需要懂一点Qt的事件循环 | 需要图形界面、交互较多、要分发给非技术同事 |
| Tkinter + qrcode | 内置库、轻量 | 界面丑、控件少、批量表格功能弱 | 自用小工具,不追求界面 |
| Web页面 + JS库(如qrcode.js) | 前端生态丰富、样式灵活 | 需要浏览器环境,导出文件批量下载麻烦 | 在线工具,不涉及本地文件批量操作 |
| 纯命令行 + qrcode | 代码最简单 | 对非技术用户不友好 | 自己是唯一用户 |
如果你只是自己用,命令行脚本完全够用,十几行代码就能解决。但一旦要交付给别人用,PyQt5的优势就很明显了:表格预览、进度条、错误日志、文件选择对话框,这些交互用Web实现反而要写更多前端逻辑,用Tkinter实现又不够漂亮。所以我最终选了PyQt5作为GUI框架。
1.3 功能边界与设计取舍
做工具最怕功能越加越多,最后变成一个谁都不好用的四不像。我在设计Q-Generator Pro时给自己定了三个必须做的核心功能,其他一概砍掉:
- 批量导入:支持TXT、CSV、Excel,这是批量工作的基础。
- 自定义命名规则:文件名不能是随机串,要能按业务规则来。
- 批量生成与进度反馈:生成过程中有进度条,完成后有统计。
至于扫码识别、二维码美化、艺术二维码这些功能,属于另一个方向,生成和识别本来就是两套逻辑,硬塞进来只会让界面臃肿。我的建议是,做工具先做窄做深,把核心流程跑顺了,再考虑扩展。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心功能拆解与界面设计要点
2.1 界面布局:三类区域分区管理
Q-Generator Pro的界面我设计成三块区域,从上到下依次是参数配置区、数据预览区、日志与进度区。这个布局思路跟很多数据处理工具的界面类似,核心原则是“配置在上、数据在中、反馈在下”。
参数配置区放在顶部,包含内容来源选择(手动输入/从文件导入)、命名规则输入框、输出目录选择、图片尺寸和容错率选项。数据预览区是中间的大表格,导入的数据会实时显示在这里,每一行代表一条待生成的二维码记录,双击单元格可以手动修改,这个功能在实际使用中非常方便,因为Excel整理的数据总有个别行需要微调。日志与进度区在底部,用来显示生成日志和进度条,出错时能看到具体是哪一行、什么原因失败。
这种布局的好处是操作逻辑非常线性:先配置,再检查数据,最后点生成看结果。不会出现找不到某个按钮在哪里的问题。
2.2 批量导入的底层思考
批量导入是整个工具的核心入口,我在实现时做了三个数据来源:TXT、CSV、Excel。TXT的处理最简单,每行一条二维码内容,文件名默认用序号;CSV和Excel则支持两列,第一列是二维码内容,第二列是文件名前缀,如果第二列为空,就自动用“未命名_序号”兜底。
这里有个关键细节:读取Excel时我用了pandas的openpyxl引擎,如果表格里有表头,需要跳过第一行,否则会把表头当成一条真实数据生成。所以在界面上我加了一个“是否包含表头”的复选框,默认勾选,这是我在实际使用中遇到过的坑——第一次读取的时候忘了跳表头,结果生成了一个内容是列名的二维码,文件名叫“二维码内容.png”,排查了半天才反应过来。
小贴士:如果你用Python处理Excel,建议在读取后print出DataFrame的前几行看一眼,这种低级错误能快速暴露。
2.3 命名规则与输出策略的设计
命名规则是Q-Generator Pro和其他二维码工具拉开差距的地方。我设计了一套简单的模板语法:用{name}代表内容中的第二列(名称字段),用{index}代表序号,用{content}代表第一条内容,用户可以在输入框里自由组合,比如输入{name}_{index},生成的文件就是“产品A_001.png”。
这个设计借鉴了Word邮件合并的思路——把一张表映射成一个模板,批量产出结果。实际使用中,运营同事最常用的是{name}_{index}和{content}_{index}这两种组合。需要注意的是,文件名不能包含\ / : * ? " < > |这些非法字符,所以我在生成文件名时做了正则替换,遇到这些字符一律换成下划线。
输出策略方面,我默认在目标目录下创建一个“二维码输出_时间戳”的文件夹,避免覆盖以前的生成结果。如果文件名重复,自动追加_1、_2后缀,而不是直接覆盖。这些细节看似简单,但能避免很多文件管理上的麻烦。
3. 实操过程与关键代码实现
3.1 环境准备与依赖安装
在开始写代码之前,先要把依赖装好。我用的是Python 3.10,PyQt5的兼容性很好,3.8到3.12都没问题。直接执行:
bash复制pip install PyQt5 qrcode[pil] pandas openpyxl
这里稍微解释一下依赖的作用:
- PyQt5:GUI框架,负责整个界面。
- qrcode:生成二维码的核心库,
[pil]后缀表示会安装Pillow,用来处理图片输出。 - pandas + openpyxl:读取Excel和CSV,是批量数据的来源。
- Pillow:处理图片缩放和格式转换,qrcode生成图片后需要它来做尺寸调整。
安装完成后,可以用一行代码验证环境是否正常:
python复制import qrcode
from PyQt5.QtWidgets import QApplication
print("dependencies ok")
如果这行代码输出了“dependencies ok”,说明环境没问题,可以开始写主程序了。
3.2 二维码生成的核心实现
二维码生成的核心逻辑其实很简单,关键是参数怎么调。qrcode库的QRCode类提供了几个重要参数:version控制二维码版本(1到40,数字越大容量越大),error_correction控制容错率,box_size控制每个格子的像素大小,border控制边框宽度。
我封装了一个生成函数,代码如下:
python复制import qrcode
from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, ERROR_CORRECT_H
from PIL import Image
from pathlib import Path
def generate_single_qr(content, output_path, size=512, error_correction=ERROR_CORRECT_M):
"""
生成一张二维码图片并保存到指定路径。
content: 二维码内容
output_path: 输出图片路径
size: 输出图片的像素尺寸(正方形)
error_correction: 容错级别,默认M
"""
if not content or not content.strip():
raise ValueError("二维码内容不能为空")
qr = qrcode.QRCode(
version=None,
error_correction=error_correction,
box_size=20,
border=4,
)
qr.add_data(content)
qr.make(fit=True)
# make_image()返回PIL Image对象,便于后续处理
img = qr.make_image(fill_color="black", back_color="white").convert("RGB")
# 用LANCZOS重采样放大,保证图片放大后边缘仍然清晰
img = img.resize((size, size), Image.Resampling.LANCZOS)
img.save(output_path, "PNG")
这里有两个点值得注意。第一,version=None + fit=True的组合会让qrcode库根据内容长度自动选择最小的版本,这是推荐的做法,手动指定版本很容易出现容量不够或者图片留白过多的问题。第二,box_size=20和border=4只是中间参数,最终图片会被缩放到size指定的像素值,我用的是512,打印场景建议设成1024,普通屏幕展示用256就够了。
3.3 批量处理与文件命名规则实现
有了单张生成的函数,批量处理其实就是一个遍历列表的过程。我把每条记录抽象成一个字典,包含content和name两个字段,然后加上序号和命名模板,生成最终文件名。这一段是批量生成的核心逻辑:
python复制import re
from pathlib import Path
def sanitize_filename(filename):
"""把文件名中非法字符替换为下划线"""
return re.sub(r'[\\/:*?"<>|]', '_', filename)
def build_output_path(output_dir, name, index, content, template="{name}_{index}"):
"""
根据命名模板生成最终输出路径。
支持 {name} {index} {content} 三个变量。
"""
filename = template.format(
name=sanitize_filename(name),
index=str(index).zfill(3),
content=sanitize_filename(content[:50]) # 内容截断,避免文件名过长
)
if not filename:
filename = f"qr_{index:03d}"
return Path(output_dir) / f"{filename}.png"
def process_tasks(tasks, output_dir, size, error_correction, template="{name}_{index}", skip_existing=False):
"""
tasks: 列表,每个元素是 {'content': str, 'name': str}
返回: (成功数, 失败列表)
"""
Path(output_dir).mkdir(parents=True, exist_ok=True)
success_count = 0
failed = []
for i, task in enumerate(tasks, start=1):
content = task.get("content", "").strip()
name = task.get("name", f"未命名_{i:03d}")
if not content:
failed.append((i, content, "内容为空"))
continue
output_path = build_output_path(output_dir, name, i, content, template)
# 处理文件名冲突
counter = 1
original_path = output_path
while output_path.exists():
if skip_existing:
break
output_path = original_path.with_name(f"{original_path.stem}_{counter}{original_path.suffix}")
counter += 1
if output_path.exists() and skip_existing:
continue
try:
generate_single_qr(content, output_path, size, error_correction)
success_count += 1
except Exception as e:
failed.append((i, content, str(e)))
return success_count, failed
这段代码把“命名”和“生成”拆成了两个纯函数,好处是便于单测,也方便以后扩展别的命名规则。with_name是Path对象的方法,在文件名冲突时自动追加序号,比如“产品A_001_1.png”,这个逻辑我在实际使用中验证过很多次,省了很多手工改名的功夫。
3.4 界面搭建设计与QThread处理耗时任务
界面部分我用的是PyQt5的QMainWindow,整体布局是垂直的:顶部是参数区(QHBoxLayout),中间是表格区(QTableWidget),底部是按钮、进度条和日志区。这里贴一段简化版的界面代码,重点看你如何把前面的生成逻辑接到界面上:
python复制from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLabel, QLineEdit, QPushButton,
QSpinBox, QComboBox, QTableWidget, QTableWidgetItem,
QProgressBar, QFileDialog, QTextEdit, QMessageBox,
QHeaderView, QAbstractItemView)
from PyQt5.QtCore import QThread, pyqtSignal
import sys, traceback, time
class GenerateWorker(QThread):
"""
后台生成线程,避免阻塞UI。
"""
progress_changed = pyqtSignal(int, int) # 已完成数,总数
log_message = pyqtSignal(str)
finished_all = pyqtSignal(int, list)
def __init__(self, tasks, output_dir, size, error_correction, template, skip_existing, parent=None):
super().__init__(parent)
self.tasks = tasks
self.output_dir = output_dir
self.size = size
self.error_correction = error_correction
self.template = template
self.skip_existing = skip_existing
self._is_cancelled = False
def cancel(self):
self._is_cancelled = True
def run(self):
total = len(self.tasks)
success = 0
failed = []
for i, task in enumerate(self.tasks, start=1):
if self._is_cancelled:
self.log_message.emit("任务已取消")
break
content = task.get("content", "").strip()
name = task.get("name", f"未命名_{i:03d}")
if not content:
failed.append((i, content, "内容为空"))
self.progress_changed.emit(i, total)
continue
try:
output_path = build_output_path(self.output_dir, name, i, content, self.template)
# 此处省略冲突处理,逻辑同process_tasks
generate_single_qr(content, output_path, self.size, self.error_correction)
success += 1
except Exception as e:
failed.append((i, content, str(e)))
self.progress_changed.emit(i, total)
self.finished_all.emit(success, failed)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Q-Generator Pro - 批量二维码生成")
self.resize(900, 650)
self.worker = None
self._init_ui()
def _init_ui(self):
# 此处省略完整控件初始化的代码,核心控件包括:
# QComboBox: 数据来源
# QLineEdit: 命名模板
# QPushButton: 选择文件、选择目录、开始生成
# QTableWidget: 数据预览
# QProgressBar: 进度条
# QTextEdit: 日志区
pass
def on_generate_clicked(self):
# 从表格里收集数据并启动线程
pass
我把耗时操作放到QThread子类GenerateWorker里执行,这是PyQt5开发中必须养成的一个习惯。原因是Qt的主线程负责界面刷新,如果直接在槽函数里跑一个大的for循环,界面会直接卡死,表现为窗口无响应、进度条不动,用户会以为程序崩溃了。GenerateWorker通过三个信号向主界面汇报进度、日志和最终结果,主界面只需连接这些信号来更新控件即可。
小建议:界面代码别写在一个类里动辄上千行,我习惯把“生成逻辑”放在纯Python模块里,界面只做数据搬运。这样以后即使想把它改成命令行工具或Web服务,复用起来也容易。
3.5 打包成exe分发
工具写完后,最终要发给同事用,我是用PyInstaller打包的。命令很简单:
bash复制pyinstaller -F -w -n QGeneratorPro main.py
这里的参数含义是:-F打包成单个exe文件,-w表示不显示控制台窗口,-n指定输出文件名。如果你需要图标,可以加上--icon=app.ico。
打包完成后,会在dist目录下生成QGeneratorPro.exe。有两点需要注意:第一,PyQt5打包出来的exe体积比较大,通常在60MB以上,这是正常的;第二,如果目标机器上没有安装VC++运行库,exe可能无法启动,我有一个小技巧是把PyQt5的Qt插件目录一起打包进来,用--collect-all PyQt5可以把所有依赖都收集齐,虽然体积更大,但兼容性最好。
4. 常见问题与排查技巧实录
4.1 高频问题速查表
我在开发和使用Q-Generator Pro的过程中,遇到过不少问题,这里整理几个高频的,方便你遇到问题时直接对照排查。
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 点击生成后界面卡死 | 生成逻辑直接跑在UI主线程 | 改用QThread,把生成任务放到后台线程 |
| 生成的二维码扫不出来 | 内容太长超过二维码容量,或者容错率太低 | 减少内容长度,或选择H级容错率;不要盲目选最大尺寸 |
| 生成的图片看起来模糊 | box_size参数太小,或缩放算法用了默认的最近邻 | box_size设到20以上,resize时用Image.Resampling.LANCZOS |
| 读取Excel时中文列名乱码 | openpyxl引擎读取时未处理编码 | 用pandas读取时指定dtype=str,并在代码开头统一用UTF-8 |
| 文件名包含特殊字符导致保存失败 | Windows下文件名不能包含`\ / : * ? " < > | ` |
| 打包后exe在别人电脑上打不开 | 缺VC++运行库或Qt插件没打全 | 用--collect-all PyQt5打包,并测试一台完全干净的机器 |
| 批量大时内存占用高 | 一次性把所有图片都读入内存 | 一张生成完立刻保存,释放内存;不要把所有PIL.Image对象放在列表里 |
4.2 几个独家心得与避坑技巧
第一,容错级别不要无脑选最高。qrcode提供L、M、Q、H四个级别,H级容错最高但容量最小,当内容超过一定长度时反而可能生成失败。我的测试经验是:普通链接用M级,内容特别重要或预计二维码会被遮挡、磨损时用H级,否则M级足够了。盲目选H级可能导致生成的二维码信息密度过高,扫码时反而不容易被识别。
第二,处理大量数据前,先在批量任务中加一个“只生成前3条”的测试模式。我刚开始用这个工具时,直接在三百多条数据上跑全量生成,结果第一条就报错,程序直接崩了。后来我加了测试模式,点击按钮后只生成前3条,确认没问题再全量跑,省了很多时间。
第三,把日志写到内存队列,不要只在界面显示。因为如果程序崩溃或用户手动关闭,界面上的日志就没了。我在工具里把日志同时追加到一个logs列表,崩溃后还能查看内存里的内容。更稳妥的做法是同时写到一个日志文件,但我为了保持exe的整洁,选择了内存队列记录最近500条日志。
第四,文件名里尽量不要带中文字符,除非你确认目标系统完全支持。Windows的NTFS文件系统对中文路径支持没问题,但有些扫码后对接的业务系统可能对中文文件名不友好,所以我在工具里做了一个开关:默认把中文名转为拼音缩写,需要时再改回来。这个功能在实际业务中帮了不少忙。
写在最后的一点经验
我在实际使用Q-Generator Pro的过程中,最大的体会是:桌面工具的价值不在于技术多复杂,而在于把重复劳动压缩到一次点击。原来运营同事手动生成三百个二维码可能要花一个下午,现在从整理Excel到全部生成,十分钟搞定。而且因为是本地离线运行,数据安全可控,不会像在线工具那样担心内容被第三方服务器记录。如果你也想做一个类似的批量处理工具,我的建议是先把核心流程跑通,哪怕界面简陋一点,也比一开始就追求完美更重要。工具做出来之后,一定要交给真实用户试用一轮,你会发现他们提出的需求和你自己想的完全不一样。这个工具后续还可以往下扩展,比如批量生成后自动压缩成zip、支持从数据库读取数据、加一个历史记录面板,这些都是很实用的方向,但那是下一个版本的事了。
