我的例子都比較適合新手,那種老司機(jī)請(qǐng)繞道,謝謝!
ps
查詢車票接口被更換了,就是多了一個(gè)O而已,不知道啥時(shí)候又要換成什么樣?我tm能說(shuō)是開發(fā)后臺(tái)的那個(gè)逼輸錯(cuò)單詞了不https://kyfw.12306.cn/otn/leftTicket/queryO?leftTicketDTO.train_date=2017-10-31&leftTicketDTO.from_station=GIW&leftTicketDTO.to_station=ZIW&purpose_codes=ADULT
前言
最近學(xué)習(xí)Python,所以呢?跟大家一樣,都是看看官網(wǎng),看看教程,然后就準(zhǔn)備搞一個(gè)小東西來(lái)試試,那么我使用的例子是實(shí)驗(yàn)樓中的12306火車票查詢例子。但是那個(gè)是2.7版本的,并且那個(gè)實(shí)驗(yàn)樓的ubuntu系統(tǒng)老是一些包裝不上,沒(méi)辦法就在我電腦上搞好了。
結(jié)果展示:
下面這一段說(shuō)明我是抄的,哈哈,因?yàn)槲易约涸僭趺磳戇€不是同樣的內(nèi)容。
讓我們先給這個(gè)小應(yīng)用起個(gè)名字吧,既然及查詢票務(wù)信息,那就tickets,其實(shí) 大家隨意了,需要發(fā)布就需要起一個(gè)更好的名字,不然只要自己玩兒的懂,但是要有程序的特點(diǎn),所以還是tickets相關(guān)的吧。方便閱讀和自己記憶。
我們希望用戶只要輸入出發(fā)站,到達(dá)站以及日期就讓就能獲得想要的信息,比如要查看10月31號(hào)貴陽(yáng)-遵義西的火車余票, 我們只需輸入:
python3.5 lnlr.py 貴陽(yáng) 遵義西 2017-10-31
注意:上面的日期(包括后面的)是筆者寫文章時(shí)確定的日期,當(dāng)你在做這個(gè)項(xiàng)目的時(shí)候可能要根據(jù)當(dāng)前時(shí)間做適當(dāng)調(diào)整。
轉(zhuǎn)化為程序語(yǔ)言就是:
python3.5 lnlr.py from to date
另外,火車有各種類型,高鐵、動(dòng)車、特快、快速和直達(dá),我們希望可以提供選項(xiàng)只查詢特定的一種或幾種的火車,所以,我們應(yīng)該有下面這些選項(xiàng):
-g 高鐵
-d 動(dòng)車
-t 特快
-k 快速
-z 直達(dá)
這幾個(gè)選項(xiàng)應(yīng)該能被組合使用,所以,最終我們的接口應(yīng)該是這個(gè)樣子的:
python3.5 lnlr.py [options] from to date
接口已經(jīng)確定好了,剩下的就是實(shí)現(xiàn)它了。
環(huán)境
Centos 7 linux 系統(tǒng)
Python3.5.2
使用到的庫(kù)
docopt------>命令行解釋器(把我玩兒死)
colorama--->一個(gè)文本著色器
requests --->爬蟲必備,http請(qǐng)求庫(kù)
prettytable->表格顯示
安裝庫(kù)
-
未安裝之前
Linux上面目前的庫(kù)
安裝庫(kù):
pip3.5 install requests colorama docopt prettytble
-
安裝之后
安裝完成之后的庫(kù)列表
ok,我們環(huán)境有了,庫(kù)有了,那么應(yīng)該干啥呢?
爬蟲個(gè)人分析:
- 制定爬取內(nèi)容
- 選取目標(biāo)
- 準(zhǔn)備環(huán)境,上面就提前說(shuō)了,因?yàn)檫@個(gè)本來(lái)就是在搞爬蟲,所以...
- 分析該網(wǎng)站的html結(jié)構(gòu),得到url
- 爬取數(shù)據(jù)
- 分析數(shù)據(jù)
- 封裝數(shù)據(jù)(組裝數(shù)據(jù)),弄成自己想要的樣子
- coding......
那么我們開始吧
第一步
當(dāng)然是打開12306的官網(wǎng)了,然后進(jìn)行一個(gè)余票查詢,當(dāng)然首先你得按一下f12,打開控制臺(tái)面板哦。
我是查詢的是:貴陽(yáng)--遵義西 10-31號(hào)的車票
我先埋下一個(gè)伏筆:
我看到的貴陽(yáng)-遵義 10-31的車次一共是11個(gè)班次。
第二步
首先在控制臺(tái)找到Network按鈕,點(diǎn)擊。然后選擇XHR ---》找到請(qǐng)求到后臺(tái)的接口,
那么我們先看一個(gè)三個(gè)接口的請(qǐng)求和返回的數(shù)據(jù):
1.https://ad.12306.cn/sdk/webservice/rest/appService/getAdAppInfo.json?placementNo=0004&clientType=2&billMaterialsId=28e783cd2ec048ee8575cc3e502292c2
查看返回的結(jié)果:
貌似沒(méi)有什么有用的信息。
-
https://kyfw.12306.cn/otn/leftTicket/log?leftTicketDTO.train_date=2017-10-31&leftTicketDTO.from_station=GIW&leftTicketDTO.to_station=ZIW&purpose_codes=ADULT
好像也看不到關(guān)于貴陽(yáng)-遵義的任何信息
哈哈,在里面我看到了貴陽(yáng),遵義,那么大膽的猜測(cè)這個(gè)接口有可能就是我們需要的。
我們繼續(xù)點(diǎn)開看看有什么東西,這些數(shù)據(jù)都是以json的格式傳遞過(guò)來(lái)的。
到這里我相信,聰明的人已經(jīng)知道了這個(gè)就是我們所需要的接口了,而這些數(shù)據(jù)就絕對(duì)是車次信息的數(shù)據(jù)。
由上面的我f12查看到的數(shù)據(jù)是11條,那么你們就沒(méi)有點(diǎn)小激動(dòng)么?
說(shuō)明這個(gè)接口就是我們所需要的接口無(wú)誤。那么現(xiàn)在我們就要得到它的url咯。
靠,雙十一快到了,被女朋友抓去看了一會(huì)兒衣服,可能今天就不寫了,明天接著寫。
那么這樣:我就明天開始分析url,然后就coding
請(qǐng)求的接口URL:
https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-10-31&leftTicketDTO.from_station=GIW&leftTicketDTO.to_station=ZIW&purpose_codes=ADULT
分析:
1. 查看請(qǐng)求方式 POST/GET(常用的),這里使用的是GET請(qǐng)求
2. 肯定帶有參數(shù)了,并且GET請(qǐng)求是拼接到url之后的,我們查詢的時(shí)候是輸入了起始地點(diǎn),目的地點(diǎn),出發(fā)時(shí)間。那么url上也應(yīng)該帶有這三個(gè)。
3. 得到參數(shù)名稱:leftTicketDTO.train_date=2017-10-31,leftTicketDTO.from_station=GIW,leftTicketDTO.to_station=ZIW
還有一個(gè)參數(shù):purpose_codes=ADULT 根據(jù)ADULT的意思(成人,成年)大膽猜測(cè)這就是學(xué)生票和成人票。
4.得到了四個(gè)參數(shù),但是我們還不知道其中有兩個(gè)GIW,ZIW是什么意思。
因?yàn)槲逸斎氲氖侵形?,但是出現(xiàn)的是字母代號(hào)。做過(guò)前后臺(tái)交互的同學(xué)應(yīng)該覺(jué)得這種是很常見的。目的是避免了中文傳輸導(dǎo)致的問(wèn)題。
分析參數(shù)的獲取
leftTicketDTO.train_date=2017-10-31 時(shí)間
leftTicketDTO.from_station=GIW 出發(fā)地
leftTicketDTO.to_station=ZIW 目的地
同學(xué)們請(qǐng)注意:我們輸入的是中文,出來(lái)的是地點(diǎn)代碼,說(shuō)明中間有一層轉(zhuǎn)換,那么在常規(guī)的網(wǎng)站中,只有兩種三種方式能這樣處理?
- 將這個(gè)地點(diǎn)-地點(diǎn)代碼字典寫入js中,這個(gè)地方有可能,因?yàn)閲?guó)內(nèi)的地點(diǎn)太多,需要手動(dòng)維護(hù)。
- 將地點(diǎn)-地點(diǎn)代碼字典寫入本地文件文件,做好緩存,每次讀取文件,然后使用。
- 從遠(yuǎn)端服務(wù)器進(jìn)行獲取,在這里也沒(méi)必要,因?yàn)槊看味家フ?qǐng)求后臺(tái),增加服務(wù)器的壓力,這個(gè)是沒(méi)必要的,因?yàn)檫@個(gè)字典的話是基本不會(huì)變化的。
ok經(jīng)過(guò)上面的分析,我們就能很清楚的知道這個(gè)字典絕對(duì)是從js獲取的,那么我們就也一樣的使用發(fā)f12來(lái)檢查到資源,找到該頁(yè)面所引用的所有js,然后進(jìn)行分析。
上圖中的矩形中的就是當(dāng)前頁(yè)面中的所有js,當(dāng)然每個(gè)js的作用我就不一一的說(shuō)了,各位需要幫助的可以email(leihfein@gmail.com)我,或者在下面留言。
那么我直接查看各個(gè)的內(nèi)容,發(fā)現(xiàn)有一個(gè)是:
這樣我們就得到了一個(gè)js的請(qǐng)求地址哦。https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9028
我們還可以來(lái)一個(gè)測(cè)試:
我在查詢的時(shí)候是輸入了,貴陽(yáng)-遵義,搜索一下看看吧。
同學(xué)們,看到這個(gè)你們覺(jué)得爽不爽,說(shuō)明這個(gè)文件就是我們所需要的。
ok,到這里,編碼前期準(zhǔn)備工作,所有的都昨晚了,我從一步一步的分析,然后截圖。給大家思路,方法,步驟。希望大家能夠更明白,更多的是學(xué)習(xí)到其中的分析思路哈。
codingwars
- 首先爬取地點(diǎn)-代碼code字典。
#!/usr/bin/env python3
# coding: utf-8
import requests
import re
from pprint import pprint
"""
獲取到地點(diǎn)-地點(diǎn)code字典
"""
def get_station():
url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9028'
response = requests.get(url,verify=False)
station = re.findall(u'([\u4e00-\u95fa5]+)\|([A-Z]+)',response.text)
pprint(dict(station),indent=4)
if __name__=="__main__":
get_station()
在最后,我是使用pprint輸出到屏幕的,大家可以進(jìn)行拷貝,或者是使用Linux的重定向進(jìn)行輸出到別的文件 。
python3.5 get_station_code.py > stations.py
并且需要在stations.py文件中增加一個(gè)名字哦,因?yàn)檩敵鰜?lái)時(shí)也沒(méi)有名字的。見下圖
在windows中就只能拷貝了,
好的,到這里我們的地點(diǎn)-地點(diǎn)代碼就得到了,那么我們就應(yīng)該寫那個(gè)爬取車次信息的py了。
處理輸出的代碼:
from prettytable import PrettyTable
from stations import stations
from colorama import init, Fore
"""
處理爬取出來(lái)的車次信息,并進(jìn)行表格輸出
"""
init()
class TrickCollection(object):
def __init__(self, available_trains, options):
self.header = ('車次 車站 時(shí)間 歷時(shí) 特等座 一等 二等 高級(jí)軟臥 軟臥 動(dòng)臥 硬臥 '
+ '軟座 硬座 無(wú)座 備注').split()
self.available_trains = available_trains
self.options = options
# 將歷時(shí)轉(zhuǎn)化為小時(shí)和分鐘的形式
def get_duration(self, raw_train):
duration = raw_train[10].replace(':', '小時(shí)') + '分'
if duration.startswith('00'):
return duration[4:]
if duration.startswith('0'):
return duration[1:]
return duration
# 返回每個(gè)車次的基本信息
def trains(self):
for raw_train in self.available_trains:
# 列車號(hào)
train_no = raw_train[3]
# 得到什么列車并小寫
initial = train_no[0].lower()
# 反轉(zhuǎn)station所對(duì)應(yīng)的字典
stations_re = dict(zip(stations.values(), stations.keys()))
if not self.options or initial in self.options:
# 將車次的信息保存到列表中
# train 出發(fā)地
begin_station = stations_re.get(raw_train[4])
# train 目的地
end_station = stations_re.get(raw_train[5])
# your 出發(fā)地
from_station = stations_re.get(raw_train[6])
# your 目的地
to_station = stations_re.get(raw_train[7])
# 判斷是起始還是經(jīng)過(guò)
begin_flag = self.__check_equals(begin_station, from_station)
end_flag = self.__check_equals(end_station, to_station)
train = [
train_no,
'\n'.join([begin_flag + ' ' + self.__get_color(Fore.GREEN, from_station),
end_flag + ' ' + self.__get_color(Fore.RED, to_station)]),
'\n'.join([self.__get_color(Fore.GREEN, raw_train[8]),
self.__get_color(Fore.RED, raw_train[9])]),
# 時(shí)間
self.get_duration(raw_train),
# 歷時(shí)
raw_train[32],
# 特等座
self.__show_color(raw_train[31]),
# 一等
self.__show_color(raw_train[30]),
# 二等
self.__show_color(raw_train[22]),
# 高級(jí)軟臥
self.__show_color(raw_train[23]),
# 軟臥
self.__show_color(raw_train[33]),
# 硬臥
self.__show_color(raw_train[28]),
# 軟座
self.__show_color(raw_train[24]),
# 硬座
self.__show_color(raw_train[29]),
# 無(wú)座
self.__show_color(raw_train[26]),
# 備注
self.__show_color(raw_train[1])
]
# 更改不運(yùn)行車次的時(shí)間和歷時(shí)
if raw_train[14] == 'null':
train[2] = '--\n--'
train[3] = '--'
# 將空字符串轉(zhuǎn)化為‘--’
for i, item in enumerate(train):
if not item:
train[i] = '--'
yield train
def __check_equals(self, from_station, to_station):
"""
檢查是否是始、過(guò)
檢查你的起始站是否為該車次的起始站
檢查你的終止站是否為該車次的終止站
:param from_station: 出發(fā)位置
:param to_station: 結(jié)束位置
:return: 決定了是使用‘始' 還是 ’過(guò)‘
"""
if from_station == to_station:
return '始'
else:
return '過(guò)'
def __get_color(self, color, content):
"""
返回顏色內(nèi)容組合,并且清除該內(nèi)容之后的顏色
:param color: 傳遞的顏色
:param content: 內(nèi)容
:return: 返回值為拼接上顏色
"""
return color + content + Fore.RESET
def __show_color(self, content):
"""
對(duì)內(nèi)容進(jìn)行顏色顯示,并且只顯示有,其余不上色
:param content: 需要顏色顯示的內(nèi)容
:return: 返回設(shè)置結(jié)果
"""
if content == '有':
return Fore.GREEN + content + Fore.RESET
else:
return content
def pretty_print(self):
"""
顯示內(nèi)容
:return:
"""
pt = PrettyTable()
pt._set_field_names(self.header)
for train in self.trains():
pt.add_row(train)
print(pt)
成果展示:
但是我在遠(yuǎn)端上使用xshell沒(méi)有顏色,這個(gè)是什么鬼。最后發(fā)現(xiàn)是自己設(shè)置xshell而已。
好的,下面就是我們的程序執(zhí)行的結(jié)果。
如果程序輸入的地點(diǎn)在字典中查詢不到,那么就不能查票。所以我們需要隨時(shí)更新stations.py文件(地點(diǎn)-code字典表)
ok,到這里的話,我們的所有的爬蟲就抓取成功,并且輸出成我們想要的結(jié)果了。我準(zhǔn)備下一步就是搞那個(gè)搶票。試試吧。
代碼在后面我會(huì)給出github地址的。
總結(jié)
在本次實(shí)驗(yàn)中,我所遇到的問(wèn)題簡(jiǎn)單說(shuō)一下:
- optdoc的使用,這個(gè)是我遇到的最大的坑,(usage 下面命令之后必須空一行)
- 在Linux上的縮進(jìn)問(wèn)題
3.就是對(duì)python還不是特別的熟。
github地址
閱讀到本教程的童鞋,喜歡請(qǐng)點(diǎn)個(gè)喜歡,不求贊賞,只求喜歡,頂上去?,F(xiàn)在網(wǎng)上很多都是以前的接口的例子,已經(jīng)跑不起來(lái)了,所以我希望讓更多的人看到,能幫助更多還在py的起步階段,又沒(méi)有人練手項(xiàng)目的人。