做运营或电商活动常常要出大量相似海报,比如每款商品一张促销图,或者给不同门店生成专属宣传底图。如果靠人工在软件里重复改字、换图、导出,一天撑死做几十张还累得不行。用程序自动跑才是正解,核心思路是准备好一张背景模板,再把变动的文字、二维码通过代码绘上去,最后循环保存。

一、环境准备与底图模板设计
要实现批量出图,第一步是把环境搭好。Python这边我们主要用PIL(也就是Pillow库)来做图像绘制,再用openpyxl读Excel里的文案数据。安装很简单,用pip装两个包就行。底图模板建议用Photoshop或Figma先做一张空白背景,把固定logo、装饰线条、边框都放好,只留几个文字区和二维码区不填,导出为png,这样代码只需要往指定坐标写内容。
很多新手容易犯的错是把所有元素都用代码画,结果调坐标调到崩溃。实际上固定的视觉部分交给设计软件,代码只处理变量,效率最高。底图尺寸也要统一,比如都做1080乘1920像素,避免批量时尺寸参差。下面这段代码演示怎么打开底图并确认尺寸:
from PIL import Image
# 打开做好的背景模板
base = Image.open('poster_base.png')
print('底图尺寸:', base.size) # 输出类似 (1080, 1920)
除了底图,字体文件也要准备好。Windows可以到系统字体目录拷一个ttf,比如微软雅黑。代码里用ImageFont加载,后面写字全靠它。如果海报要放二维码,可以提前用任意二维码工具生成好图片,命名规则和Excel行对应,代码里直接粘贴即可。
二、用PIL绘制文字与图片的核心代码
PIL里负责画东西的是ImageDraw模块。先拿base图创建一个draw对象,然后调用text方法往指定坐标写文案。坐标是以左上角为原点的坐标体系,写之前最好在设计软件里量好位置。下面示例展示怎么写主标题和副标题,并贴一张二维码:
from PIL import Image, ImageDraw, ImageFont
base = Image.open('poster_base.png').convert('RGBA')
draw = ImageDraw.Draw(base)
title_font = ImageFont.truetype('msyh.ttf', 80)
sub_font = ImageFont.truetype('msyh.ttf', 40)
# 写主标题,位置(120, 300)
draw.text((120, 300), '夏季大促五折起', font=title_font, fill='white')
# 写副标题
draw.text((120, 420), '限时三天 错过等一年', font=sub_font, fill='#FFD700')
# 贴二维码
qr = Image.open('qr_001.png').convert('RGBA')
base.paste(qr, (400, 1400), qr)
base.save('out_poster_001.png')
这里有个关键点:fill参数填颜色,可以用英文色名也能用十六进制字符串。paste贴图时第三个参数是掩码,让png的透明区域正确融合。如果文字太长超出底图,PIL不会自动换行,需要自己写函数按字符宽度截断或缩放字号。
为了适配不同长度文案,可以量一下文字像素宽,超了就减小字体。下面是个简单的自适应函数思路:先用font.getsize量宽,若大于最大宽就换小一号字体再量,直到合适。这样哪怕Excel里有人填了三十字长句,也不会撑破版面。比起人工每张去调,脚本一遍过省事太多。
三、读取Excel并循环批量导出
单张会做了,批量就是套循环。用openpyxl读xlsx,每一行代表一张海报的数据,比如A列标题、B列副标、C列二维码文件名。遍历行,每次重新打开底图(避免上一张残留),画完save成不同名字。这样一百张也就是百次循环的事,电脑跑下来一两分钟。
from openpyxl import load_workbook
from PIL import Image, ImageDraw, ImageFont
wb = load_workbook('data.xlsx')
ws = wb.active
title_font = ImageFont.truetype('msyh.ttf', 80)
for i, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=1):
title, sub, qr_name = row[0], row[1], row[2]
base = Image.open('poster_base.png').convert('RGBA')
draw = ImageDraw.Draw(base)
draw.text((120, 300), str(title), font=title_font, fill='white')
draw.text((120, 420), str(sub), font=ImageFont.truetype('msyh.ttf', 40), fill='#FFD700')
qr = Image.open(qr_name).convert('RGBA')
base.paste(qr, (400, 1400), qr)
base.save(f'poster_{i:03d}.png')
循环里一定要把base重开,不然draw会在同一张图累加内容。文件名用格式化补零,方便后期按顺序找。如果数据量上了万级,可以加个多进程,把行切片丢给不同CPU核同时跑,速度还能翻几倍。
实际落地时,建议先拿十行数据试跑,检查出图效果再全量。也有人把文案直接放csv,用内置csv模块读,逻辑一样。只要模板和坐标固定,一天几百张海报真不是人力问题,而是有没有把重复动作代码化。把这套逻辑封装成函数,下次换活动只改底图和Excel,十分钟搞定千图任务。