用過12306
用Python寫一個命令行的火車票查看器
要求在命令行敲一行命令來獲得你想要的火車票信息
接口設置
先給這個小應用起個名字吧,既然及查詢票務信息,那就叫它tickets
我們希望用戶只要輸入出發站,到達站以及日期就讓就能獲得想要的信息,所以tickets應該這樣被使用:
$ tickets from to date
最終 $ tickets [-gdtkz] from to date
開發環境
用virtualenv建立虛擬環境,用Python3開發,
$ virtualenv -p /usr/bin/python3 venv
通過下面的命令激活它:
$ . venv/bin/activate
解析參數
Python有很多寫命令行應用的工具,我們選用docopt這個簡單易用的工具,我們先安裝它:
$ pip3 install docopt
docopt可以按我們在文檔字符串中定義的格式來解析參數,
獲取數據
https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=2016-07-01&from_station=SHH&to_station=BJP
https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8955
保存stations.html
顯示結果
prettytable這個庫可以讓我們它像MySQL數據庫那樣格式化顯示數據
$ pip3 install prettytable
各位同學注意啦!!
歡迎加入我的Python交流群
626062078
本文的文字及圖片來源于網絡,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯系我們以作處理。
作者:TM0831
送資料python學習群:⑹⑨⑸①⑻⑤⑷②⑼,先:到ā先「得←
一、登錄
登錄功能是通過使用selenium實現的,用到了超級鷹來識別驗證碼。沒有超級鷹賬號的先注冊一個賬號,充值一點題分,然后把下載這個Python接口文件,再在里面添加一個use_cjy的函數,以后使用的時候傳入文件名就可以了(驗證碼類型和價格可以在價格體系查看):
1 def use_cjy(filename):
2 username = "" # 用戶名
3 password = "" # 密碼
4 app_id = "" # 軟件ID
5 cjy = CJYClient(username, password, app_id) # 用戶中心>>軟件ID
6 im = open(filename, 'rb').read() # 本地圖片文件路徑
7 return cjy.PostPic(im, 9004) # 9004->驗證碼類型
然后進入12306的登錄頁面,網址為https://kyfw.12306.cn/otn/login/init,可以看到有一個像下面這樣的驗證碼:
要破解這個驗證碼,第一個問題是怎么得到這個驗證碼圖片,我們可以很輕松的找到這個驗證碼圖片的鏈接,但是如果用requests去請求這個鏈接,然后把圖片下載下來,這樣得到的圖片和網頁上的驗證碼圖片是不同的,因為每次請求都會刷新一次驗證碼。所以需要換個思路,比如先把網頁截個圖,然后我們可以知道驗證碼圖片在網頁中的位置,然后再根據這個位置,把截圖相應的位置給截取出來,就相當于把驗證碼圖片從整個截圖中給摳出來了,這樣得到的驗證碼圖片就和網頁上的驗證碼一樣了。相關代碼如下:
1 # 定位到驗證碼圖片
2 captcha_img = browser.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
3 location = captcha_img.location
4 size = captcha_img.size
5 # 寫成我們需要截取的位置坐標
6 coordinates = (int(location['x']), int(location['y']),
7 int(location['x'] + size['width']), int(location['y'] + size['height']))
8 browser.save_screenshot('screen.png')
9 i = Image.open('screen.png')
10 # 使用Image的crop函數,從截圖中再次截取我們需要的區域
11 verify_code_image = i.crop(coordinates)
12 verify_code_image.save('captcha.png')
現在已經得到了驗證碼圖片了,下一個問題是怎么識別?點觸驗證碼識別起來有兩個難點,一個是文字識別,要把圖上的鞭炮文字識別出來,第二點是識別圖片中的內容,比如上圖就要把有鞭炮的圖片識別出來,而這兩個難點利用OCR技術都很那實現,因此選擇使用打碼平臺(比如超級鷹)來識別驗證碼。對于上面這個圖,在使用超級鷹識別之后會返回下面這個結果:
{'pic_id': '6048511471893900001', 'err_no': 0, 'err_str': 'OK', 'md5': 'bde1de3b886fe2019a252934874c6669', 'pic_str': '117,140'}
其中pic_str對應的值就是有鞭炮的圖片的坐標位置(如果有多個坐標,會用“|”進行分隔),我們對這個結果進行解析,把坐標提取出來,再利用selenium模擬點擊就可以了,相關代碼如下:
1 # 調用超級鷹識別驗證碼
2 capture_result = use_cjy('captcha.png')
3 print(capture_result)
4 # 對返回的結果進行解析
5 groups = capture_result.get("pic_str").split('|')
6 points = [[int(number) for number in group.split(',')] for group in groups]
7 for point in points:
8 # 先定位到驗證圖片
9 element = WebDriverWait(browser, 20).until(
10 EC.presence_of_element_located((By.CLASS_NAME, "touclick-bgimg")))
11 # 模擬點擊驗證圖片
12 ActionChains(browser).move_to_element_with_offset(element, point[0], point[1]).click().perform()
13 sleep(1)
帶有車票信息的ajax接口很容易找到,格式也是標準的json格式,解析起來會方便不少
但是爆保存車票的字符串很復雜,我們先把第一條信息打印出來看看,以下是部分信息:
'hH0qeKPBgl0X0aCnrtZFyBgzqydzV45U2M1r%2F32FsaPHeb7Mul00sIb7y9W%2B6df1tUdDGCxqdVs8%0Aw2VodSjdXjUQ2uNdwFprKdVK9iaW60Wj2jKpNKaViR4ndlBCjsYB0SIF
QR0pLksy7HDP0KcaoLe4%0A4RW6zRcscO7SRNJZOsF%2Fxj3Ooq76lzzdku3Uw957yjLFyf7ikixOaC%2FAOrLAwCc7y0krRpKJbSn3%0ApBsY%2F%2Fok%2Bmg2xNhXapoCPIt4w0p9', 這段字符是隨機生成的,過幾秒就回失效。
'39000D30280G', 列車編號
'D3028', 車次
'HKN', 始發站
'AOH', 終點站
'HKN', 出發站
'AOH', 目的站
'07:31', 出發時間
'13:06', 到達時間
'05:35', 總耗時
'Y', Y表示可以購票,N表示不可以
'20181111', 日期
后面基本都是座位的余票信息了。
對于提到的列車站點代碼,可以通過請求這個鏈接,通過得到JS腳本中的station_names變量獲取,對應的站點以@字符分隔,相關代碼如下:
1 # 請求保存列車站點代碼的鏈接
2 res1 = requests.get("https://kyfw.12306.cn/otn/resources/js/framework/station_name.js")
3 # 把分割處理后的車站信息保存在station_data中
4 self.station_data = res1.text.lstrip("var station_names ='").rstrip("'").split('@')
1 # 返回車站英文縮寫
2 def get_station(self, city):
3 for i in self.station_data:
4 if city in i:
5 return i.split('|')[2]
6
7 # 返回車站中文縮寫
8 def get_city(self, station):
9 for i in self.station_data:
10 if station in i:
11 return i.split('|')[1]
由于ajax接口有了一點變化,所以我對之前的代碼做了一點修改,在輸入數據的部分:
1 # 需要按2018-01-01的格式輸入日期,不然會出現錯誤
2 d = input("請輸入日期(如:2018-01-01):")
3 f = self.get_station(input("請輸入您的出發站:"))
4 t = self.get_station(input("請輸入您的目的站:"))
5 url = "https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}" \
6 "&leftTicketDTO.to_station={}&purpose_codes=ADULT".format(d, f, t)
CJYDemo.py
import requests
from hashlib import md5
class CJYClient(object):
def __init__(self, username, password, soft_id):
self.username = username
self.password = md5(password.encode('utf8')).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: 圖片字節
codetype: 題目類型 參考 http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id:報錯題目的圖片ID
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
def use_cjy(filename):
username = "" # 用戶名
password = "" # 密碼
app_id = "" # 軟件ID
cjy = CJYClient(username, password, app_id) # 用戶中心>>軟件ID
im = open(filename, 'rb').read() # 本地圖片文件路徑
return cjy.PostPic(im, 9004) # 9004->驗證碼類型
test.py
央視新聞報道,鐵路部門宣布,新版火車票6月25日開始試用,新舊車票并存過渡,8月1日起全國推行。
趕緊來看看新舊火車票票面的具體變化吧:
1、新車票在始發站和到達站的站名后均標注上了“站”字;
新版
舊版
2、原來位于票面右上角的售票車站信息被調整至車票最下方21位售票碼的右邊,原來位于票面二維碼左邊的候車檢票位置信息則調整到了票面右上角;
新版
舊版
3、乘車人身份證號和姓名并排標注;
新版
舊版
4、部分車票正面將標注“買票請到12306,發貨請到95306”字樣。(不帶這樣打廣告的~)
遺憾的是,對于很多網友呼聲很高的到站時間,新版火車票仍未在票面上進行體現。
關注我的打工網公眾微信,打工故事招工信息,每天送到手!
微信公眾號:wodedagong
轉載請注明出處來自我的打工網(http://www.wodedagong.com/Infos/info-5475.html)
*請認真填寫需求信息,我們會在24小時內與您取得聯系。