一、引言:验证码,网络安全的第一道防线
在网络应用中,验证码是防止自动化攻击的重要手段。12306作为中国铁路售票系统,其验证码设计尤为复杂,从早期的数字字母组合,到后来的图片点击,再到如今的智能验证,一直在与自动化脚本进行着"军备竞赛"。
12306抢票项目的验证码处理模块,完整实现了从验证码获取、保存到识别的全流程,支持手动输入和自动识别两种方式,是学习Python验证码处理的绝佳范例。本文将深入分析12306项目的验证码处理逻辑,探讨验证码识别的技术要点和实践经验。
二、验证码处理流程:从获取到识别的完整链路
12306项目的验证码处理主要由两个核心文件组成,形成了完整的处理链路:
获取验证码 -> 保存图片 -> 识别验证码 -> 转换坐标 -> 提交验证
1. 核心文件结构
inter/GetPassCodeNewOrderAndLogin.py:负责验证码图片的获取与保存inter/GetRandCode.py:负责验证码的识别(手动/自动)和坐标转换
三、验证码图片获取与保存:GetPassCodeNewOrderAndLogin.py
1. 功能实现
该模块负责从12306服务器下载验证码图片,支持登录和订单两种场景。
2. 核心代码解析
python
# -*- coding=utf-8 -*-
from config.urlConf import urls
def getPassCodeNewOrderAndLogin(session, imgType):
"""
下载验证码
:param session: 会话对象,包含HTTP客户端和URL配置
:param imgType: 下载验证码类型,login=登录验证码,其余为订单验证码
:return: 验证码图片二进制数据或False(失败时)
"""
try:
# 根据场景选择对应的URL
if imgType == "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()
return result
except Exception as e:
print(u"获取验证码失败: {0}".format(e))
return False
3. 技术要点
- 动态URL生成:根据场景(登录/订单)选择不同的验证码URL
- 防缓存机制:添加随机数参数,确保每次获取的都是最新验证码
- 会话保持:使用会话对象的HTTP客户端,保持登录状态
- 异常处理:捕获网络异常,确保系统稳定性
四、验证码识别:GetRandCode.py的双重实现
1. 功能实现
该模块实现了验证码的识别功能,支持手动输入和自动识别两种方式,并将识别结果转换为12306服务器要求的坐标格式。
2. 核心代码解析
2.1 验证码识别主函数
python
# -*- coding=utf-8 -*-
from PIL import Image
from config.ticketConf import _get_yaml
from damatuCode.ruokuai import RClient
try:
raw_input # Python 2
except NameError: # Python 3
raw_input = input
def getRandCode(is_auto_code, auto_code_type, result):
"""
识别验证码
:param is_auto_code: 是否自动识别
:param auto_code_type: 自动识别类型(1:打码兔,2:若快)
:param result: 验证码图片二进制数据
:return: 验证码坐标字符串或空字符串(失败时)
"""
try:
# 保存验证码图片到文件
try:
with open('./tkcode.png', 'wb') as img:
img.write(result)
print(u"验证码图片已成功保存到 ./tkcode.png")
except Exception as e:
print(u"保存验证码图片失败: {0}".format(e))
return ""
if is_auto_code:
# 自动识别逻辑
if auto_code_type == 1:
print(u"打码兔已关闭, 如需使用自动识别,请使用若快平台 auto_code_type == 2")
return
if auto_code_type == 2:
# 调用若快打码平台API
rc = RClient(_get_yaml()["auto_code_account"]["user"], _get_yaml()["auto_code_account"]["pwd"])
Result = rc.rk_create(result, 6113) # 6113是12306验证码类型
if "Result" in Result:
return codexy(Ofset=",".join(list(Result["Result"])), is_raw_input=False)
else:
if "Error" in Result and Result["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"验证码图片已打开,请查看并输入")
except Exception as e:
print(u"打开验证码图片失败: {0}".format(e))
print(u"请手动双击根目录下的 tkcode.png 文件查看验证码")
# 调用手动输入函数
return codexy()
except Exception as e:
print(u"验证码处理异常: {0}".format(e))
import traceback
traceback.print_exc()
return ""
2.2 坐标转换函数
python
def codexy(Ofset=None, is_raw_input=True):
"""
获取验证码坐标
:param Ofset: 自动识别结果或None(手动输入时)
:param is_raw_input: 是否手动输入
:return: 验证码坐标字符串
"""
if is_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坐标
for ofset in select:
if ofset == '1':
offsetsY = 77
offsetsX = 40
elif ofset == '2':
offsetsY = 77
offsetsX = 112
elif ofset == '3':
offsetsY = 77
offsetsX = 184
elif ofset == '4':
offsetsY = 77
offsetsX = 256
elif ofset == '5':
offsetsY = 149
offsetsX = 40
elif ofset == '6':
offsetsY = 149
offsetsX = 112
elif ofset == '7':
offsetsY = 149
offsetsX = 184
elif ofset == '8':
offsetsY = 149
offsetsX = 256
else:
pass
post.append(offsetsX)
post.append(offsetsY)
# 转换为12306要求的坐标格式
randCode = str(post).replace(']', '').replace('[', '').replace("'", '').replace(' ', '')
print(u"验证码识别坐标为{0}".format(randCode))
return randCode
3. 技术要点
- 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. 验证码图片处理优化
python
# 优化前:直接保存二进制数据
with open('./tkcode.png', 'wb') as img:
img.write(result)
# 优化后:添加图片验证和压缩
from PIL import Image
from io import BytesIO
try:
# 验证图片完整性
img = Image.open(BytesIO(result))
img.verify()
# 重新打开并保存,可选择压缩
img = Image.open(BytesIO(result))
img.save('./tkcode.png', optimize=True, quality=90)
print(u"验证码图片已成功保存到 ./tkcode.png")
except Exception as e:
print(u"验证码图片无效: {0}".format(e))
return ""
2. 自动识别重试机制
python
# 优化前:单次调用
Result = rc.rk_create(result, 6113)
# 优化后:添加重试机制
max_retries = 3
for i in range(max_retries):
try:
Result = rc.rk_create(result, 6113)
if "Result" in Result:
return codexy(Ofset=",".join(list(Result["Result"])), is_raw_input=False)
except Exception as e:
print(u"自动识别重试 {0}/{1} 失败: {2}".format(i+1, max_retries, e))
time.sleep(1)
3. 验证码坐标映射优化
python
# 优化前:多个if-elif分支
if ofset == '1':
offsetsY = 77
offsetsX = 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)
}
if ofset in coord_map:
offsetsX, offsetsY = coord_map[ofset]
八、结语:验证码处理的技术演进
12306项目的验证码处理模块展示了Python在验证码处理方面的强大能力,从图片获取、保存到识别、坐标转换,形成了完整的处理链路。通过学习这个项目,我们可以掌握以下核心技术:
- PIL/Pillow图像处理:图片的打开、显示和保存
- 文件IO操作:二进制文件的读写
- API调用:第三方服务的集成和调用
- 用户交互设计:友好的用户提示和输入处理
- 异常处理:提高系统稳定性的关键
随着AI技术的发展,验证码识别技术也在不断演进,从早期的OCR识别,到如今的深度学习模型,识别准确率不断提高。然而,12306的验证码也在不断升级,从简单的图片点击,到如今的多轮验证,形成了一场持续的"攻防战"。
对于开发者来说,掌握验证码处理的核心技术,不仅可以应对12306这样的特定场景,还可以将这些技术应用到其他需要验证码处理的场景中。无论是手动输入还是自动识别,关键是根据实际需求选择合适的处理方式,并不断优化和改进。
希望本文对你理解Python验证码处理有所帮助,祝你在验证码处理的道路上越走越远!
参考资料:
- 12306抢票项目源码
- Python PIL/Pillow官方文档
- 若快打码平台API文档