由于近期需要刷火车票,飞猪和智行刷没有vip略坑,自己尝试了一下。具体的思路:python requests库爬取12306余票结果,re模块正则找到想要的车次,通过twilio发送短信给自己的手机,python threading库实现多个车次的监控。
1. 环境:
linux,python 2.7,twilio
twilio : 支持python发送短信,https://www.twilio.com/ 免费注册,第一次尝试没有注册成功,百度结果是注册可能需要fq,用xxnet尝试注册仍然不行,后来无意间再试一次,没有fq也成功了,注册赠送$15,账号生成account_sid,auth_token和一个美国号码。
2. 实现:
2.1 初始化变量
url1="https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9098"
#获取12306站点代号查询结果,版本可能需要更新
html1=requests.get(url1).text
area=re.findall(ur"([\u4E00-\u9FA5]+)\|([A-Z]+)",html1)
area=dict(area)
#把站点代码映射存入一个dict
tag = '无'.decode('utf-8')
#查询结果标记
pool = []
#标记一个线程池
account_sid = '************************'
auth_token = '**************************'
#twilio 提供的接口
twilioclient = Client(account_sid, auth_token)
#登录twilio
2.2 生成12306查询网址
#param是订单参数
def gerUrl(param):
from_area=area[param['fs']]
to_area=area[param['ts']]
time=param['data']
url=("https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}"
"&leftTicketDTO.from_station={}"
"&leftTicketDTO.to_station={}"
"&purpose_codes=ADULT").format(time,from_area,to_area)
return url
2.3 查询及结果处理
def work(param):
print "mission start"
print param
tageturl = gerUrl(param)
while True :
time.sleep(1)
#到车票日期结束后,没有结果直接结束
if time.mktime(time.strptime(param['data'],"%Y-%m-%d"))-time.time() < 86400:
return
try:
html2=requests.get(tageturl)
s=html2.json()["data"]["result"]
except Exception as e:
continue
for area_new in s:
list=area_new.split("|")
if list[3]==param['zNo'] :
res = "%s 二等座:%s,无座:%s,%s"%(param['zNo'],list[30].encode('utf-8'),list[26].encode('utf-8'),datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
if list[30]!= tag :
#sendmail(text)
sendmsg(res)
return
2.4 发送邮件及短信
def sendmsg(text):
message = twilioclient.messages.create(
to="+8601234567890",
from_="+12345678901",
body=text)
print text
print ('Done.sent message success')
def sendmail(text):
host = 'smtp.163.com'
# 设置发件服务器地址
port = 465
# 设置发件服务器端口号。注意,这里有SSL和非SSL两种形式,现在一般是SSL方式
sender = 'xxx@163.com'
# 设置发件邮箱,一定要自己注册的邮箱
pwd = 'xxx'
# 设置发件邮箱的授权码密码,根据163邮箱提示,登录第三方邮件客户端需要授权码
receiver = 'xxxxx'
# 设置邮件接收人,可以是QQ邮箱
# 设置邮件正文,这里是支持HTML的
msg = MIMEText(text.decode('utf-8'), 'plain','gbk')
# 设置正文为符合邮件格式的HTML内容
msg['subject'] = '火车票监控'.decode('utf-8')
# 设置邮件标题
msg['from'] = sender
# 设置发送人
msg['to'] = receiver
# 设置接收人
try:
s = smtplib.SMTP_SSL(host, port)
# 注意!如果是使用SSL端口,这里就要改为SMTP_SSL
s.login(sender, pwd)
# 登陆邮箱
s.sendmail(sender, receiver, msg.as_string())
# 发送邮件!
print ('Done.sent email success')
except smtplib.SMTPException:
print ('Error.sent email fail')
2.5 多线程及主函数
def addtask(param):
t=threading.Thread(target=work, args=(param,))
t.setDaemon(True)
t.start()
pool.append(t)
time.sleep(5)
if __name__ == '__main__':
param1 = {
'fs':'合肥'.decode('utf-8'),
'ts':'杭州'.decode('utf-8'),
'zNo':'D2224',
'data':'2019-04-04'
}
param2 = {
'fs':'杭州'.decode('utf-8'),
'ts':'合肥'.decode('utf-8'),
'zNo':'D2192',
'data':'2019-04-07'
}
addtask(param1)
addtask(param2)
flag = True
while flag:
time.sleep(1)
for t in pool:
flag |= t.__dict__['_Thread__stopped']
print "mission completed!"
3. 结果及完善
由于比较仓促,只实现了功能,还有地方没有完善也不想太折腾,比如param中日期、地点和车次不匹配的话,没有提醒功能(这个比较容易实现,就是懒…),比如python实现登录后自动抢票等等,以后有时间完善吧。
好了,全部的代码也贴一遍。
#!/usr/bin/python
# encoding: utf-8
import sys
import threading
import re,requests
import smtplib
from email.mime.text import MIMEText
import time, datetime
from twilio.rest import Client
url1="https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9098"
html1=requests.get(url1).text
area=re.findall(ur"([\u4E00-\u9FA5]+)\|([A-Z]+)",html1)
area=dict(area)
tag = '无'.decode('utf-8')
pool = []
account_sid = '*********************'
auth_token = '**********************'
twilioclient = Client(account_sid, auth_token)
def gerUrl(param):
from_area=area[param['fs']]
to_area=area[param['ts']]
time=param['data']
url2=("https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}"
"&leftTicketDTO.from_station={}"
"&leftTicketDTO.to_station={}"
"&purpose_codes=ADULT").format(time,from_area,to_area)
return url2
#table=PrettyTable(["车次","出发站","到达站","出发时间","到达时间","历时","特等座","一等座","二等座","软卧","硬卧","软座","硬座","无座"])
def work(param):
print "mission start"
print param
tageturl = gerUrl(param)
while True :
time.sleep(1)
if time.mktime(time.strptime(param['data'],"%Y-%m-%d"))-time.time() < 86400:
return
try:
html2=requests.get(tageturl)
s=html2.json()["data"]["result"]
except Exception as e:
continue
for area_new in s:
list=area_new.split("|")
if list[3]==param['zNo'] :
res = "%s 二等座:%s,无座:%s,%s"%(param['zNo'],list[30].encode('utf-8'),list[26].encode('utf-8'),datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
if list[30]!= tag :
#sendmail(text)
sendmsg(res)
return
def sendmsg(text):
message = twilioclient.messages.create(
to="+8601234567890",
from_="+12345678901",
body=text)
print text
print ('Done.sent message success')
def sendmail(text):
host = 'smtp.163.com'
# 设置发件服务器地址
port = 465
# 设置发件服务器端口号。注意,这里有SSL和非SSL两种形式,现在一般是SSL方式
sender = 'xxxx@163.com'
# 设置发件邮箱,一定要自己注册的邮箱
pwd = 'xxxxx'
# 设置发件邮箱的授权码密码,根据163邮箱提示,登录第三方邮件客户端需要授权码
receiver = 'xxx'
# 设置邮件接收人,可以是QQ邮箱
# 设置邮件正文,这里是支持HTML的
msg = MIMEText(text.decode('utf-8'), 'plain','gbk')
# 设置正文为符合邮件格式的HTML内容
msg['subject'] = '火车票监控'.decode('utf-8')
# 设置邮件标题
msg['from'] = sender
# 设置发送人
msg['to'] = receiver
# 设置接收人
try:
s = smtplib.SMTP_SSL(host, port)
# 注意!如果是使用SSL端口,这里就要改为SMTP_SSL
s.login(sender, pwd)
# 登陆邮箱
s.sendmail(sender, receiver, msg.as_string())
# 发送邮件!
print ('Done.sent email success')
except smtplib.SMTPException:
print ('Error.sent email fail')
def addtask(param):
t=threading.Thread(target=work, args=(param,))
t.setDaemon(True)
t.start()
pool.append(t)
time.sleep(5)
if __name__ == '__main__':
param1 = {
'fs':'合肥'.decode('utf-8'),
'ts':'杭州'.decode('utf-8'),
'zNo':'D2224',
'data':'2019-04-04'
}
param2 = {
'fs':'杭州'.decode('utf-8'),
'ts':'合肥'.decode('utf-8'),
'zNo':'D2192',
'data':'2019-04-07'
}
addtask(param1)
addtask(param2)
flag = True
while flag:
time.sleep(1)
for t in pool:
flag |= t.__dict__['_Thread__stopped']
print "mission completed!"
'''
#这里查找别人的结果,指出各个字段的意思。。。。
YUNXIN=list[1]
no=list[3]
from_area=list[6]
to_area=list[7]
star_time=list[8]
over_time=list[9]
pass_time=list[10]
TDZ=list[32] or"--"
YDZ=list[31] or"--"
EDZ=list[30] or"--"
RW=list[23] or "--"
YW=list[28] or "--"
RZ=list[27] or"--"
YZ=list[29] or"--"
WZ=list[26] or"--"
table.add_row([no,t[from_area],t[to_area],star_time,over_time,pass_time,TDZ,YDZ,EDZ,RW,YW,RZ,YZ,WZ])
#print(table)'''