1. 程式人生 > >【Python3爬蟲】12306爬蟲

【Python3爬蟲】12306爬蟲

此次要實現的目標是登入12306網站和檢視火車票資訊。

 

具體步驟

一、登入

登入功能是通過使用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]

 

完整程式碼已上傳到GitHub:https://github.com/QAQ112233/12306