前段时间买火车票老是需要让携程给我弄什么加速包来抢票,于是就想着自己弄一个抢票的小程序。刚开始本来也没打算用selenium来做,因为这个部署到服务器很麻烦,所以想着用requests+js2py来做,尝试了一下后发现从登陆到订票一系列的参数和跳转太多了,分析得我脑壳痛,于是不得已还是用了selenium+chromedriver,不得不说,嗯,真香!
我先说一下我的思路吧:
1、首先抛开登陆这个先不谈,先把简单的事儿做了。先尝试查询一下火车余票信息,这个其实可以用requests很简单的就能爬下来。当有余票信息后立马调用selenium。
2、对于selenium这部分来说主要是解决登录和订票问题,登录所需的验证码还是需要去找到地址爬取下来,然后人为去识别然后再在页面上获取其(x,y)值然后发送点击事件。这样是比较麻烦的一种做法,我想到一个简单的,就是用代码中实现在selenium上一次登录后记录下cookie值,写进文件里面,下次再需要的时候直接读取cookie值发送过去就行了。
3、订票的过程没什么好说的,按照页面点击就行。
4、最后由于订到票之后会有30分钟的付款时间,所以可以做一个邮件提醒功能,或是手机短信提醒,这样就能及时的付款了,还能调用微信的接口,给自己发条微信,也是可以的。
具体先看查询余票,12306有自己的一长串城市和城市代码的map,就是下面这个,我写了一个方法,方便返回城市名或城市代码。
station_map = '@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2@bjn|'
def get_city_or_citycode(city):
for i in station_map.split( '@'):
if i:
tmp = i.split( '|')
if city == tmp[1]:
return tmp[2]
elif city==tmp[2]:
return tmp[1]
return False
然后就可以发送请求了
import requests,json
from city_map import get_city_or_citycode
headers={
'Accept':'*/*',
'Accept-Encoding':'gzip, deflate, br',
'Accept-Language':'zh-CN,zh;q=0.9',
'Cache-Control':'no-cache',
'Connection':'keep-alive',
'Host':'kyfw.12306.cn',
'If-Modified-Since':'0',
'Referer':'https://kyfw.12306.cn/otn/leftTicket/init',
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
'X-Requested-With':'XMLHttpRequest'
}
def query_ticker(riqi="2018-12-31",from_sta='北京',to_sta="上海"):
from_sta=get_city_or_citycode(from_sta)
to_sta=get_city_or_citycode(to_sta)
url='https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=%s&leftTicketDTO.from_station=%s&leftTicketDTO.to_station=%s&purpose_codes=ADULT'%(riqi,from_sta,to_sta)
resp=requests.get(url)
data=json.loads(resp.text)
#这一步就拿到数据了
results=data.get('data').get('result')
#做一下数据清洗
index = 0
trains = []
for i in results:
trains.append([])
for n in i.split('|'):
trains[index].append(n)
index += 1
bancis=[]
for train in trains:
per={}
per['班次'] =train[3]
per['出发地'] =get_city_or_citycode(train[6])
per['目的地'] =get_city_or_citycode(train[7])
per['发车时间'] =train[8]
per['到达时间'] =train[9]
per['历时时间'] =train[10]
per['商务座'] = train[32]
per['一等座'] =train[31]
per['二等座'] =train[30]
per['高级软卧'] =train[21]
per['软卧'] =train[23]
per['硬卧'] =train[28]
per['硬座'] =train[29]
per['无座'] =train[26]
bancis.append(per)
print(bancis)
return bancis
if __name__ == '__main__':
riqi=input('请输入时间(例如"2018-01-01"):')
from_city=input('请输入出发站:')
to_city=input('请输入终点站:')
print('查询ing......')
trains=query_ticker(riqi,from_city,to_city)
按照它的规则组装好url就行,headers那部分我也没测哪些有用哪些没用,反正都给弄上去了,如果不行的话就再加上cookie,就应该没问题。
再就是抢票部分,这部分用了selenium,判断了一下当前目录下有没有cookie文件,没有的话需要手动在浏览器内后写入cookie,然后无限循环查票抢票,抢到后就调用邮件发送类,随便发个什么邮件以示抢票成功就行了,也可以换用微信接口,但是由于这部分不是重点,就不做多说了。
class Qiangpiao(object):
def __init__(self):
self.login_url = 'https://kyfw.12306.cn/otn/login/init'
self.initmy_url = 'https://kyfw.12306.cn/otn/view/index.html'
self.search_url = 'https://kyfw.12306.cn/otn/leftTicket/init'
self.confirmPassenger = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
self.need_write_cookie = True
self.success=False
def wait_input(self):
self.from_station = input('出发地:')
self.to_station = input('目的地:')
#"2018-12-01"
self.depart_time = input('出发时间:')
self.passengers = input('乘客姓名:(多个乘客使用英文逗号分割)').split(',')
self.trains = input('车次:(多个车次使用英文逗号分割)').split(',')
def _login(self,driver):
#查找cookies文件,没有则需要写入
path=os.getcwd()
lists=os.listdir(path)
for i in lists:
if i == 'cookies.json':
self.need_write_cookie = False
break
if self.need_write_cookie:
driver.get(self.login_url)
print(11)
WebDriverWait(driver,1000).until(EC.url_to_be(self.initmy_url))
dictCookies = driver.get_cookies()
jsonCookies = json.dumps(dictCookies)
# 登录完成后,将cookie保存到本地文件
with open('cookies.json', 'w') as f:
f.write(jsonCookies)
print('恭喜您,您已登录成功了!正在写入cookie......')
print('cookie写入成功!')
self.need_write_cookie = False
else:
# 一定要先在这个域下建立连接,并删除初始cookie,然后读取cookie
driver.get("https://kyfw.12306.cn/otn/leftTicket/init")
driver.delete_all_cookies()
with open('cookies.json', 'r', encoding='utf-8') as f:
listCookies = json.loads(f.read())
for cookie in listCookies:
driver.add_cookie({
'domain': '.12306.cn',
'name': cookie['name'],
'value': cookie['value'],
'path': '/',
'expires': None
})
print('添加cookie成功')
def _order_ticket(self,driver):
#订票
driver.get("https://kyfw.12306.cn/otn/leftTicket/init")
if driver.find_element_by_xpath('//input[@id="fromStationText"]'):
from_btn=driver.find_element_by_xpath('//input[@id="fromStationText"]')
to_btn=driver.find_element_by_xpath('//input[@id="toStationText"]')
from_btn.click()
from_btn.send_keys(self.from_station)
from_btn.send_keys(Keys.ENTER)
to_btn.click()
to_btn.send_keys(self.to_station)
to_btn.send_keys(Keys.ENTER)
go_input=driver.find_element_by_xpath('//input[@id="train_date"]')
go_input.click()
now_or_next_month=handle_time(self.depart_time)
if now_or_next_month[0]=='NOW_MONTH':
go_date=driver.find_element_by_xpath('//div[@class="cal"]//div[@class="cal-cm"]//div[@class="cell"][position()=%s]'%now_or_next_month[1])
else:
go_date=driver.find_element_by_xpath('//div[@class="cal cal-right"]//div[@class="cal-cm"]//div[@class="cell"][position()=%s]'%now_or_next_month[1])
go_date.click()
query_btn=driver.find_element_by_xpath('//a[@id="query_ticket"]')
query_btn.click()
WebDriverWait(driver, 1000).until(EC.presence_of_element_located((By.XPATH,".//tbody[@id = 'queryLeftTable']/tr")))
tr_list = driver.find_elements_by_xpath(".//tbody[@id ='queryLeftTable']/tr[not(@datatran)]")
for tr in tr_list:
train_number = tr.find_element_by_class_name('number').text
if train_number in self.trains:
left_ticket = tr.find_element_by_xpath('.//td[3]').text #找到第四个td标签下的文本
print(left_ticket,left_ticket.isdigit())
if left_ticket == '有' or left_ticket.isdigit(): #判断输入的车次是否在列表中
orderBotton = tr.find_element_by_class_name('btn72')
orderBotton.click()
#获取所有的乘客信息
WebDriverWait(driver, 1000).until(EC.url_to_be(self.confirmPassenger))
WebDriverWait(driver, 1000).until(EC.presence_of_element_located((By.XPATH,".//ul[@id = 'normal_passenger_id']/li")))
passanger_labels = driver.find_elements_by_xpath(".//ul[@id = 'normal_passenger_id']/li/label")
for passanger_label in passanger_labels:
name = passanger_label.text
if name in self.passengers:#判重
passanger_label.click()
#获取提交订单的按钮
submitBotton = driver.find_element_by_id('submitOrder_id')
submitBotton.click()
#等待确人订单对话框
WebDriverWait(driver, 1000).until(EC.presence_of_element_located((By.CLASS_NAME,'dhtmlx_wins_body_outer')))
#等待确认按钮
WebDriverWait(driver, 1000).until(EC.presence_of_element_located((By.ID,'qr_submit_id')))
ConBotton = driver.find_element_by_id('qr_submit_id')
ConBotton.click()
mail_sender=MailSender('[email protected]','今天去香格里拉吃饭')
mail_sender.sendEmail()
while ConBotton:
ConBotton.click()
ConBotton = driver.find_element_by_id('qr_submit_id')
self.success=True
return
def run(self,driver):
self.wait_input()
self._login(driver)
while not self.success:
self._order_ticket(driver)
if __name__ == '__main__':
driver = webdriver.Chrome(chrome_options=chrome_options,executable_path='D:\chromedriver.exe')
spider = Qiangpiao()
spider.run(driver)
最后还有改进的地方就是改写成多任务,这个也不难,再就是付款流程,因为要在30分钟内完成支付才行,所以可以进入付款页面获取支付的二维码图片,截下来后通过微信发送给自己就行了,也是很方便的。