每逢腊月,我的手机就开始被两类消息刷屏:一类是抢票加速包的“好友助力”,另一类是复制粘贴式的群发祝福。前几年我也随大流,直到有一次盯着12306的刷新按钮盯到凌晨,大拇指都快抽筋,才意识到这种重复劳动本不应该由人来干。于是去年春节,我用Python写了三个自动化脚本,把抢票监控、批量祝福、定时提醒这三件事全部交给机器,那几天是我过得最轻松的一个年。
这篇文章就是那次实践的完整记录。不讲虚的,直接说清楚:为什么选Python、环境怎么搭、三个脚本分别怎么写、跑起来之后踩了哪些坑,以及哪些自动化能做、哪些不该做。如果你也想在春节前搞定这套“数字年味”基建,可以参考这份踩过坑的实战笔记。
1. 为什么春节自动化是刚需:三个脚本解决的痛点
1.1 春节场景的三个高频痛点拆解
春节的“忙”,跟日常工作的忙完全不是一回事。日常忙是任务多、节奏快,春节忙是纯体力消耗叠加情绪消耗。
先说抢票。12306预售期一出,全家人的行程就像一盘散沙落在不同日期、不同车次上。手动刷票时,你得反复切换查询条件,盯着余票数字从“有”变成“无”,再等它从“无”变回“有”。如果是热门线路,放票后几分钟内连候补都排不上。这里最折磨人的不是操作本身,而是不确定性——你不知道什么时候会放票,只能一遍遍刷新,把整个人的精力都耗在了一个刷新按钮上。
再说群发祝福。我每年通讯录里要点对点发祝福的人有三百多个,包括同事、客户、老同学、亲戚。用微信自带群发助手只能选200人上限,而且不能针对不同人换称呼。手动逐条发,从晚上八点发到凌晨,经常发到后半段开始复制粘贴错乱,把给张总的祝福发给了李姐,场面一度非常尴尬。
第三件是提醒类的小事,比如抢红包雨时间、年会抽奖投票、给长辈拜年的准确时间节点。这些事单看不难,但混在年夜饭、看春晚、打牌这些事里,很容易忘。人一旦进入过节状态,对“定时”这件事的敏感度会断崖式下降。
这三件事有一个共同特征:规则明确、操作重复、容错率低。这正是自动化脚本最擅长处理的场景。
1.2 为什么选Python而不是其他工具
市面上做自动化的工具有很多:按键精灵、AutoHotkey、iMacros、甚至Excel里的VBA。为什么最终选了Python?我的理由有三条。
第一,生态覆盖面广。抢票监控需要HTTP请求和JSON解析,批量祝福需要模拟键鼠操作或调用接口,定时任务需要系统调度,这三个需求在Python里都有非常成熟的库:requests、json、pyautogui、schedule、logging。不需要在不同工具之间来回跳,一个语言全搞定。
第二,排查问题成本低。自动化脚本最大的坑是“不知道哪里错了”。Python的异常堆栈可读性极高,配合requests的响应状态码和pyautogui的截图定位,问题基本能在几分钟内定位。而按键精灵这类工具一旦出错,你看到的只是“脚本停止”,调试体验接近盲人摸象。
第三,可迁移性。今年写完抢票监控,明年改改日期参数就能复用;给A客户群发祝福的逻辑,换个Excel表格就能给B项目发通知。脚本一旦沉淀下来,就是自己的数字化资产。
当然Python也有短板:打包分发不如Go方便,运行效率也不是最高。但春节自动化这种低频、轻量、逻辑简单的场景,Python的打法完全够用。
1.3 这套方案能做什么、不能做什么
先说能做的:
- 定时监控12306余票,一旦目标车次有票立即推送通知到手机;
- 批量生成带称呼的个性化祝福文案,按名单逐条发送;
- 用定时任务在指定时间点自动执行脚本,全程不需要人工盯守;
- 日志记录每次执行结果,出错自动告警。
再说不能做的:
- 不能绕过12306的验证码和风控机制去“抢票”。铁路部门的用户协议明确禁止非官方的自动购票行为,所以我的方案定位是“监控+提醒”,把人工操作的等待成本降到最低,而不是替代人工下单;
- 不能对微信等平台进行大规模、高频的自动化操作。这个我后面会细讲,平台风控不是吃素的,脚本写得再稳也扛不住封号;
- 不能保证100%不出错。网络波动、登录态过期、接口结构调整,任何一个环节都可能挂,所以必须设计异常兜底。
把预期管理做对,工具才能真正提升效率,而不是给你惹麻烦。
2. Python开发环境三件套:安装、虚拟环境与IDE配置
2.1 本机Python安装的坑与版本选择
很多新手第一步就栽在Python安装上。这里把三个主流系统的关键点一次说清。
Windows:去官网下载安装包时,一定要勾选“Add Python to PATH”,否则装完在cmd里敲python会提示找不到命令。装完后打开cmd,输入python --version验证。如果出现“Microsoft Store”的应用商店跳转,说明你用的是系统自带的假入口,需要去“设置 -> 应用 -> 应用执行别名”里把两个python.exe的别名关掉。
macOS:系统自带的Python 2.7早就不维护了,千万别用。建议直接装Homebrew,然后brew install python@3.12。装完注意看终端提示的PATH路径,Homebrew会把Python装到/opt/homebrew/bin,如果which python3指向/usr/bin/python3,说明还在用系统自带版本。
Linux(Ubuntu/Debian系):执行sudo apt update && sudo apt install python3 python3-venv python3-pip。注意不要手动删系统自带的Python,很多系统工具依赖它,删了会导致桌面环境出问题。
版本选择上,我建议用3.10或3.11,不要盲目追最新版。原因很实际:自动化脚本常用的一些库,尤其是涉及GUI模拟和旧接口的,对新版本Python的适配会有滞后。我实测3.12跑pyautogui在部分Windows环境会有权限弹窗兼容问题,而3.11非常稳定。选一个成熟版本,远比你用最新版但被依赖库报错卡住要省心。
2.2 venv虚拟环境:为什么不直接全局安装
如果你想把多个Python项目装在同一个环境里,很快会碰到“依赖地狱”。A项目需要requests==2.28,B项目需要requests==2.31,两个项目共用全局环境,升级一个另一个就挂了。
venv是Python自带的虚拟环境工具,核心作用就是为每个项目创建独立的依赖目录。操作很简单:
# 创建虚拟环境(在项目目录下执行) python -m venv venv # 激活虚拟环境 # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate # 激活后命令行前面会出现(venv)前缀,表示已进入虚拟环境之后用pip install安装的所有包都会进到这个独立环境里,不影响系统全局。我第一次做春节脚本时嫌麻烦,直接在全局环境装了一堆库,结果春节前一周升级了一个依赖库,把另一个脚本搞崩了,大半夜在那排查依赖冲突,血的教训。
项目依赖固定下来后,用requirements.txt管理:
# 导出当前虚拟环境的所有依赖 pip freeze > requirements.txt # 在新机器上一键还原 pip install -r requirements.txt这个文件我会连同脚本一起存到Git仓库里,换电脑、换环境都能快速复原。
2.3 IDE选型与调试技巧
春节写脚本讲究快、稳、好排查,我建议用VS Code加Python插件,轻量且调试体验够用。
关键配置有三块:
第一,Python解释器指向虚拟环境。在VS Code里按Ctrl+Shift+P,输入“Python: Select Interpreter”,选择刚才创建的venv目录下的Python。选错解释器是新手最常见的坑,写了半天代码,运行时报ModuleNotFoundError,其实就是解释器没切到虚拟环境。
第二,开启“Python: Terminal: Activate Env In Current Terminal”,这样每次打开新终端,VS Code会自动激活虚拟环境。
第三,学会用断点调试。在代码行号左侧点一下出现红点,然后按F5启动调试。程序执行到红点处会暂停,你可以逐行查看变量的值。这个功能在排查“接口返回的数据结构和预期不一致”这种问题时,比print大法高效十倍。
基础环境就绪后,接下来进入正题:三个脚本逐个拆解。
3. 12306余票监控脚本:原理、接口分析与时序设计
3.1 抢票自动化的工作流程
一个完整的12306自动购票流程包括:登录、查询余票、提交订单、选择乘客、确认支付。这里面每一环都涉及风控和验证机制。
我见过不少人尝试用Selenium控制浏览器去模拟整个购票流程,但实际风中控的强度远高于预期:验证码识别是专业的图像模型在搞,登录状态异常会触发滑块验证,高频请求会直接封IP。普通脚本作者在这条路上投入产出比极低,而且有合规风险。
所以我采用的方案是“只做查询和提醒,不做下单”。查询余票这个动作对人来说是重复劳动,但对服务器来说只是一个公开的查询接口。这个方案把用户协议里最敏感的“自动化购票”部分绕开了,风险和实现成本都大幅下降。省下来的时间足够你在收到提醒后,用手机在官方App上一分钟完成手动购票。
3.2 余票查询接口实例:以requests模拟查询
12306的余票查询接口是对外开放的,不需要登录也能访问。核心请求是一个GET接口,参数包括出发站、到达站、日期。车次信息里包含各席别余票情况。
先做车站代码表。12306用的不是“北京”这种中文名,而是电报码,比如“北京”对应BJP。车站代码表可以提前抓取一次并存成JSON文件,避免每查一次就重拉全表。
import requests import json # 先请求一次车站代码表(频率不要太高,存本地复用) def fetch_station_map(): url = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js" resp = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}) text = resp.text # 返回格式: var station_names ='@bjb|北京北|BJP|...' data = text.split("'")[1].split("@")[1:] station_map = {} for item in data: parts = item.split("|") # parts[1]是中文名, parts[2]是电报码 station_map[parts[1]] = parts[2] with open("stations.json", "w", encoding="utf-8") as f: json.dump(station_map, f, ensure_ascii=False, indent=2) return station_map然后用车站代码查余票:
import requests import json from datetime import datetime, timedelta def query_left_ticket(from_station, to_station, date_str): # 读车站表 with open("stations.json", "r", encoding="utf-8") as f: station_map = json.load(f) from_code = station_map.get(from_station) to_code = station_map.get(to_station) if not from_code or not to_code: raise ValueError("车站代码不存在,请检查中文名") url = "https://kyfw.12306.cn/otn/leftTicket/query" params = { "leftTicketDTO.train_date": date_str, "leftTicketDTO.from_station": from_code, "leftTicketDTO.to_station": to_code, "purpose_codes": "ADULT" } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://kyfw.12306.cn/otn/leftTicket/init" } resp = requests.get(url, params=params, headers=headers, timeout=10) resp.raise_for_status() data = resp.json() tickets = [] if data.get("data") and data["data"].get("result"): for item in data["data"]["result"]: fields = item.split("|") # 关键字段索引:3=车次, 8=出发时间, 9=到达时间, 13=二等座, 26=一等座, 29=无座 train_no = fields[3] start_time = fields[8] end_time = fields[9] second_class = fields[30] # 各版本字段索引略有差异 first_class = fields[31] no_seat = fields[29] tickets.append({ "车次": train_no, "出发": start_time, "到达": end_time, "二等座": second_class, "一等座": first_class, "无座": no_seat }) return tickets if __name__ == "__main__": # 明天 target_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d") result = query_left_ticket("北京", "上海", target_date) for t in result: if t["二等座"] not in ("无", ""): print(f"{t['车次']} {t['出发']}-{t['到达']} 二等座有票: {t['二等座']}")这里要特别提醒:接口返回的字段索引在不同版本可能有变化。我最初从网上找了一份字段表,直接跑的时候数据错位,把“有票”误判成“无票”,还好没有造成实际损失,只是虚惊一场。所以写完后第一步是打印原始返回的fields数组,对照着确认索引。
3.3 数据解析与信息推送
查到了余票,还要让人及时看到。手机盯屏幕当然可以,但既然做了自动化,就要把通知也一并自动化。我常用的推送方式有三种:
| 推送方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SMTP邮件 | 配置简单、无条件限制 | 可能有延迟、容易进垃圾箱 | 兜底通知 |
| Server酱 | 扫码绑定微信、实时推送 | 需要注册获取SendKey | 个人预警 |
| 钉钉/企业微信群机器人 | 免费、支持多人接收 | 需要建群、配置Webhook | 家庭群通知 |
我推荐Server酱作为主力通知通道,配置非常轻量。注册后在个人后台拿到一个SendKey,然后:
import requests def send_serverchan(title, content, send_key): url = f"https://sctapi.ftqq.com/{send_key}.send" data = {"title": title, "desp": content} resp = requests.post(url, data=data) resp.raise_for_status()这样当脚本轮询到目标车次有余票时,微信会立刻收到推送,标题写“北京到上海 D705 二等座有票”,正文放车次详情和购票链接。收到推送后用手机打开官方App手动购票,全程不到一分钟。
3.4 避开封禁的节奏控制
自动化查询有两个“雷区”:请求频率过高、请求特征过于统一。
请求频率方面,官方接口虽然公开,但频繁访问同样会被限流。我的实测数据是:单次查询请求耗时约200到500毫秒,如果把轮询间隔设在1秒以内,持续几分钟后大概率返回异常响应或直接超时。稳妥的轮询间隔是3到5秒,每查询一次就随机sleep 3到5秒之间的一个值。
import random import time interval = random.uniform(3, 5) time.sleep(interval)请求特征方面,User-Agent不要固定用一个,可以在几个常见浏览器的UA池里随机切换。另外建议用requests.Session()保持连接,模拟浏览器的行为模式:
session = requests.Session() session.headers.update({ "User-Agent": random.choice(UA_LIST), "Referer": "https://kyfw.12306.cn/otn/leftTicket/init" })控制好节奏,脚本连续跑几天不出问题是很正常的。
4. 祝福消息批量发送:从pyautogui模拟操作到模板化文案
4.1 消息自动化的三种实现路线
群发祝福比抢票更微妙,因为涉及社交平台的用户协议。我做这个功能时认真对比过三条技术路线。
路线一:开放平台官方API。企业微信、钉钉、Telegram这类平台有完善的机器人API,合法合规,但问题是:你的客户、同学、亲戚未必在这类平台上。如果只是给企业客户发,这确实是首选。
路线二:非官方第三方库,比如itchat、wechaty这类。它们的原理是通过网页版或HOOK方式接管微信协议。问题在于:网页版微信本身在很多账号上不可用,HOOK方式更容易触发风控。我在两年前测试过,批量发送几十条消息后,账号被限制了朋友圈和部分聊天功能,折腾了一周才解封。为了发个祝福搭上账号,非常不值。
路线三:桌面端GUI自动化,也就是pyautogui模拟鼠标键盘操作。它不介入通信协议,本质上是替你在电脑上“手动操作”。优点是不容易被平台判定为协议异常,缺点是如果界面布局发生变化,脚本需要同步调整。
综合来看,个人场景下路线三最平衡。本文后续就围绕这条路展开。
4.2 模板化祝福文案设计
群发祝福最尴尬的一点是“复制粘贴感”太强。发给所有人的文案都是一模一样,收的人一眼就能看出来。但逐条现写三百条又不现实,所以要用模板加变量的方式做个性化。
我的做法是用Excel维护一个联系人名单,字段包含:姓名、称呼、关系类别、个性化备注。然后准备几个不同风格的模板,根据关系类别自动切换。
称呼:{称呼} 模板(同事):新年快乐!{称呼},感谢这一年的并肩作战。愿新岁平安喜乐,诸事顺遂,咱们节后见! 模板(客户):尊贵的{称呼},感谢过去一年与您同行。值此新春之际,谨祝事业蒸蒸日上,身体健康万事如意! 模板(朋友):{称呼},新春大吉!愿你岁岁常欢愉,万事皆胜意。改天约饭!读取Excel用pandas库,但春节自动化这种轻量场景,只用标准库的csv模块就够了,避免为了一个小功能引入重型依赖。
import csv contacts = [] with open("contacts.csv", encoding="utf-8-sig") as f: reader = csv.DictReader(f) for row in reader: contacts.append(row) # 模板替换 def gen_message(contact): template = contact["template"] msg = template.replace("{称呼}", contact["称呼"]) return msg注意CSV文件要用utf-8-sig编码打开,否则Excel里写的中文在读取时会乱码。这个细节我第一年就踩过,生成的消息批量乱码,差点全发出去了。
4.3 pyautogui实操:打开窗口、定位输入框、发送
pyautogui的核心逻辑就三步:定位目标坐标、模拟点击输入、按回车发送。第一步最关键,也是最容易出问题的。
下面是一套我在Windows上稳定跑通的流程:
import pyautogui import time pyautogui.PAUSE = 1.5 # 每个操作之间停顿1.5秒,给界面留反应时间 pyautogui.FAILSAFE = True # 鼠标快速移到左上角可紧急停止 def send_message_to_contact(contact_name, message): # 1. 打开微信的搜索框(快捷键 Ctrl+F) pyautogui.hotkey("ctrl", "f") time.sleep(1) # 2. 输入联系人姓名 pyautogui.write(contact_name, interval=0.05) time.sleep(1) # 3. 按回车进入聊天窗口 pyautogui.press("enter") time.sleep(1) # 4. 在输入框粘贴消息 pyautogui.write(message, interval=0.02) time.sleep(0.5) # 5. 发送 pyautogui.press("enter") time.sleep(1)这里有两个很重要的参数:PAUSE和FAILSAFE。
PAUSE是每个pyautogui操作之间的默认间隔。如果设成0,脚本执行速度会飞快,但界面渲染跟不上,很容易丢操作。1到1.5秒的间隔虽然看起来“慢”,但胜在稳定。
FAILSAFE是安全机制。一旦开启,如果脚本失控乱飞,你只要把鼠标甩到屏幕左上角,程序就会立刻抛出pyautogui.FailSafeException并停止,避免脚本在不知情的情况下乱点一通。
还有一个细节:pyautogui.write()对中文支持不太好,它在Windows上模拟的是直接按键输入,中文会丢字符。最好的方案是先把消息复制到剪贴板,再模拟Ctrl+V粘贴:
import pyperclip pyperclip.copy(message) pyautogui.hotkey("ctrl", "v") pyautogui.press("enter")pyperclip是一个轻量的剪贴板操作库,配合pyautogui在中文输入场景下非常好用。
4.4 发消息的节奏与风控提醒
GUI自动化虽然比协议级操作安全,但依然要控制节奏。我实测下来,每次发送间隔建议不少于5到8秒,每发20条左右暂停一两分钟。频率太高,微信PC端的检测机制同样会介入。
这里有一个更稳妥的替代思路:与其用微信逐条发文字,不如把祝福生成一张张长图。用PIL库把文案渲染成图片,再通过文件传输助手批量发到手机,由你手动转发到朋友圈或群聊。这样操作量大幅下降,而且“发图”比“逐条发文字”的触发风控概率低很多。
from PIL import Image, ImageDraw, ImageFont def draw_blessing_image(name, message, output_path): img = Image.new("RGB", (800, 500), color=(255, 248, 235)) draw = ImageDraw.Draw(img) # 字体路径根据系统调整,Windows可以用微软雅黑 font_title = ImageFont.truetype("msyh.ttc", 48) font_text = ImageFont.truetype("msyh.ttc", 28) draw.text((60, 60), f"{name},新春快乐!", font=font_title, fill=(180, 40, 40)) draw.text((60, 200), message, font=font_text, fill=(60, 60, 60)) img.save(output_path)把写好的文案逐条渲染成图片,然后手动或半自动发送。这个方案我在去年春节用下来,既保住了仪式感,又避开了高频操作风险。
5. 定时任务调度与异常兜底:让脚本自动运行起来的完整方案
5.1 定时触发:Windows任务计划与Linux cron
脚本写好只是第一步,关键是让它按计划自动跑。很多人第一反应是在代码里写while True加sleep,这在短时间运行可以,但长时间驻留有两个问题:一是电脑休眠脚本就断,二是没有自动重启机制,出错后只能人工救。
正确的做法是交给操作系统级的定时任务管理器。
Windows环境下:按Win+R输入taskschd.msc打开任务计划程序,创建基本任务。触发器选“每天”,时间设为早上8点,操作选“启动程序”。程序填python.exe的完整路径,参数填脚本的完整路径,起始于填脚本所在目录。重点注意几个设置:
- “条件”选项卡里,取消勾选“只有在计算机使用交流电源时才启动此任务”,否则笔记本拔电后任务不会执行;
- “设置”选项卡里,勾选“如果任务失败,按以下频率重新启动”,间隔5分钟,最多重启3次;
- 关键一步:你的Python要激活虚拟环境,但任务计划直接执行
python.exe不会自动进入虚拟环境,所以要么在脚本前加上激活命令,要么直接用虚拟环境里的python.exe路径。
# 任务计划程序的“操作”里可以直接写: C:\path\to\venv\Scripts\python.exe C:\path\to\ticket_monitor.pyLinux/macOS环境下:用crontab配置,一行搞定:
# 每天8点和20点各执行一次余票监控 0 8,20 * * * cd /path/to/project && /usr/bin/python3 ticket_monitor.py >> logs/cron.log 2>&1注意cron执行时的环境变量和终端不一样,尤其是PATH。建议在脚本开头用绝对路径,或者在crontab里显式声明SHELL和PATH。
5.2 日志记录与异常捕获
定时任务跑起来后,你不可能每次都盯着终端看输出。如果没有日志,脚本出错你根本不知道。
我用的方案是Python标准库的logging,同时输出到文件和控制台:
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.FileHandler("script.log", encoding="utf-8"), logging.StreamHandler() ] ) logger = logging.getLogger(__name__)在关键节点打日志,比如“开始查询”“查询结束,发现N个车次有票”“网络请求超时,重试第2次”。这样出问题后翻日志,一眼就能定位是哪一步挂了。
异常捕获要区分“可恢复错误”和“不可恢复错误”。网络超时是可恢复的,重试几次就好;登录态过期是不可恢复的,应该立刻发通知让人工介入,而不是无限重试。
import time MAX_RETRY = 3 def query_with_retry(max_retry=MAX_RETRY): for attempt in range(1, max_retry + 1): try: return query_left_ticket("北京", "上海", date_str) except requests.exceptions.RequestException as e: logger.warning(f"第{attempt}次查询失败: {e}") if attempt == max_retry: send_serverchan("余票监控异常", f"连续{max_retry}次失败,请检查网络或接口", send_key) raise time.sleep(10 * attempt)5.3 进程守护:让脚本崩了自动重启
定时任务适合“短时运行完就退出”的脚本。如果某个脚本需要长时间驻留,比如持续监听某个事件,那就要引入进程守护。
Linux下我用supervisor,配置一个简单的守护进程:
[program:ticket_monitor] command=/path/to/venv/bin/python /path/to/ticket_monitor.py directory=/path/to/project autostart=true autorestart=true stderr_logfile=/path/to/logs/err.log stdout_logfile=/path/to/logs/out.logautorestart=true的意思是进程异常退出后自动拉起。这比你在代码里写while True再包一层try...except要可靠得多。
Windows下可以用计划任务的“如果任务失败重新启动”做到类似效果,或者用脚本套一层死循环,但体验一般。个人建议:春节这种短周期场景,能拆成定时短任务的就不要做成常驻进程,复杂度越低越不容易出问题。
5.4 断网、登录失效的处理
春节在老家,网络环境不稳定是常态。我在县城老家实测,晚上8点到11点用网高峰期,宽带延迟能到200毫秒以上,偶尔还会断线。
针对断网,我的方案是在脚本轮询前先做一次网络连通性检测:
import socket def is_network_ok(host="www.baidu.com", timeout=3): try: socket.create_connection((host, 80), timeout=timeout) return True except OSError: return False如果是12306的登录态过期,查询接口本身不需要登录,问题不大。但如果你后续扩展了自动下单功能,登录Cookie的刷新管理就是一门学问。我的建议是:查询场景保持匿名访问,不要为了“省一次手动输入账号”去维护登录态,复杂度会成倍上升。
6. 脚本落地后的真实体验与合规提醒
6.1 实测数据:脚本跑了多少天、节省多少时间
去年春运,我从腊月十六开始跑余票监控脚本,到除夕前一天,一共跑了14天。目标是从北京回老家的三趟车次。
对比前年的手动刷票体验,前年我每天早中晚查三次票,每次花10到15分钟,累计超过6小时,而且经常因为没及时看到余票而错过。去年脚本全天候轮询,总共推送了7次有票提醒,我成功买到了3张票(自己和父母各一张),实际人工投入时间不超过20分钟——主要是收到推送后的手动下单操作。
除夕群发祝福方面,往年我从晚上7点发到11点多,耗时超过4小时,还发错好几次。去年用脚本加长图方案,下午4点开始跑,5点半全部搞定,包含检查和修改的时间。而且因为用了模板加称呼替换,每个收件人看到的都是带自己名字的版本,反馈明显比以前“复制粘贴”的祝福好。
6.2 踩坑记录:硬编码带来的连环翻车
第一次跑祝福脚本时,我把联系人名单直接硬编码在Python文件里,看起来省事,实际上给后面挖了坑。中途客户名单新增了一个人,我得打开代码文件,找到那一长串字典,小心翼翼地插入一行,还要注意逗号不能漏。改完之后突然想到,万一代码换行把某个文件夹路径弄乱了怎么办?
于是我从一开始就决定用csv文件管理联系人数据,代码只负责读文件。这样做的好处是,数据更新完全不需要碰代码,给谁发、不发谁、改称呼,一个Excel全搞定。春节前三天你还在加人?没关系,改完CSV保存,脚本下次运行自动生效。
这个“数据与逻辑分离”的思路,后来也被我用在祝福模板上。模板和文案全部放在一个独立的纯文本文件中,脚本按标记读取。调整文案只需要改文本,不需要重新看代码逻辑。
另一个容易忽视的坑是时区和日期边界问题。腊月二十九的深夜,脚本用的是“当天”日期去查询余票,结果到了23点50分还在查当天的票,车次早就全部开走了,白白浪费请求次数。后来我加了判断:如果当前时间晚于20点,自动把查询日期滚到第二天。
6.3 合规边界与风险自检清单
自动化这件事,技术可行性只是其中一个维度,合规和伦理边界同样重要。
先说12306。铁路部门在用户协议中明确禁止使用非官方渠道进行自动购票。我的脚本只做“余票查询+信息推送”,不涉及提交订单、不绕过验证码、不影响购票系统正常使用,这在我个人判断中属于可接受的技术实践。但如果你要把它扩展成“全自动抢票”,请务必考虑法律风险。我强烈建议保持“监控+提醒”的定位,把最终决策和执行交给人来完成。
再说微信等社交平台。无论用pyautogui还是其他方式,批量操作都可能违反平台用户协议,并存在账号处罚风险。春节祝福是善意的,但善意不能成为行为的免责金牌。我的原则是:频率要低、数量要合理、内容要克制。宁可战线拉长一点,也不要在短时间轰炸。
风险自检清单,每次跑脚本前过一遍:
- 该操作是否影响其他人的正常使用体验?
- 是否遵守了目标平台的服务协议?
- 脚本出错时,是否有紧急停止机制(比如
FAILSAFE)? - 涉及个人数据的,是否只保存了本次任务必需的信息?
- 如果脚本被公开,是否会让你的账号或平台产生风险?
最后说一句我自己的体会。Python自动化脚本的价值不在于“替代人”,而在于把人的精力从重复劳动中解放出来,去做机器做不了的事——比如收到余票提醒后,综合时间、价格、换乘方案做出的购票决策;比如看完名单后,想想今年到底该给谁发什么样的祝福。技术解决“怎么做”,但“做什么”和“为什么做”,永远是人的课题。希望这篇文章能让你今年的春节,少一点机械的忙碌,多一点真正属于节日的时间。