一.所谓抢票,本质上就是基于浏览器驱动,实现登录、预定、确认信息的自动化,其操作主要有以下几步组成
1.登录
登录过程中,自动输入用户名和密码比较简单,难点在于识别验证码。截至目前,各种自动识别验证码的方案准确率都不高,因此,这里识别验证码由人工完成,选择图形验证码后点击“登陆”。
2.基本信息填写、查询、预定
在抢票的时候,需要按优先级轮询各种方案,整体没有难点
3.订单信息填写
4.订单确认
我们要实现的抢票软件主要实现以上四个步骤的自动化,代替人工的操作
二.准备工作
2.1 下载对应Chrome 浏览器版本的浏览器驱动
先chrome://version/查看浏览器的版本
然后下载与之版本匹配的驱动 chromedriver(附:下载网址)
2.2 Selenium 模块准备
打开CMD,输入命令下载
pip install -U selenium
2.3 必要的信息准备
列车购票官网经过数次改革,出发地、目的地、车次、席别等都不是明文,而是以编码表示。因此,需要提前准备好这些信息。信息获取方法:谷歌浏览器打开 12306 官网购票页面,鼠标右键“查看”可以获取到上述信息
三.编码工作
'''
Created on 2019年10月4日
coding=utf-8
@author: linhaiy
'''
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
class trainBooking:
"""
Info:用户应根据实际情况自定义以下信息
"""
#浏览器驱动程序的路径
driver_path='F:\Application\chromedriver\chromedriver.exe'
#自定义变量区
value_fromstation = '%u676D%u5DDE%2CHZH' # 始发站(杭州)
value_tostation = '%u6210%u90FD%2CCDW' # 终点站(成都)
value_date = '2019-10-20' # 出发时间
username='1150979147@qq.com' # 用户名
password='####' # 密码
#杭州-成都:车次&席别&预定
#车次信息字典,数据分别表示车次、一等座ID、二等座ID、无座ID、对应车次的预定按钮ID
train_info = {"D2222":[['ZY_56000D222251', 'ZE_56000D222251', 'WZ_56000D222251'], 'ticket_56000D222251'],
"D2262":[['ZY_56000D226251', 'ZE_56000D226251', 'WZ_56000D226251'], 'ticket_56000D226251']}
"""
Info:以下信息不需要修改
"""
ticket_type_dict = {'student': '//input[@name="sf" and @id="sf1"]',
'common': '//input[@name="sf" and @id="sf2"]'}
#车次类型字典
train_type_dict = {'T': '//input[@name="cc_type" and @value="T"]', # 特快
'G': '//input[@name="cc_type" and @value="G"]', # 高铁
'D': '//input[@name="cc_type" and @value="D"]', # 动车
'Z': '//input[@name="cc_type" and @value="Z"]'} # 直达
#登陆页面url
login_url = 'https://kyfw.12306.cn/otn/login/init'
#个人信息页面url
initmy_url = "https://kyfw.12306.cn/otn/view/index.html"
#订票页面url
book_url = 'https://kyfw.12306.cn/otn/leftTicket/init'
#乘客选择页面url
confirm_url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
"""
Info:构造函数,创建一个浏览器对象
"""
def __init__(self):
print(u"欢迎使用列车订票工具")
self.driver = webdriver.Chrome(self.driver_path)
self.driver.implicitly_wait(300)
"""
desc: 登陆模块 login_proc()设计
Info: 登陆过程处理函数,其中图形验证码需要手动选择
"""
def login_proc(self):
self.driver.get(self.login_url)
# sign in the user name
try:
self.driver.find_element_by_id("username").send_keys(self.username)
self.driver.find_element_by_id("password").send_keys(self.password)
except Exception as err:
print(u"输入用户名或密码失败!",err)
#点击验证码,人工辅助,目前识别图形验证码比较困难,因此选择人工辅助
print(u"请自行选择验证码,点击登陆")
while True:
if(self.driver.current_url != self.initmy_url):
time.sleep(1)
else:
print('Login finished!')
break
"""
desc: 基本信息填写模块 filling_proc()设计
Info: 填写起始站,终点站,出发时间,车次类型,车票类型等信息
"""
def filling_proc(self,train_type,ticket_type):
print (u'列车类型:', train_type)
print (u'车票类型:', ticket_type)
# 打开订票网页
self.driver.get(self.book_url)
# 选择始发站
self.driver.add_cookie({"name": "_jc_save_fromStation", "value": self.value_fromstation})
# 选择终点站
self.driver.add_cookie({"name": "_jc_save_toStation", "value": self.value_tostation})
# 选择出发日期
self.driver.add_cookie({"name": "_jc_save_fromDate", "value": self.value_date})
self.driver.refresh()
# 选择车次类型
if (train_type == 'T' or train_type == 'G' or train_type == 'D' or train_type == 'Z'):
self.driver.find_element_by_xpath(self.train_type_dict[train_type]).click()
else:
print (u"车次类型异常或未选择!(train_type=%s)" % train_type)
# 选择车票类型
if (ticket_type == 'student' or ticket_type == 'common'):
self.driver.find_element_by_xpath(self.ticket_type_dict[ticket_type]).click()
else:
print (u"车票类型异常或未选择!(train_type=%s)" % ticket_type)
"""
desc: 查询、预订、订单信息填写模块 booking_proc()设计
Info: 订票处理过程,循环查询符合条件的车次,如果存在则点击“预定”
"""
def booking_proc(self,refresh_interval=0):
book_ticket_flag = False
# 循环查询
while True:
time.sleep(refresh_interval)
# 点击“查询”按钮,刷新页面开始查询,查询按钮的ID="query_ticket"
search_btn = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, '//*[@id="query_ticket"]')))
search_btn.click()
# 扫描查询结果,根据自定义车次字典train_info提供的信息,逐一查询
try:
for train in self.train_info:
print(u"当前查询车次为:",train)
# 根据车次查询对应的席别:商务,一等,二等,无座等
seat_list= self.train_info.get(train)
print("当前所有抢票车次信息: ",seat_list)
for seat in seat_list[0]:
print("当前所抢的班次 :",seat_list[0])
ticket_seat_id = '//*[@id="' + seat + '"]'# 席别ID
tic_tb_item = 'default'
# 获取车票数量信息:"-","无","数字"
tic_tb_item = WebDriverWait(self.driver, 2).until(
EC.presence_of_element_located((By.XPATH, ticket_seat_id)))
tic_ava_num = tic_tb_item.text
# 无票或未开售,则结束当前查询
if(tic_ava_num == u'无' or tic_ava_num == u'*'):
print("当前无票或未开售,正在努力抢票....")
continue
# 如果车次有票,则点击对应车次的“预定”按钮
else:
book_ticket_btn = '//*[@id="' + seat_list[1] + '"]/td[13]/a'
self.driver.find_element_by_xpath(book_ticket_btn).click()
book_ticket_flag = True
print(u"开始预定")
break
if (book_ticket_flag):
break
except Exception as err:
print(err)
# 网络状态不好的时候,点击查询按钮,可能返回查询结果失败,对此异常可再次点击
search_btn.click()
if (book_ticket_flag):
break
"""
desc: 订单确认模块 confirm_proc() 设计
Info: 点击“预定”之后,需要确认乘客信息和座位信息
"""
def confirm_proc(self):
# 判断页面跳是否转至乘客选择页面
while True:
if (self.driver.current_url == self.confirm_url):
print (u'页面跳转成功!')
break
else:
print (u'等待页面跳转...')
time.sleep(1)
# 乘车人选择:针对乘车人列表多于一人的情况
print(u"选择乘客")
while True:
try:
# 选择乘车人列表中的第二个人
self.driver.find_element_by_xpath('//*[@id="normalPassenger_1"]').click()
break
except Exception as err:
print (u'等待常用联系人列表。。。',err)
time.sleep(0.5)
try:
print(u"提交订票信息")
self.driver.find_element_by_xpath('//*[@id="submitOrder_id"]').click()
time.sleep(1.5)
print(u"确认订票信息")
self.driver.find_element_by_xpath('//*[@id="qr_submit_id"]').click()
except Exception as err:
print (err)
"""
desc: 主函数测试
"""
if __name__ == '__main__':
leave_date = '2019-10-20'
train_type = 'D'
ticket_type = 'common'
refresh_interval = 0.1
booker=trainBooking()
print("开始尝试登陆")
booker.login_proc()
print("登陆成功,开始抢票!")
booker.filling_proc(train_type, ticket_type)
booker.booking_proc(refresh_interval)
booker.confirm_proc()
一运行函数就可以实现指定区间的抢票功能了,这里只是实现一个简单的功能,感兴趣的小伙伴可以将功能做得更强大一些,例如将一些固定的变量全变成可选可改,抢票函数放进一个线程池中,并行高效地实现抢票功能。这里精力有限,不做深究了。