歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

1.1 爬下12306

1.1爬取信息

[code]#!/bin/bash
curl --insecure --user-agent "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0" "https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=$1&from_station=SHH&to_station=BJP" | grep -oP "(?<={)[^{}]+(?=})" | sed -r 's/.*station_train_code":"([^"]+).*start_station_name":"([^"]+).*end_station_name":"([^"]+).*start_time":"([^"]+).*arrive_time":"([^"]+).*ze_num":"([^"]+).*zy_num":"([^"]+).*swz_num":"([^"]+).*/\1 \2 \3 \4 \5 \6 \7 \8/'

對於上圖的fetch_sh-bj.sh腳本程序,也許現在你還看得一頭霧水。
但請不要著急,熬過了黑夜就可以見到黎明的曙光。
先喝一口24K純度的涼白開壓壓驚,下面聽我為你娓娓道來關於fetch_sh-bj.sh前世今生。
前文提到fetch_sh-bj.sh一共可以分為三部分。
本小節我們先聊聊和爬取信息相關的那一部分—curl
curl命令可以分為三段:

第一段:
--insecure
選項

insecure
選項用於告知curl不對網站的證書做校驗。
我相信很多童鞋在第一次使用12306網站定票時,都有過類似的體驗,打開訂票頁面時,浏覽器爆出個“當前網頁不受信任,是否繼續”之類的警告信息。

curl在爬取訂票信息時,干著和浏覽器類似的事。
如果不指明
insecure
選項,則會顯示當前網頁認證失敗。
[code]cyf@cyf$curl --user-agent "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0" "https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=2016-05-14&from_station=SHH&to_station=BJP"
curl: (60) SSL certificate problem, verify that the CA cert is OK. Details:
error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
More details here:http://curl.haxx.se/docs/sslcerts.html 
curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

讀到這裡,愛鑽牛角尖的讀者一定會問:“可不可以不使用insecure選項,並且curl依然可以成功認證?“
嗯,這是個好問題。
關於這個問題,此處先劇透一下結論,在後文會給出詳細解釋。
結論就是“YES”

第二段:
--user-agent
選項

user-agent選項的值為:
"Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0"

