实现原理:
1. 使用 selenium 模拟自动打开 chrome浏览器;
2. (浏览器打开后)会自动填写账号密码,手动选择验证码并提交;
3. 自动跳转选择“预定”及其单程购票设置;
4. 循环查询余票:先按顺序查询所有车次二等座,没有再按顺序查询无座,如此循环;
5. 发现余票后自动预定,并选择第一个乘客提交订单。
注意:提交订单后若没有余票,不会自动返回(没写完整)。此外,如果失败,又得重头再来,尝试多次频繁登录会导致账号短时间内不能登录。
验证码功能虽然没有实现,不过也大概了解了其验证原理:
验证码图片实为一张图(如下图),图片是动态的,地址为:
https://kyfw.12306.cn/passport/captcha/captcha-image
示例:https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.547952507783088
该图片分上下两块区域:上部分为文字,下部分为各图片组合。(可判断这图片是固定的,可爬取所有验证码下来做自动识别)
当我们点选下面的答案中的小图时,页面脚本会识别选择点的像素位置,生成一个 div 标签行,添加到表单中(即添加在验证码图片div内),如下图。
点击登录,首先进行验证码校验,后台有相应的接口来返回验证结果,校验成功才进行登录表单提交。
https://kyfw.12306.cn/passport/captcha/captcha-check
暂时只能人工识别验证码,主要是速度快,方便自己而已。如果需要特定的其他查询选择,则需要更完善些。这里没有这么完善。
#-*- coding: utf-8 -*-
# python 3.5.0
import re
import time
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
class Main(object):
def __init__(self):
self.username = "账号"
self.password = "密码"
self.fromStation = "深圳"
self.toStation = "南宁"
self.train_date = "2018-02-14"
self.train = ['G2912','G2914','G2916'] #优先顺序
self.loginurl = "https://kyfw.12306.cn/otn/login/init"
#_chromedriver = "D:/Python35/selenium/phantomjs/bin/phantomjs.exe"
#driver = webdriver.PhantomJS(_chromedriver)
self.chromedriver = 'D:/Python35/selenium/webdriver/chromedriver/chromedriver.exe'
self.driver = webdriver.Chrome(self.chromedriver)
def setUrl(self,url):
self.loginurl = url
def getUrl(self):
print("[0]打开浏览器")
self.driver.get(self.loginurl)
time.sleep(2)
def setUserPwd(self):
print("[1]设置登录账号")
self.driver.find_element_by_id("username").send_keys(self.username)
self.driver.find_element_by_id("password").send_keys(self.password)
#此处在浏览器界面手动点击验证码并登录!
def waitingNewPage(self):
print("[2]请手动点击登录")
while self.loginurl == self.driver.current_url:
print("[*]login waiting……")
time.sleep(3)
def selectYuding(self):
print("[3]点击车票预订")
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID,'selectYuding'))).click()
time.sleep(1)
def selectOptions(self):
print("[4]点击单程")
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID,'dc_label'))).click()
print("[5]点击普通票")
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID,'query_ticket'))).click()
def setQueryLeftForm(self):
print("[6]设置信息")
#出发地
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID,'fromStation_icon_image'))).click()
self.driver.find_element_by_xpath("//li[@title='%s'][@data='SZQ']" % self.fromStation).click()
#目的地
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID,'toStation_icon_image'))).click()
self.driver.find_element_by_xpath("//li[@title='%s'][@data='NNZ']" % self.toStation).click()
#出发日
year = self.train_date.split('-')[0]
month = self.train_date.split('-')[1]
day = self.train_date.split('-')[2]
y = 4 if year == "2017" else 5 if year == "2018" else 6
#m = int(month) - 1
#d = int(day)-1
m = int(month)
d = int(day)
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID,'date_icon_1'))).click()
self.driver.find_element_by_xpath("//div[@class='year']/input").click()
self.driver.find_element_by_xpath("//div[@class='year']/div/ul/li[%s]" % y).click()
self.driver.find_element_by_xpath("//div[@class='month']/input").click()
self.driver.find_element_by_xpath("//div[@class='month']/ul/li[%s]" % m).click()
self.driver.find_element_by_xpath("//div[@class='cal']/div[@class='cal-cm']/div[%s]/div" % d).click()
def queryTicket(self):
print("[7]点击查询")
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID,'query_ticket'))).click()
time.sleep(1)
#总车次数
def getTraiNum(self):
#self.driver.find_element_by_xpath("//div[@class='sear-result']/p[1]/strong[2]").text.strip()
return self.driver.find_element_by_id('trainum').text.strip()
def click(self,xpath):
self.driver.find_element_by_xpath(xpath).click()
def checkTicket(self,tx,zuo,name):
#是否是自己选择的车次,是则检查是否有二等座,有则预订
for t in self.train:
if re.match(t, tx):
if zuo == "--" or zuo == "无":
pass
else:
print("【可预订车次】%s [%s]" % (tx,name))
return 1
else:
pass
return 0
#遍历车次
def checkTicketAll(self):
click_xpath = ""
mark = 0
trainum = self.getTraiNum()
print("【共计 %s 个车次】" % trainum)
id = 1
while id <= 2*int(trainum): #第一次遍历所有车次的二等座
xpath = "//tbody[@id='queryLeftTable']/tr[%s]" % id
tx = self.driver.find_element_by_xpath(xpath + "/td[1]").text.strip().replace('\n', ',') #车次信息
ze = self.driver.find_element_by_xpath(xpath + "/td[4]").text.strip() #二等座
mk = self.checkTicket(tx,ze,"二等座")
click_xpath = xpath + "/td[13]/a"
id = id + 2
if mk == 1:
mark = 1
return mark,click_xpath
id = 1
while id <= int(trainum): #第二次遍历所有车次的无座
xpath = "//tbody[@id='queryLeftTable']/tr[%s]" % id
tx = self.driver.find_element_by_xpath(xpath + "/td[1]").text.strip().replace('\n', ',') #车次信息
wz = self.driver.find_element_by_xpath(xpath + "/td[11]").text.strip() #无座
mk = self.checkTicket(tx,wz,"无座")
click_xpath = xpath + "/td[13]/a"
id = id + 2
if mk == 1:
mark = 1
return mark,click_xpath
return mark,click_xpath
def checkTicketLoop(self):
click_xpath = ""
mark = 0
mark,click_xpath = self.checkTicketAll()
while mark == 0:
self.queryTicket()
time.sleep(2) #几秒刷新一次??
mark,click_xpath = self.checkTicketAll()
print("--------------------------------------------------")
#点击预订
if mark == 1:
print("[8]点击预定")
self.click(click_xpath)
#选择乘客,提交订单
def submitOrder(self):
try:
print("[9]选择乘客")
passenger = WebDriverWait(self.driver, 5).until(EC.presence_of_element_located((By.ID, 'normalPassenger_0')))
passenger.click()
print("[10]提交订单")
submit = WebDriverWait(self.driver, 5).until(EC.presence_of_element_located((By.ID, 'submitOrder_id')))
submit.click()
except TimeoutException:
print("Loading took too much time!")
time.sleep(1)
#检查余票
def checkTicketNum(self):
ze = self.driver.find_element_by_xpath("//p[@id='sy_ticket_num_id']/strong[1]").text.strip()
wz = self.driver.find_element_by_xpath("//p[@id='sy_ticket_num_id']/strong[1]").text.strip()
if int(ze) == 0 and int(wz) == 0 :
return 0
else:
return 1
def submitOrderConfirm(self):
print("[11]确认提交订单!")
self.driver.find_element_by_id("qr_submit_id").click()
if __name__ == "__main__":
main = Main()
sub = 0
#main.setUrl("https://kyfw.12306.cn/otn/leftTicket/init")
main.getUrl()
main.setUserPwd()
main.waitingNewPage()
main.selectYuding()
main.selectOptions()
main.setQueryLeftForm()
main.queryTicket()
main.checkTicketLoop()
main.submitOrder() #订单提交
sub = main.checkTicketNum()
while sub == 0:
print("[11]返回修改~")
main.setUrl("https://kyfw.12306.cn/otn/leftTicket/init")
main.getUrl()
main.selectYuding()
main.selectOptions()
main.setQueryLeftForm()
main.queryTicket()
main.checkTicketLoop()
main.submitOrder() #订单提交
sub = main.checkTicketNum()
if sub == 1:
main.submitOrderConfirm() #确认订单提交
"""
while True:
time.sleep(5)
"""