一、引言:验证码,网络安全的第一道防线
在网络应用中,验证码是防止自动化攻击的重要手段。12306作为中国铁路售票系统,其验证码设计尤为复杂,从早期的数字字母组合,到后来的图片点击,再到如今的智能验证,一直在与自动化脚本进行着"军备竞赛"。
12306抢票项目的验证码处理模块,完整实现了从验证码获取、保存到识别的全流程,支持手动输入和自动识别两种方式,是学习Python验证码处理的绝佳范例。本文将深入分析12306项目的验证码处理逻辑,探讨验证码识别的技术要点和实践经验。
二、验证码处理流程:从获取到识别的完整链路
12306项目的验证码处理主要由两个核心文件组成,形成了完整的处理链路:
获取验证码 -> 保存图片 -> 识别验证码 -> 转换坐标 -> 提交验证1. 核心文件结构
inter/GetPassCodeNewOrderAndLogin.py:负责验证码图片的获取与保存inter/GetRandCode.py:负责验证码的识别(手动/自动)和坐标转换
三、验证码图片获取与保存:GetPassCodeNewOrderAndLogin.py
1. 功能实现
该模块负责从12306服务器下载验证码图片,支持登录和订单两种场景。
2. 核心代码解析
# -*- coding=utf-8 -*-fromconfig.urlConfimporturlsdefgetPassCodeNewOrderAndLogin(session,imgType):""" 下载验证码 :param session: 会话对象,包含HTTP客户端和URL配置 :param imgType: 下载验证码类型,login=登录验证码,其余为订单验证码 :return: 验证码图片二进制数据或False(失败时) """try:# 根据场景选择对应的URLifimgType=="login":url=session.urls["getCodeImg"]else:url=session.urls["codeImgByOrder"]# 添加随机数参数,防止缓存url="{0}?{1}".format(url,"r={0}".format(session.httpClint.send(session.urls["randCode"])))# 发送请求获取验证码response=session.httpClint.opener.open(url,timeout=5)result=response.read()returnresultexceptExceptionase:print(u"获取验证码失败: {0}".format(e))returnFalse3. 技术要点
- 动态URL生成:根据场景(登录/订单)选择不同的验证码URL
- 防缓存机制:添加随机数参数,确保每次获取的都是最新验证码
- 会话保持:使用会话对象的HTTP客户端,保持登录状态
- 异常处理:捕获网络异常,确保系统稳定性
四、验证码识别:GetRandCode.py的双重实现
1. 功能实现
该模块实现了验证码的识别功能,支持手动输入和自动识别两种方式,并将识别结果转换为12306服务器要求的坐标格式。
2. 核心代码解析
2.1 验证码识别主函数
# -*- coding=utf-8 -*-fromPILimportImagefromconfig.ticketConfimport_get_yamlfromdamatuCode.ruokuaiimportRClienttry:raw_input# Python 2exceptNameError:# Python 3raw_input=inputdefgetRandCode(is_auto_code,auto_code_type,result):""" 识别验证码 :param is_auto_code: 是否自动识别 :param auto_code_type: 自动识别类型(1:打码兔,2:若快) :param result: 验证码图片二进制数据 :return: 验证码坐标字符串或空字符串(失败时) """try:# 保存验证码图片到文件try:withopen('./tkcode.png','wb')asimg:img.write(result)print(u"验证码图片已成功保存到 ./tkcode.png")exceptExceptionase:print(u"保存验证码图片失败: {0}".format(e))return""ifis_auto_code:# 自动识别逻辑ifauto_code_type==1:print(u"打码兔已关闭, 如需使用自动识别,请使用若快平台 auto_code_type == 2")returnifauto_code_type==2:# 调用若快打码平台APIrc=RClient(_get_yaml()["auto_code_account"]["user"],_get_yaml()["auto_code_account"]["pwd"])Result=rc.rk_create(result,6113)# 6113是12306验证码类型if"Result"inResult:returncodexy(Ofset=",".join(list(Result["Result"])),is_raw_input=False)else:if"Error"inResultandResult["Error"]:print(u"打码平台错误: {0}, 请登录打码平台查看-http://www.ruokuai.com/client/index?6726".format(Result["Error"]))return""else:# 手动输入逻辑try:# 尝试打开验证码图片img=Image.open('./tkcode.png')print(u"正在打开验证码图片...")img.show()print(u"验证码图片已打开,请查看并输入")exceptExceptionase:print(u"打开验证码图片失败: {0}".format(e))print(u"请手动双击根目录下的 tkcode.png 文件查看验证码")# 调用手动输入函数returncodexy()exceptExceptionase:print(u"验证码处理异常: {0}".format(e))importtraceback traceback.print_exc()return""2.2 坐标转换函数
defcodexy(Ofset=None,is_raw_input=True):""" 获取验证码坐标 :param Ofset: 自动识别结果或None(手动输入时) :param is_raw_input: 是否手动输入 :return: 验证码坐标字符串 """ifis_raw_input:# 显示坐标提示print(u""" ***************** | 1 | 2 | 3 | 4 | ***************** | 5 | 6 | 7 | 8 | ***************** """)print(u"验证码分为8个,对应上面数字,例如第一和第二张,输入1, 2")print(u"如果是linux无图形界面,请使用自动打码,is_auto_code: True")print(u"如果没有弹出验证码,请手动双击根目录下的tkcode.png文件")Ofset=raw_input(u"输入对应的验证码: ")# 标准化输入格式Ofset=Ofset.replace(",",",")select=Ofset.split(',')# 坐标映射post=[]offsetsX=0# X坐标offsetsY=0# Y坐标forofsetinselect:ifofset=='1':offsetsY=77offsetsX=40elifofset=='2':offsetsY=77offsetsX=112elifofset=='3':offsetsY=77offsetsX=184elifofset=='4':offsetsY=77offsetsX=256elifofset=='5':offsetsY=149offsetsX=40elifofset=='6':offsetsY=149offsetsX=112elifofset=='7':offsetsY=149offsetsX=184elifofset=='8':offsetsY=149offsetsX=256else:passpost.append(offsetsX)post.append(offsetsY)# 转换为12306要求的坐标格式randCode=str(post).replace(']','').replace('[','').replace("'",'').replace(' ','')print(u"验证码识别坐标为{0}".format(randCode))returnrandCode3. 技术要点
- PIL/Pillow图像处理:用于打开和显示验证码图片
- 文件IO操作:将验证码图片保存到本地文件
- API调用:集成若快打码平台API,实现自动识别
- 坐标转换:将用户输入或API返回的结果转换为12306服务器要求的坐标格式
- 跨Python版本兼容:兼容Python 2和Python 3的
raw_input/input函数
五、手动vs自动:验证码处理方式对比
| 处理方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 手动输入 | 准确率高、无成本、无需依赖第三方服务 | 效率低、需要人工干预、不适合长时间运行 | 调试阶段、自动识别失败时 |
| 自动识别 | 效率高、无需人工干预、适合长时间运行 | 需要付费、准确率受平台影响、依赖网络 | 正式抢票、批量操作、无人值守 |
六、验证码识别经验技巧
1. 提高手动输入效率的技巧
- 熟悉坐标映射:记住8个位置对应的数字,减少思考时间
- 快速输入:直接输入数字,用逗号分隔,如"1,3,5"
- 注意大小写:确保输入法在英文状态,避免中文逗号
2. 优化自动识别的建议
- 选择可靠平台:若快平台对12306验证码有专门优化,识别率较高
- 保持账号余额充足:确保打码平台账号有足够余额,避免因余额不足导致识别失败
- 合理设置超时时间:根据网络情况调整API调用超时时间
- 添加重试机制:自动识别失败时,切换到手动模式或重试
3. 验证码处理的通用经验
- 保存验证码图片:无论自动还是手动识别,都建议保存验证码图片,便于调试和分析
- 添加异常处理:捕获可能出现的各种异常,确保系统稳定性
- 日志记录:记录验证码处理过程,便于排查问题
- 定期更新识别策略:随着验证码技术的发展,及时更新识别策略
七、代码优化建议
1. 验证码图片处理优化
# 优化前:直接保存二进制数据withopen('./tkcode.png','wb')asimg:img.write(result)# 优化后:添加图片验证和压缩fromPILimportImagefromioimportBytesIOtry:# 验证图片完整性img=Image.open(BytesIO(result))img.verify()# 重新打开并保存,可选择压缩img=Image.open(BytesIO(result))img.save('./tkcode.png',optimize=True,quality=90)print(u"验证码图片已成功保存到 ./tkcode.png")exceptExceptionase:print(u"验证码图片无效: {0}".format(e))return""2. 自动识别重试机制
# 优化前:单次调用Result=rc.rk_create(result,6113)# 优化后:添加重试机制max_retries=3foriinrange(max_retries):try:Result=rc.rk_create(result,6113)if"Result"inResult:returncodexy(Ofset=",".join(list(Result["Result"])),is_raw_input=False)exceptExceptionase:print(u"自动识别重试 {0}/{1} 失败: {2}".format(i+1,max_retries,e))time.sleep(1)3. 验证码坐标映射优化
# 优化前:多个if-elif分支ifofset=='1':offsetsY=77offsetsX=40# ... 其他分支# 优化后:使用字典映射,更简洁高效coord_map={'1':(40,77),'2':(112,77),'3':(184,77),'4':(256,77),'5':(40,149),'6':(112,149),'7':(184,149),'8':(256,149)}ifofsetincoord_map:offsetsX,offsetsY=coord_map[ofset]八、结语:验证码处理的技术演进
12306项目的验证码处理模块展示了Python在验证码处理方面的强大能力,从图片获取、保存到识别、坐标转换,形成了完整的处理链路。通过学习这个项目,我们可以掌握以下核心技术:
- PIL/Pillow图像处理:图片的打开、显示和保存
- 文件IO操作:二进制文件的读写
- API调用:第三方服务的集成和调用
- 用户交互设计:友好的用户提示和输入处理
- 异常处理:提高系统稳定性的关键
随着AI技术的发展,验证码识别技术也在不断演进,从早期的OCR识别,到如今的深度学习模型,识别准确率不断提高。然而,12306的验证码也在不断升级,从简单的图片点击,到如今的多轮验证,形成了一场持续的"攻防战"。
对于开发者来说,掌握验证码处理的核心技术,不仅可以应对12306这样的特定场景,还可以将这些技术应用到其他需要验证码处理的场景中。无论是手动输入还是自动识别,关键是根据实际需求选择合适的处理方式,并不断优化和改进。
希望本文对你理解Python验证码处理有所帮助,祝你在验证码处理的道路上越走越远!
参考资料:
- 12306抢票项目源码
- Python PIL/Pillow官方文档
- 若快打码平台API文档