ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

公众号二维码批量导出与Logo合并自动化实战

公众号二维码批量导出与Logo合并自动化实战 做公众号矩阵运营或者给品牌方做增长物料的朋友十有八九都干过这种活儿公众号后台下载二维码打开设计工具把Logo贴上去再导出重命名周而复始。刚开始只有三五个号还能忍等到手里捏着几十个号、上百个活动物料要投放的时候这种纯手工流程就变成了一场灾难。这篇文章想聊的就是把“公众号二维码批量导出并选择性合并Logo”这条链路彻底自动化。你可能是运营、是设计、是写脚本的程序员只要手头需要批量处理公众号二维码这套思路都能直接抄。重点会放在二维码类型怎么区分、技术方案怎么选、Logo合并的正确比例和实现细节、高频踩坑点排查全部是实测可复现的内容。1. 需求拆解与方案定型1.1 先分清你要导出的是哪一种二维码很多人一上来就问“怎么批量导出公众号二维码”其实公众号体系里二维码并不只有一种搞混了后面全是坑。第一种是后台直接下载的关注二维码。登录公众号后台在“设置与开发-公众号设置-二维码下载”里就能拿到。它代表的是用户扫码关注公众号这个动作每个人拿到的都是同一张没有渠道追踪能力适合线下物料、名片、海报这类纯曝光场景。第二种是带参数二维码也叫场景二维码。这需要使用公众号的开放接口来创建开发者可以给每个渠道分配不同的参数扫码后微信回调事件里会带上scene_id运营就能知道这个二维码是哪个活动、哪个推广位带来的关注。这种二维码在后台界面里看不到现成的必须通过接口拿ticket再换取二维码图片。批量导出如果要做渠道追踪就必须走接口这条路。还有第三种是外部平台生成的活码本质上是把扫码后跳转的目标地址做成可变配置跟公众号自身没关系不在讨论范围内。实操里最常见的场景反而是两个一是手里已经有一批从后台下载好的官方二维码图片只需要批量贴Logo、统一命名输出二是需要创建几十个带参二维码并同时覆盖Logo。这两种需求前半段路径不同后半段的Logo合并逻辑完全一致下面会分别讲清楚。1.2 手动流程和自动化流程的真实差距先算一笔账。单个二维码从后台下载到贴Logo导出熟练的话三到五分钟不熟练十分钟打底。三十个二维码乐观估计也要两三个小时而且中途大概率会出现某几个码命名不统一交付出去被前端吐槽Logo尺寸忽大忽小有的贴中间有的贴右下角视觉根本不统一漏掉某一个码没贴Logo投放出去才被发现品牌方突然说Logo要换新版所有图全部重做。这些问题的本质不是操作者不细心而是人工流程天然不可控。自动化方案的核心价值不在于“快”这一件事而是可复现、可审计、可版本化。同一套脚本跑十次和跑一次输出结果完全一致换Logo、换尺寸、换命名规则改几个参数重新跑一遍就行。1.3 技术方案定型我选了Python作为主语言核心依赖是qrcode、Pillow、openpyxl、requests。选这套组合的理由很朴素qrcode负责二维码生成本身是成熟稳定的老牌库对纠错率支持完整Pillow处理图片的打开、缩放、抠图、粘贴Logo合并这种活它是最顺手的openpyxl负责读Excel清单运营同学填表就能驱动整个任务不用碰代码requests用来调微信接口获取access_token和二维码ticket。有人会问为什么不直接在后台批量下载或者用现成的第三方在线工具挨个合Logo不是不行但第三方工具通常限制分辨率、有数量限制、还不能自定义文件名更没法批量接接口。自建脚本一旦跑通后续所有二维码相关工作都能复用它这才是值得投入的原因。2. 工具链准备与素材预处理2.1 环境准备与依赖安装环境是Python 3.9以上即可建议直接用虚拟环境避免污染系统Python。安装命令很简单pip install qrcode[pil] Pillow openpyxl requestsqrcode[pil]这个写法会把Pillow一起装进来因为渲染二维码图片需要它。openpyxl用来处理xlsx格式的清单文件requests用来打微信接口。如果机器上已经有这些包可以跳过但要注意版本别太老Pillow 8.0以下对某些PNG模式的兼容性不太好。装完可以快速验证一下import qrcode from PIL import Image print(qrcode.__version__) print(Image.__version__)能正常打印版本号就说明环境没问题。2.2 Logo素材的三个基本要求Logo素材的质量直接影响最终合成效果。我踩过几次坑后总结出三条硬性要求。第一尽量使用透明背景的PNG。白底方块的Logo贴上去之后会直接压掉一片二维码模块而且和二维码的白边融在一起视觉上像打了块补丁非常难看。透明底可以只保留Logo本身的形状让它“浮”在二维码中央。第二Logo解析度要足够。二维码最终输出可能是800像素甚至更大如果Logo原图只有100像素放上去必然糊。建议来源Logo分辨率不低于500x500像素矢量图导出PNG时直接导出大尺寸后面由脚本缩放。第三不建议用内容特别复杂的Logo。二维码中央区域能承载的信息有限如果Logo里全是细密文字、复杂渐变缩小到22%左右的比例之后基本看不清还会干扰扫码识别。简洁的图形Logo效果最好。2.3 设计一份可驱动的Excel清单脚本能不能泛用关键看驱动方式。我把需求写成一个Excel清单运营同学只需要维护这表格脚本读表格执行任务。字段设计如下字段含义示例name公众号名称某某科技qr_image已有二维码图片路径可留空./input/qr/xxx.pngqr_content需要生成的二维码内容二选一https://mp.weixin.qq.com/...need_logo是否合并Logo是/否logo_pathLogo图片路径./input/logo/logo.pngoutput_name输出文件名某某科技_线下海报.png设计逻辑qr_image和qr_content二选一有现成二维码就读取现有图片没有就给内容让它现场生成。need_logo控制是否贴Logo这样同一个任务里有的二维码带Logo、有的不带实现“选择性合并”。output_name支持自定义方便对接前端命名规范。3. 核心实现批量生成、导出与Logo合并3.1 从公众号接口导出带参二维码如果需要的是带参数二维码第一步是拿access_token。调用接口前先准备好公众号的AppID和AppSecret在后台“设置与开发-基本配置”里能看到。获取tokenimport requests APP_ID your_appid APP_SECRET your_secret def get_access_token(): url https://api.weixin.qq.com/cgi-bin/token params { grant_type: client_credential, appid: APP_ID, secret: APP_SECRET, } resp requests.get(url, paramsparams, timeout10).json() if access_token in resp: return resp[access_token] raise Exception(f获取token失败: {resp})拿到token之后用接口创建二维码ticket。永久二维码和临时二维码参数略有差异永久二维码传scene_str或scene_id临时二维码还需要expire_seconds参数最大2592000秒。以永久二维码为例def create_qr_ticket(access_token, scene_str): url https://api.weixin.qq.com/cgi-bin/qrcode/create payload { action_name: QR_LIMIT_STR_SCENE, action_info: { scene: {scene_str: scene_str} } } resp requests.post( url, params{access_token: access_token}, jsonpayload, timeout10 ).json() if ticket in resp: return resp[ticket] raise Exception(f创建二维码失败: {resp})拿到ticket之后二维码图片的下载地址是固定的拼接格式https://mp.weixin.qq.com/cgi-bin/showqrcode?ticketurl_encode(ticket)。注意ticket必须URL编码之后再拼进去否则特殊字符会导致下载失败。下载图片直接用requests GET即可返回的内容就是图片二进制。整套接口链路其实不复杂真正要小心的是access_token的有效期是7200秒而且每天调用次数有限制。高频率场景建议把token做本地缓存过期再刷新否则脚本跑到一半token失效后面的二维码全部创建失败。3.2 二维码生成的基本参数控制没有现成图片、需要脚本现场生成二维码时qrcode库的几个参数必须理解透。我一般这样初始化import qrcode from qrcode.constants import ERROR_CORRECT_H qr qrcode.QRCode( versionNone, error_correctionERROR_CORRECT_H, box_size10, border4, ) qr.add_data(https://mp.weixin.qq.com/s/xxxxx) qr.make(fitTrue) img qr.make_image(fill_colorblack, back_colorwhite).convert(RGB)这里最关键的是error_correction也就是纠错等级。二维码规范里一共四个等级L级约能修复7%的损毁M级约15%Q级约25%H级约30%。我们要把Logo贴在中央为什么扫码还能成功靠的就是纠错机制。Logo遮住的那部分内容扫描器可以根据周围冗余信息把它还原出来。所以要想在二维码中央放Logo纠错率直接选最高的H级这是不需要犹豫的选项。box_size控制每个模块就是二维码里最小的黑白方块占多少像素。box_size10时一个25x25模块的二维码原始尺寸大概就是250x250像素。border是四周留白宽度按模块数计算默认是4不建议小于2扫描器需要这部分安全边距。3.3 合并Logo的实现细节Logo合并看起来就是“图片贴上去”但有两个细节不到位会出现各种怪问题。第一个是尺寸比例。Logo覆盖面积太大直接把二维码的有效信息全挡住哪怕H级纠错也救不回来。我实测下来的安全比例是Logo边长控制在整个二维码边长的20%到30%之间我自己默认取22%。比如二维码最终是1000x1000像素Logo粘贴尺寸就是220x220像素。小于20%会显得Logo太小识别感不强大于30%扫码失败的案例会明显变多。第二个是透明图层粘贴的正确姿势。Pillow里直接粘贴带透明度的Logo时常见的报错是“cannot paste without mask or is not RGBA”。原因在于背景图是RGB模式而Logo是RGBA模式粘贴时缺少透明的mask信息。正确做法是把粘贴操作写成from PIL import Image qr_img Image.open(qr_output.png).convert(RGB) logo Image.open(logo.png).convert(RGBA) # 计算缩放宽高 logo_ratio 0.22 logo_size int(qr_img.size[0] * logo_ratio) logo.thumbnail((logo_size, logo_size), Image.LANCZOS) # 计算居中位置 pos ( (qr_img.size[0] - logo.size[0]) // 2, (qr_img.size[1] - logo.size[1]) // 2, ) # 第三个参数传入logo自身作为mask透明部分就不会覆盖底图 qr_img.paste(logo, pos, logo) qr_img.save(output.png, PNG)这段代码里thumbnail用的是PIL.LANCZOS重采样缩放后的边缘质量比默认算法好尤其是带细线条的Logo差别明显。paste的第三个参数传入logo本身等于告诉Pillow“用这张图的alpha通道作为蒙版”透明像素的位置实际什么都没贴底图完整保留。3.4 选择性合并的控制逻辑“选择性”三个字听起来花哨落地其实就是一行if判断。我在循环里读Excel的need_logo字段这个字段为是才执行Logo合并否则直接输出原始二维码。另一个非常实用的控制项是不同公众号用不同的Logo品牌矩阵里主号和子号的Logo不完全一样那就给每个公众号单独配置logo_path脚本每次循环都读取当行的路径而不是全局写死一个。再进一步还可以把Logo放在二维码的不同位置。默认居中但有的设计稿要求放在右下角或左下角这时候只需要改pos的坐标计算逻辑把居中改成基于边长偏移的计算。这样脚本就不是一次性方案而是一个可配置的小工具后续各种需求都能复用。4. 实操过程与完整脚本串联4.1 从Excel清单到批量产出的完整流程把前面几块拼起来就是一个能直接跑的脚本。核心循环逻辑是这样import os from pathlib import Path from openpyxl import load_workbook import qrcode from qrcode.constants import ERROR_CORRECT_H from PIL import Image INPUT_EXCEL tasks.xlsx OUTPUT_DIR Path(output) OUTPUT_DIR.mkdir(exist_okTrue) wb load_workbook(INPUT_EXCEL, data_onlyTrue) ws wb.active for row in ws.iter_rows(min_row2, values_onlyTrue): name, qr_image, qr_content, need_logo, logo_path, output_name row[:6] if not name: continue if qr_image and Path(qr_image).exists(): qr_img Image.open(qr_image).convert(RGB) else: qr qrcode.QRCode( versionNone, error_correctionERROR_CORRECT_H, box_size10, border4, ) qr.add_data(qr_content or ) qr.make(fitTrue) qr_img qr.make_image(fill_colorblack, back_colorwhite).convert(RGB) if str(need_logo).strip() 是 and logo_path and Path(logo_path).exists(): logo Image.open(logo_path).convert(RGBA) logo_size int(qr_img.size[0] * 0.22) logo.thumbnail((logo_size, logo_size), Image.LANCZOS) pos ( (qr_img.size[0] - logo.size[0]) // 2, (qr_img.size[1] - logo.size[1]) // 2, ) qr_img.paste(logo, pos, logo) save_name output_name if output_name else f{name}_二维码.png qr_img.save(OUTPUT_DIR / save_name, PNG) print(f已生成: {save_name})这段脚本读取Excel的每一行对每个二维码执行“读取或生成→选择性贴Logo→统一保存”。跑一遍下来output文件夹里就是一批命名统一、Logo位置一致的成品图。整个过程不需要人工介入几十个二维码几分钟处理完。我在实际项目里还会加两个防御性逻辑一是输出前检查目标文件是否已存在防止覆盖旧版本二是给输出名加上时间戳后缀为后续归档留余地。4.2 参数计算示例用真实参数来演示一遍计算过程。假设二维码生成时box_size8内容是一个纯链接version自动计算出来是5也就是37x37个模块。加上border4单边实际是37845个模块乘上box_size8原始图片边长就是360像素。如果Logo比例取22%Logo粘贴宽度就是360×0.22≈79像素。这个尺寸在屏幕上看有点小但二维码一般会印刷成大尺寸物料原图360像素并不够交付。所以实际操作里我通常先生成box_size12或15的大图比如box_size12时同样的码边长变成45×12540像素贴的Logo就是119像素比例还是22%视觉重量不变清晰度却高很多。值得多说一句的是比例是相对值不是绝对值。不管二维码放大到几百还是几千像素Logo占比保持在22%左右识别效果都稳定。所以参数计算的核心就是记住这个相对比例而不是死记某个绝对像素值。4.3 归档命名与目录规范批量产物如果不规划命名生成完又是一堆乱码。我的习惯是输出目录里再按日期建子目录文件名用“公众号名称_用途_日期”这种结构。例如output/ 20260110/ 某某科技_线下海报_20260110.png 某某教育_朋友圈二维码_20260110.png 某某电商_公众号挂卡_20260110.png这个结构的好处是可以按批次回溯哪天投放了哪批物料、用的哪个版本的Logo一眼就能查清楚。配合脚本每次运行先创建日期文件夹基本杜绝了版本混乱问题。5. 高频问题与排查心得5.1 合完Logo之后扫码失败这个是最常见也是最让人抓狂的问题。排查方向按优先级来先看Logo占比超过30%基本必挂先缩到22%左右试一次再看纠错等级如果直接把qrcode默认的M级拿去用贴Logo之后冗余不够也容易挂最后看有没有把二维码周围识别区压住尤其是粘贴时如果做了偏移、Logo边缘碰到三个角的定位框那几乎是灾难。定位框是二维码左上、右上、左下三个角上的“回”字形大方块扫描器依赖它来确定方向和坐标系。Logo居中不会碰到定位框但如果你手动改了位置比如把Logo挪到右下角且尺寸偏大就很容易蹭到底部的定位框这种码基本废了。5.2 Pillow报“cannot paste without mask”前面已经提到RGB背景图上粘贴RGBA的Logo时必须传蒙版参数。很多人会顺手把背景图也转成RGBA这样也能绕过报错但因为二维码本身不需要透明度转RGBA反而徒增文件体积。正确做法仍然是用logo自身做mask。5.3 中文文件名乱码或保存失败代码里直接写死中文输出名在Windows电脑上有时会变成一堆乱码。主要原因是最早在读Excel时字段类型不对openpyxl本身能正确处理中文但如果你用记事本改了CSV文件再去读编码就会翻车。建议Excel统一用xlsx格式取值时做一次strip和str转换。另外文件名里不要带/ \ : * ? |这些字符Windows不允许它们出现在文件名中脚本会直接报错。5.4 接口报40001或4200140001是access_token无效或过期42001是access_token过期。这两种情况本质都一样token缓存失效了。排查时先看看服务器时间是否准确微信接口对时间偏移敏感再看是不是多个环境共用同一对AppID导致token互相覆盖。最稳妥的方案是写一个带本地文件缓存的token获取函数过期才重新请求。5.5 Logo有白底贴上去像狗皮膏药这个属于素材规范问题。很多运营同学顺手发来一个白底的JPG Logo贴上去之后二维码中央出现一个白色矩形视觉上非常突兀。我的建议是脚本里加一道检测如果Logo不是RGBA模式就主动弹个警告。更理想的方案是在素材阶段就要求必须给透明底PNG。5.6 高频问题速查表现象主要原因解决办法合成后扫不出Logo占比过大或纠错等级低Logo边长控制在22%左右纠错用H级Pillow粘贴报错背景RGB与Logo RGBA不匹配paste第三参数传logo本身作为mask中文输出名乱码源文件编码有问题统一xlsx格式str转换strip接口返回40001token过期/时间偏移本地缓存token检查服务器时间输出图模糊Logo原图分辨率低Logo源文件不低于500x500像素Logo周围白块素材非透明背景换透明底PNG素材关于这套方案最后说点我自己的经验脚本跑通之后后期改需求才是真正的考验。我接过最频繁的改动就是换Logo、调尺寸、改命名规则。之所以强调“可配置”就是因为这些改动如果藏死在代码里每次都要找人来改但如果你一开始就把Logo路径、尺寸比例、输出命名抽成配置项运营同学自己改表格就能解决技术这边只需要偶尔看看日志。另一个比较深的体会是做这种自动化不是为了炫技而是为了把重复劳动从人身上移走。人的精力应该花在投放策略、物料创意、数据复盘上而不是在后台点几百次下载按钮。如果你也经常被这种琐碎任务缠身不妨花一个下午把流程拆一遍写一个像上面这样的小脚本。之后的每一次二维码需求都会变成几分钟的例行公事。
返回列表