Python3.x 抓取12306車次信息,表格詳情顯示,讓你學(xué)會(huì)思路,分析網(wǎng)站特點(diǎn),爬取數(shù)據(jù)。12306車票查看器!

我的例子都比較適合新手,那種老司機(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é)果展示:

我在window上運(yùn)行的結(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ù)

  1. 未安裝之前


    Linux上面目前的庫(kù)

安裝庫(kù):

pip3.5 install requests colorama docopt prettytble

  1. 安裝之后


    安裝完成之后的庫(kù)列表

ok,我們環(huán)境有了,庫(kù)有了,那么應(yīng)該干啥呢?

爬蟲個(gè)人分析:

  1. 制定爬取內(nèi)容
  2. 選取目標(biāo)
  3. 準(zhǔn)備環(huán)境,上面就提前說(shuō)了,因?yàn)檫@個(gè)本來(lái)就是在搞爬蟲,所以...
  4. 分析該網(wǎng)站的html結(jié)構(gòu),得到url
  5. 爬取數(shù)據(jù)
  6. 分析數(shù)據(jù)
  7. 封裝數(shù)據(jù)(組裝數(shù)據(jù)),弄成自己想要的樣子
  8. coding......

那么我們開始吧

第一步

當(dāng)然是打開12306的官網(wǎng)了,然后進(jìn)行一個(gè)余票查詢,當(dāng)然首先你得按一下f12,打開控制臺(tái)面板哦。
我是查詢的是:貴陽(yáng)--遵義西 10-31號(hào)的車票

f12之后,查票頁(yè)面

我先埋下一個(gè)伏筆:

我看到的貴陽(yáng)-遵義 10-31的車次一共是11個(gè)班次。

第二步

首先在控制臺(tái)找到Network按鈕,點(diǎn)擊。然后選擇XHR ---》找到請(qǐng)求到后臺(tái)的接口,

請(qǐng)求接口

那么我們先看一個(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)有什么有用的信息。

  1. 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)-遵義的任何信息

3.https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2017-10-31&leftTicketDTO.from_station=GIW&leftTicketDTO.to_station=ZIW&purpose_codes=ADULT

第三個(gè)接口返回的數(shù)據(jù)

哈哈,在里面我看到了貴陽(yáng),遵義,那么大膽的猜測(cè)這個(gè)接口有可能就是我們需要的。
我們繼續(xù)點(diǎn)開看看有什么東西,這些數(shù)據(jù)都是以json的格式傳遞過(guò)來(lái)的。


展開結(jié)果

到這里我相信,聰明的人已經(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)站中,只有兩種三種方式能這樣處理?

  1. 將這個(gè)地點(diǎn)-地點(diǎn)代碼字典寫入js中,這個(gè)地方有可能,因?yàn)閲?guó)內(nèi)的地點(diǎn)太多,需要手動(dòng)維護(hù)。
  2. 將地點(diǎn)-地點(diǎn)代碼字典寫入本地文件文件,做好緩存,每次讀取文件,然后使用。
  3. 從遠(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)行分析。

該頁(yè)面所加載的js文件

上圖中的矩形中的就是當(dāng)前頁(yè)面中的所有js,當(dāng)然每個(gè)js的作用我就不一一的說(shuō)了,各位需要幫助的可以email(leihfein@gmail.com)我,或者在下面留言。
那么我直接查看各個(gè)的內(nèi)容,發(fā)現(xiàn)有一個(gè)是:

包含了地點(diǎn),地點(diǎn)代碼js

這樣我們就得到了一個(gè)js的請(qǐng)求地址哦。https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9028

瀏覽器中輸入該URL看到的結(jié)果

我們還可以來(lái)一個(gè)測(cè)試:
我在查詢的時(shí)候是輸入了,貴陽(yáng)-遵義,搜索一下看看吧。

搜索

同學(xué)們,看到這個(gè)你們覺(jué)得爽不爽,說(shuō)明這個(gè)文件就是我們所需要的。

ok,到這里,編碼前期準(zhǔn)備工作,所有的都昨晚了,我從一步一步的分析,然后截圖。給大家思路,方法,步驟。希望大家能夠更明白,更多的是學(xué)習(xí)到其中的分析思路哈。

codingwars

  1. 首先爬取地點(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中就只能拷貝了,
新增一個(gè)stations字典名稱

好的,到這里我們的地點(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ō)一下:

  1. optdoc的使用,這個(gè)是我遇到的最大的坑,(usage 下面命令之后必須空一行)
  2. 在Linux上的縮進(jìn)問(wèn)題
    3.就是對(duì)python還不是特別的熟。

github地址

12306火車票查看器git地址

閱讀到本教程的童鞋,喜歡請(qǐng)點(diǎn)個(gè)喜歡,不求贊賞,只求喜歡,頂上去?,F(xiàn)在網(wǎng)上很多都是以前的接口的例子,已經(jīng)跑不起來(lái)了,所以我希望讓更多的人看到,能幫助更多還在py的起步階段,又沒(méi)有人練手項(xiàng)目的人。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 看到網(wǎng)上有很多火車票查詢的小腳本,參考一下,發(fā)現(xiàn)很多都已經(jīng)不能再運(yùn)行了,據(jù)說(shuō)12306接口返回的數(shù)據(jù)格式更新比較快...
    lexyhp閱讀 3,838評(píng)論 1 3
  • 前幾天看了一個(gè)爬取12306來(lái)獲得火車票信息的教程,發(fā)現(xiàn)12306官網(wǎng)的存儲(chǔ)車票信息的 Json 數(shù)據(jù)格式已經(jīng)變了...
    LiuHDme閱讀 2,108評(píng)論 0 12
  • 項(xiàng)目簡(jiǎn)介:使用Python3抓取12306網(wǎng)站信息提供一個(gè)命令行的火車票查詢工具。通過(guò)該項(xiàng)目的實(shí)現(xiàn),可以熟悉Pyt...
    海人為記閱讀 1,750評(píng)論 0 1
  • 蓋被子 2017.8.31 一直覺(jué)得自己對(duì)寶爸不夠上心,在他面前既不賢惠也不體貼,寶爸會(huì)有一些抱怨,我自己也有些自...
    Cici清清閱讀 248評(píng)論 0 0
  • 復(fù)習(xí)指南 金融衍生工具(李國(guó)華) 期貨市場(chǎng)的產(chǎn)生,發(fā)展,特點(diǎn),功能交易流程,結(jié)算流程,期貨套期保值,期貨投機(jī),利率...
    千張卷卷閱讀 327評(píng)論 0 0