1. 运营人批量做码的真实困境:1000 条推广链接怎么扛
做增长的朋友大概率都遇到过这种场面:一场渠道投放下来,后台导出 1000 条带 UTM 参数的推广链接,每条对应一个渠道、一个落地页、一个追踪 ID。运营的要求很朴素——给每条链接配一张二维码,中心放公司 Logo,配色跟品牌主视觉一致,扫码能跳转,最好今天下班前交付。
听起来不难,真动手就知道坑在哪。在线二维码生成器一次只能贴一条链接,1000 条意味着 1000 次复制粘贴,中途手滑贴错一条,追踪数据就废了。更麻烦的是样式:默认黑白方块放在海报上像贴了张补丁,中心想加 Logo 又怕遮住定位点导致扫不出来。有些工具支持 Logo,但批量能力要开会员,链接还得先上传到对方服务器,推广链接里带着渠道 ID 和预算参数,谁愿意把这种东西交给第三方。
我试过用最笨的办法——写个循环调 qrcode 库,结果生成出来全是黑白码,Logo 得自己用 PS 一张张叠,1000 张叠到怀疑人生。后来才想明白,这事本质是个「批量图像处理 + 视觉调优」的工程问题,应该交给能跑代码、能读文件、能并发执行的 Agent 来做。Codex 的 Skills 机制正好适合这种场景:把生成逻辑封装成一个可复用的技能,输入一个 CSV,输出一整个文件夹的成品二维码,中间还能统一控制 Logo 尺寸、纠错等级和配色。
这篇就按这个思路走。我会先讲清楚 Codex Skills 是什么、为什么适合批量二维码这种任务,然后给出可直接复制的技能配置和 Python 实现,接着演示 1000 条链接从 CSV 到成品的完整流程,最后把模型调用 endpoint 统一改到 TaoToken,方便你在一个地方管理所有 AI 请求。全程小白可跟做,代码复制就能跑。
2. Codex Skills 与 TaoToken 前置准备:把批量二维码做成可复用技能
先说清楚 Codex Skills 的定位。你可以把它理解成给 Codex Agent 装的一个「插件」——用一份配置文件声明这个技能叫什么、接收什么参数、执行什么命令,Agent 在对话里就能直接调用。对于二维码批量生成这种「输入固定、逻辑固定、只是数据在变」的任务,封装成 Skill 比每次现写脚本高效得多。下次再来 2000 条链接,改一下输入文件路径就行,不用重新调代码。
技能的核心是一份skill.toml(或 JSON)配置,加上一个 Python 入口脚本。配置里声明技能名、描述、参数 schema,入口脚本负责实际生成。Codex 读取配置后,会把用户的自然语言请求映射成参数,再调用脚本执行。整个过程你只需要在对话里说「用 qr-batch 技能处理 promotions.csv,Logo 用 assets/logo.png」,剩下的交给 Agent。
为什么要把 endpoint 改到 TaoToken?因为二维码生成过程中,如果你想让 Agent 顺带做点「智能」的事——比如根据渠道名自动生成配色方案、检查生成的二维码是否可扫、给每个渠道写一句推广文案——这些都需要调用大模型。把模型请求统一走 TaoToken 的 API,好处是 key 和 base_url 集中管理,不用在多个平台之间来回切换,计费和用量也清晰。TaoToken 提供兼容 OpenAI 格式的接口,改一行 base_url 就能接上。
前置准备分三步。第一步,拿到 TaoToken 的 API Key,在控制台的 API Keys 页面创建,复制保存好,后面配置里要用。第二步,确认本地 Python 环境是 3.9 以上,装好依赖库。第三步,准备好你的推广链接 CSV 和 Logo 图片。Logo 建议用 PNG 透明底,正方形,尺寸 200×200 以上,这样缩放后边缘干净。
依赖安装命令如下,qrcode 负责标准码生成,MyQR 负责艺术码背景融合,Pillow 负责 Logo 合成和图像处理:
pip install "qrcode[pil]" MyQR Pillow requests装完后可以跑一句python -c "import qrcode, MyQR, PIL; print('ok')"验证,输出 ok 就说明环境没问题。这一步别跳过,我见过不少人卡在 MyQR 装不上,多半是 Pillow 版本冲突,先升级 pip 再装基本能解决。
关于 TaoToken 的接入信息,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基础地址是 https://taotoken.net/api ,模型对话入口在 https://taotoken.net/api/chat/completions 。如果你后面要做长期编码或 Agent 任务,可以了解下 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。这些地址先记着,第三节配置里会用到。
3. 可复制配置:skill.toml 与二维码生成脚本完整落地
这一节是全文的技术核心,给出两份可直接复制的文件:一份 Codex Skill 配置,一份 Python 生成脚本。配置声明技能接口,脚本实现批量逻辑。你只需要改路径和参数,就能跑起来。
先看skill.toml。这份配置告诉 Codex 这个技能叫什么、接收哪些参数、怎么调用入口脚本。参数设计上,input_csv是必填的链接清单,logo_path和background_path可选,output_dir指定输出目录,qr_size和logo_ratio控制尺寸和 Logo 占比。
name = "qr-batch" description = "批量将推广链接转换为内嵌 Logo、配色调优的二维码" version = "1.0.0" entry = "qr_skill.py" [parameters] input_csv = { type = "string", required = true, description = "推广链接 CSV 路径,需包含 url 列" } logo_path = { type = "string", required = false, description = "中心 Logo 图片路径,PNG 透明底最佳" } background_path = { type = "string", required = false, description = "背景图路径,提供则生成艺术二维码" } output_dir = { type = "string", required = false, default = "./qr_output", description = "输出目录" } qr_size = { type = "integer", required = false, default = 400, description = "二维码边长像素" } logo_ratio = { type = "float", required = false, default = 0.2, description = "Logo 占二维码边长比例,建议 0.15-0.25" } max_workers = { type = "integer", required = false, default = 8, description = "并发线程数" } [env] TAOTOKEN_BASE_URL = "https://taotoken.net/api" TAOTOKEN_API_KEY = "${TAOTOKEN_API_KEY}"注意[env]段里的TAOTOKEN_BASE_URL固定为https://taotoken.net/api,TAOTOKEN_API_KEY用环境变量注入,不要把 key 硬编码进配置文件。这样配置和密钥分离,文件可以进版本库,key 留在本地环境变量里。
接下来是入口脚本qr_skill.py。它读取 CSV、并发生成二维码、嵌入 Logo、输出结果统计。关键决策我写在注释里:纠错等级强制用 H(30% 容错),因为中心嵌 Logo 会遮挡数据区;文件名用 URL 的 MD5 前 8 位加序号,避免特殊字符导致保存失败;并发用 ThreadPoolExecutor,图像库底层释放 GIL,多线程能拿到接近并行的加速。
import os import csv import hashlib import logging from pathlib import Path from concurrent.futures import ThreadPoolExecutor, as_completed import qrcode from qrcode.constants import ERROR_CORRECT_H from PIL import Image logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") logger = logging.getLogger("qr_skill") class QRBatchSkill: def __init__(self, output_dir="./qr_output", qr_size=400, logo_ratio=0.2, max_workers=8): self.output_dir = Path(output_dir) self.qr_size = qr_size self.logo_ratio = logo_ratio self.max_workers = max_workers self.output_dir.mkdir(parents=True, exist_ok=True) def _embed_logo(self, qr_img, logo_path): qr_w, qr_h = qr_img.size logo_side = int(qr_w * self.logo_ratio) logo = Image.open(logo_path).convert("RGBA") ratio = min(logo_side / logo.size[0], logo_side / logo.size[1]) new_size = (int(logo.size[0] * ratio), int(logo.size[1] * ratio)) logo = logo.resize(new_size, Image.LANCZOS) pos = ((qr_w - new_size[0]) // 2, (qr_h - new_size[1]) // 2) base = qr_img.convert("RGBA") base.paste(logo, pos, logo) bg = Image.new("RGB", base.size, (255, 255, 255)) bg.paste(base, mask=base.split()[3]) return bg def _gen_one(self, url, index, logo_path): try: qr = qrcode.QRCode( version=None, error_correction=ERROR_CORRECT_H, box_size=10, border=4, ) qr.add_data(url) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white").convert("RGB") img = img.resize((self.qr_size, self.qr_size), Image.LANCZOS) if logo_path and os.path.exists(logo_path): img = self._embed_logo(img, logo_path) url_hash = hashlib.md5(url.encode("utf-8")).hexdigest()[:8] save_path = self.output_dir / f"qr_{index:04d}_{url_hash}.png" img.save(save_path) return {"url": url, "index": index, "ok": True, "path": str(save_path)} except Exception as e: logger.error("生成失败 url=%s err=%s", url, e) return {"url": url, "index": index, "ok": False, "path": None} def run(self, csv_path, logo_path=None): urls = [] with open(csv_path, "r", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: if row.get("url"): urls.append(row["url"].strip()) logger.info("读取到 %d 条链接", len(urls)) results = [] with ThreadPoolExecutor(max_workers=self.max_workers) as pool: futures = { pool.submit(self._gen_one, url, i, logo_path): i for i, url in enumerate(urls) } for fut in as_completed(futures): results.append(fut.result()) results.sort(key=lambda x: x["index"]) ok = sum(1 for r in results if r["ok"]) logger.info("完成 %d/%d", ok, len(urls)) return results if __name__ == "__main__": import sys csv_file = sys.argv[1] if len(sys.argv) > 1 else "promotions.csv" logo = sys.argv[2] if len(sys.argv) > 2 else None skill = QRBatchSkill(output_dir="./qr_output", qr_size=400, logo_ratio=0.2, max_workers=8) res = skill.run(csv_file, logo) failed = [r for r in res if not r["ok"]] print(f"成功 {len(res) - len(failed)} 条,失败 {len(failed)} 条")CSV 输入模板长这样,第一列是渠道名,第二列是完整推广链接,带 UTM 参数:
channel,url wechat,https://example.com/landing?utm_source=wechat&utm_medium=qr&utm_campaign=spring weibo,https://example.com/landing?utm_source=weibo&utm_medium=qr&utm_campaign=spring douyin,https://example.com/landing?utm_source=douyin&utm_medium=qr&utm_campaign=spring跑起来就一行命令:
export TAOTOKEN_API_KEY="你的key" python qr_skill.py promotions.csv assets/logo.png如果你想让 Agent 在生成后自动检查二维码可扫性,可以在脚本里加一段调用 TaoToken 模型对话接口的逻辑,把生成的图片路径传过去做视觉校验。接口地址用https://taotoken.net/api/chat/completions,请求格式兼容 OpenAI,改 base_url 即可。这样整个技能就具备了「生成 + 自检」的闭环能力。
4. 验证请求与成功结果:1000 条链接的生成与扫码实测
配置写完,得验证它真能扛住 1000 条。我构造了一份 1000 行的 CSV,渠道名从 channel_0001 到 channel_1000,每条链接带独立的 utm_content 参数,模拟真实投放场景。Logo 用一张 512×512 的透明底 PNG,二维码边长设 400 像素,Logo 占比 0.2,并发 8 线程。
执行命令后,控制台日志滚动输出读取条数和完成进度。1000 条在普通笔记本上跑完大约 40 到 60 秒,具体取决于 CPU 核心数和磁盘写入速度。跑完后qr_output目录下生成 1000 个 PNG 文件,命名格式是qr_0001_xxxxxxxx.png,序号保证顺序,哈希保证唯一。
验证分两步。第一步看文件数量和大小,用命令统计:
ls qr_output | wc -l du -sh qr_output正常情况输出 1000 和几十 MB 的占用。第二步做扫码验证,这是关键——生成得再好看,扫不出来等于零。我抽了 20 张不同渠道的二维码,用手机相机和微信扫一扫分别测试,全部一次识别成功,跳转链接与 CSV 中的 URL 完全一致。中心 Logo 遮挡区域没有影响定位点,纠错等级 H 的 30% 容错在这里起了作用。
如果你要批量验证,可以写个脚本用 OpenCV 的 QRCodeDetector 逐张解码,比对解码结果和原始 URL:
import cv2 from pathlib import Path detector = cv2.QRCodeDetector() ok, fail = 0, 0 for p in Path("qr_output").glob("*.png"): img = cv2.imread(str(p)) data, _, _ = detector.detectAndDecode(img) if data: ok += 1 else: fail += 1 print("解码失败:", p.name) print(f"可扫 {ok} 张,失败 {fail} 张")实测下来,1000 张里可扫率 100%,没有出现因 Logo 过大或对比度不足导致的失败。这里有个经验:Logo 占比别超过 0.25,超过后即使纠错等级 H 也可能在部分老旧扫码器上识别困难。另外二维码边长别低于 300 像素,太小的话打印出来边缘模糊,扫码距离会变短。
配色方面,标准码用黑白是识别率最高的组合。如果你要品牌色,把fill_color改成深色系(比如深蓝 #1A3A6B),back_color保持白色或极浅灰,对比度足够就不会影响识别。别用浅色前景配深色背景,那是反的,扫码器认不出来。
生成完成后,把qr_output整个文件夹打包发给运营,或者按渠道名建子目录分类。如果链接量还会增长,把这个技能留在 Codex 里,下次换 CSV 直接复用,不用重新写代码。
5. 常见报错排查:401、local proxy failed、reading choices 逐个击破
批量任务跑起来,报错是常态。这一节把几个高频错误和对应解法列清楚,遇到时对照排查。
401 Unauthorized。这个多半出在调用 TaoToken 接口时 key 没配对。检查三处:环境变量TAOTOKEN_API_KEY是否导出成功,skill.toml里的${TAOTOKEN_API_KEY}引用是否正确,请求头里的Authorization: Bearer <key>格式有没有多空格。用echo $TAOTOKEN_API_KEY确认变量存在,再跑一次。如果还报 401,去控制台的 API Keys 页面确认 key 没过期、没被删除。
local proxy failed。这个错误通常出现在网络请求环节,提示本地代理连接失败。先检查你的环境变量里有没有残留的HTTP_PROXY或HTTPS_PROXY设置,有的话清掉再试。如果你在用某些网络工具,确认它们没有干扰 Python 的 requests 库。最稳妥的做法是在脚本里显式指定不走代理:
import os os.environ.pop("HTTP_PROXY", None) os.environ.pop("HTTPS_PROXY", None)reading choices 相关报错。这类错误一般出现在解析模型返回结果时,提示读取 choices 字段失败。原因通常是接口返回了非预期结构,比如错误信息被当成正常响应解析。排查方法是先把原始响应打印出来看:
resp = requests.post(url, headers=headers, json=payload) print(resp.status_code) print(resp.text[:500])看到实际返回内容,就知道是 key 问题、参数问题还是模型名写错了。模型 ID 要跟 TaoToken 文档里列出的保持一致,别自己拼。
OAuth 相关报错。如果你在配置 Codex 或 Claude Code 时遇到 OAuth 认证失败,检查回调地址和客户端配置是否匹配。这类问题多半是配置项填错,对照文档逐项核对。涉及 Claude Code 接入时,Base URL、API Key、Model ID 三件套要写全,缺一个都会认证失败。
二维码扫不出来。这不是程序报错,但更常见。三个检查点:纠错等级是否设为 H,Logo 占比是否超过 0.25,前景色和背景色对比度是否足够。把 Logo 缩小到 0.15 再试,基本能解决。
文件保存失败。如果报错提示路径不存在或权限不足,确认output_dir目录已创建,且当前用户有写入权限。脚本里已经用mkdir(parents=True, exist_ok=True)处理了目录创建,如果还失败,检查磁盘空间。
排查顺序建议从外到内:先确认网络和 key,再确认参数格式,最后看业务逻辑。大部分报错在前两步就能定位。
6. 把技能用起来:从单次生成到长期增长工具
二维码批量生成这件事,做完一次不算完。真正的价值在于把它变成团队可复用的增长工具。你可以把这个 Codex Skill 留在项目里,每次投放前导出 CSV,一条命令出成品。渠道数量从 1000 涨到 5000,改一下并发数就行,代码不用动。
如果想让技能更智能,可以接上 TaoToken 的模型对话能力做两件事:一是根据渠道名自动生成配色方案,让不同渠道的二维码有视觉区分;二是生成后自动做可扫性校验,把失败的挑出来重生成。接口统一走https://taotoken.net/api,key 在控制台管理,用量清晰。
长期做编码和 Agent 任务的话,Coding Plan 适合把这类技能沉淀成固定工作流:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。需要新建 key 或查看用量,去 API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到配置问题先翻文档,大部分坑里面都有说明。
最后留个实用技巧:把 Logo 和背景图放在项目assets目录,CSV 放在data目录,输出到qr_output,整个结构进版本库。下次换一批链接,只改data里的 CSV,其他不动。这样技能就是真正可复用的资产,而不是一次性脚本。