網頁抓取的基本原理就是模擬浏覽器向服務器請求數據。
在FireFox中打開網頁時,浏覽器向服務器發送類似
"Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0"
的user-agent。
不同浏覽器版本,該值也許略有差異。
在這個例子中,curl就是模擬浏覽器從服務端獲取數據,所以我們添加了這段用於欺騙服務器的user-agent聲明。
細心的讀者也許發現了,在這個例子中,即使不添加
user-agent
也是可以運行的。
確實如此。
如果不添加user-agent,則curl使用默認這樣的user-agent:
[code]curl/7.22.0 (i686-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3

不同版本該值也許略有差異。
那是不是user-agent完全沒用呢?
也不盡然,只是在本例中沒有體現出來而已。
客戶端一般通過user-agent向服務端聲明自己。
服務器根據這個user-agent申明,判斷客戶端浏覽器類型。
針對不同的浏覽器,服務器給出不同的響應。
最簡單的例子就是手機上浏覽器和筆記本上的浏覽器。
不管屏幕尺寸多大的手機,與筆記本相比還是很小的。
所以同一個網頁在手機上和在筆記本上的呈現效果絕對是不一樣的。
手機也不一定總是處在wifi環境(土豪自行略過)。
所以對相同的網頁請求,服務器發給手機的數據量肯定比發給筆記本的數據量少。
舉例說明,我筆記本上FireFox的user-agent是這樣滴:
[code]Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0

同時我手機上UCWeb的user-agent是這樣滴:
[code]Mozilla/5.0 (Linux; U; Android 4.2.2; en-US; HUAWEI P6-T00 Build/HuaweiP6-T00) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/10.6.0.586 U3/0.8.0 Mobile Safari/534.30

我們分別假裝自己是FireFox和UCWeb下載百度首頁,並存文件如下圖所示。
從文件量可以看出FireFox共下載了96K的數據,而UCWeb下載了40K的數據
[code]cyf@cyf$curlhttps://www.baidu.com --user-agent "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0" > ff_baidu.html

cyf@cyf$curlhttps://www.baidu.com --user-agent "Mozilla/5.0 (Linux; U; Android 4.2.2; en-US; HUAWEI P6-T00 Build/HuaweiP6-T00) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/10.6.0.586 U3/0.8.0 Mobile Safari/534.30" > uc_baidu.html

cyf@cyf$ls -l ff_baidu.html uc_baidu.html 
-r--r--r-- 1 cyf cyf 96K Jul 18  2015 ff_baidu.html
-r--r--r-- 1 cyf cyf 40K Jul 18  2015 uc_baidu.html

直接使用浏覽器打開ff_baidu.htmluc_baidu.html可以發現兩者的展示效果亦不相同。
ff_baidu.html

uc_baidu.html

讀到這裡,也許有些讀者會有這樣的疑問,既然服務器對不同的user-agent可能給出不同的響應,那麼我怎麼知道我自己浏覽器的user-agent呢?
嗯,這也是個好問題。
此處先給出一種解決方案,後文繼續給出其它解決方案。
最簡單的方法就是打開http://whatsmyuseragent.com。
該網頁可顯示當前浏覽器的user-agent。
該網站同時列出了常用設備的user-agent。

第三段 網站URL

聰明如你的讀者一定能猜到第三段其實就是請求訂票信息的URL
[code]https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=$1&from_station=SHH&to_station=BJP

而且你也一定猜到
queryDate=$1
,中的
$1
就是我們手動敲入的日期。但是,你一定有這樣幾個小小的疑問:
1. 這個請求訂票信息的URL是如何找到的?
2. from_station=SHH中的SHH代表的是“上海火車站”、“上海南站”、“上海虹橋”還是“上海”?
3. 同理BJP代表的是北京還是北京的某個站點?
4. SHH和BJP是如何得到的,如果我想知道深圳、西安的代號,又該從哪找?
嗯,這些確實是問題,而且這些問題的解決手段是相同的,都是關於如何利用好現有工具的問題。
換位思考一下。
當使用筆記本在浏覽器查訂票信息時,浏覽器也一定向12306網站發送了相應的查票請求,只是浏覽器把這些東西放在後台完成,沒有對用戶展現而已。
如果我們有辦法把浏覽器查票時與服務器交互的所有操作均展示出來,那麼我們是否可以解決以上幾個疑問呢?
借助FireFox自帶的開發者工具或者下載Firebug網頁調試插件,可以把浏覽器與服務器交互所有信息一覽無余的展示給用戶。
Chrome、Opera、IE等其它浏覽器,也提供類似的網頁調試工具。
本文使用FireFox自帶的開發者工具來解決上述幾個疑問。
在Firefox浏覽器中打開12306查票頁面
1. 點擊浏覽器右上角的“Open menu”
2. 選擇“Developer”選項下的“Network”; 或者點擊“Tool”菜單,選擇“Web Developer”下的“Network”選項,打開網頁調試工具
Open menu -> Network

Tools->Web Developer->Network

網頁調試工具

隨意選擇“出發地”、“目的地”和“出發日”,點擊查詢。
憑借強大的調試工具,Network將上述操作過程中,浏覽器和12306服務器之間交互的數據均清晰的呈現出來。
聰明的讀者一定注意到了,當我們在頁面上點擊“查詢”時,Network立刻多出一條信息:”
200 GET query?prupose_codes=ADUILT&query...
“。
這條信息就是查詢訂票信息時,浏覽器向服務器發送的數據,點擊該條信息,可以在右邊的小窗口中得到更為詳細的描述,包括HeadersCookiesParams等信息,如下圖所示。
車票查詢

Headers選項卡下,展開Request Headers,可以查看浏覽器向服務器發送查票請求時的Header信息,裡面包含User-Agent
這是獲得User-Agent的第二種方法。
Headers選項卡中的Request URL即為第1個小疑問的答案。
選擇Params選項卡,即可得到請求訂票信息時,浏覽器向服務器發送的參數。
這些參數放在URL的“?”後面,並使用“&”區分不同參數。
浏覽器向服務器傳遞擦參數時,常用的方法有兩種:GETPOST
GET方法傳遞的參數直接加在URL後面,一般用於傳遞公開信息。
POST發則由浏覽器在後台發送,一般用戶傳遞用戶名和密碼等非公開信息。
Params選項卡中內容即為第2個和第3個小疑問的答案:SHH代表上海BJP代表北京
那麼如何解答第4個小疑問呢?
也許你會說,打開Network,在頁面上分別選擇“深圳”和“西安”,點擊“查詢”,不是可以在Network中顯示嗎?
嗯,這確實是個臨時性的方法。
但是12306網站上一共有上千個動車站點,我們總不能每個站點都用這麼土的方法獲取吧?
這樣做即不准確,還浪費時間。
所以一定有更優雅的,更高端的方法讓我們一次性獲得所有站點名稱和代號。
讓我們再次拿起Network這個調試利器。
可以發現在選“出發地”和“目的地”時,只有第一次操作時,浏覽器用GET方法向12306網站請求一個名為“close_show_citys.jpg”的圖片。
吐槽一下,city的復數形式是cities不是citys。
之後無論如何修改“出發地”和“目的地”,只要不“查詢“,浏覽器均不與服務器發生任何數據交互。
據此可以斷定,站點名稱和站點代號對應關系的數據在用戶打開查票網站(https://kyfw.12306.cn/otn/lcxxcx/init)時傳入浏覽器。
所以之後更改站點操作就不需要從服務器再次獲取站點名稱所對應的站點代號了。
因此我們需要看看頁面打開時,服務器向浏覽器發送了哪些數據。
點擊Network右下角的clear,再點擊刷新
在Network的一堆數據中,我們注意到有這麼一條信息:
[code]200 GET station_name.js?station_version=1.8...

獲取站點名稱及代號

點擊這條信息,在右邊展開的小窗口中選擇“Response”,這裡就有我們想要的信息,所有的站點以及站點所對應的代號

現在,我們差不多解答了第4個小疑問。
為什麼是差不多呢?
因為直接在Response裡看不舒服。
我們應該把這些信息提取出來,做成一張一一對應的表。
一列是站點名詞,一列是站點代號。
此處使用Bash腳本完成此事。
fetch_station_name.sh
[code]#!/bin/bash
curl --insecurehttps://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.8395 | grep -oE "@[^@]+" | gawk '{split($0,z,"|");print z[2],z[3]}'

到此位置,我們完成了對fetch_sh-bj.sh腳本的curl命令的解讀。
相信讀者可以據此快速修改出fetch_sh-sz.shfetch_sh-xa.sh了。
在結束本小節前,我們先看看crul命令的最終輸出結果,讀者可以在NetworkResponse查看,或使用文本編輯器打開sh-bj_2016-05-14.txt查看。
[code]curl --insecure --user-agent "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0" "https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=2016-05-14&from_station=SHH&to_station=BJP" > sh-bj_2016-05-14.txt

curl命令響應
Copyright © Linux教程網 All Rights Reserved