1. 程式人生 > >【Python3爬蟲】當爬蟲碰到表單提交,有點意思

【Python3爬蟲】當爬蟲碰到表單提交,有點意思

一、寫在前面

  我寫爬蟲已經寫了一段時間了,對於那些使用GET請求或者POST請求的網頁,爬取的時候都還算得心應手。不過最近遇到了一個有趣的網站,雖然爬取的難度不大,不過因為表單提交的存在,所以一開始還是有點摸不著頭腦。至於最後怎麼解決的,請慢慢往下看。

 

二、頁面分析

  這次爬取的網站是:https://www.ctic.org/crm?tdsourcetag=s_pctim_aiomsg,該網站提供了美國的一些農田管理的資料。要檢視具體的資料,需要選擇年份、單位、地區、作物種類等,如下圖:

  根據以往的經驗,這種表單提交都是通過ajax來完成的,所以熟練地按F12開啟開發者工具,選擇XHR選項,然後點選“View Summary”,結果卻什麼都沒有......

  這是怎麼回事?不急,切換到All看一下有沒有什麼可疑的東西。果然就找到了下面這個,可以看到在Form Data中包含了很多引數,而且可以很明顯看出來是一些年份、地區等資訊,這就是表單提交的內容:

  可以注意到在這些引數中有一個_csrf,很明顯是一個加密引數,那麼要怎麼得到這個引數呢?返回填寫表單的網頁,在開發者工具中切換到Elements,然後搜尋_csrf看看,很快就找到了如下資訊:

  其餘引數都是表單中所選擇的內容,只要對應填寫就行了。不過這個請求返回的狀態碼是302,為什麼會是302呢?302狀態碼的使用場景是請求的資源暫時駐留在不同的URI下,因此還需要繼續尋找。

  通過進一步查詢可知,最終的URL是:https://www.ctic.org/crm/?action=result。

  

三、主要步驟 

1.爬取郡縣資訊

  可以看到表單中包含了地區、州、郡縣選項,在填寫表單的時候,這一部分都是通過JS來實現的。開啟開發者工具,然後在頁面上點選County,選擇Region和State,就能在開發者工具中找到相應的請求。主要有兩個請求,如下:

https://www.ctic.org/admin/custom/crm/getstates/

https://www.ctic.org/admin/custom/crm/getcounties/

  這兩個請求返回的結果格式如下圖:

  這裡可以使用正則匹配,也可以使用lxml來解析,我選擇使用後者。示例程式碼如下:

1 from lxml import etree
2 
3 
4 html = '"<option  value=\"Autauga\">Autauga<\/option><option  value=\"Baldwin\">Baldwin<\/option><option  value=\"Barbour\">Barbour<\/option><option  value=\"Bibb\">Bibb<\/option><option  value=\"Blount\">Blount<\/option><option  value=\"Bullock\">Bullock<\/option><option  value=\"Butler\">Butler<\/option><option  value=\"Calhoun\">Calhoun<\/option><option  value=\"Chambers\">Chambers<\/option><option  value=\"Cherokee\">Cherokee<\/option><option  value=\"Chilton\">Chilton<\/option><option  value=\"Choctaw\">Choctaw<\/option><option  value=\"Clarke\">Clarke<\/option><option  value=\"Clay\">Clay<\/option><option  value=\"Cleburne\">Cleburne<\/option><option  value=\"Coffee\">Coffee<\/option><option  value=\"Colbert\">Colbert<\/option><option  value=\"Conecuh\">Conecuh<\/option><option  value=\"Coosa\">Coosa<\/option><option  value=\"Covington\">Covington<\/option><option  value=\"Crenshaw\">Crenshaw<\/option><option  value=\"Cullman\">Cullman<\/option><option  value=\"Dale\">Dale<\/option><option  value=\"Dallas\">Dallas<\/option><option  value=\"Dekalb\">Dekalb<\/option><option  value=\"Elmore\">Elmore<\/option><option  value=\"Escambia\">Escambia<\/option><option  value=\"Etowah\">Etowah<\/option><option  value=\"Fayette\">Fayette<\/option><option  value=\"Franklin\">Franklin<\/option><option  value=\"Geneva\">Geneva<\/option><option  value=\"Greene\">Greene<\/option><option  value=\"Hale\">Hale<\/option><option  value=\"Henry\">Henry<\/option><option  value=\"Houston\">Houston<\/option><option  value=\"Jackson\">Jackson<\/option><option  value=\"Jefferson\">Jefferson<\/option><option  value=\"Lamar\">Lamar<\/option><option  value=\"Lauderdale\">Lauderdale<\/option><option  value=\"Lawrence\">Lawrence<\/option><option  value=\"Lee\">Lee<\/option><option  value=\"Limestone\">Limestone<\/option><option  value=\"Lowndes\">Lowndes<\/option><option  value=\"Macon\">Macon<\/option><option  value=\"Madison\">Madison<\/option><option  value=\"Marengo\">Marengo<\/option><option  value=\"Marion\">Marion<\/option><option  value=\"Marshall\">Marshall<\/option><option  value=\"Mobile\">Mobile<\/option><option  value=\"Monroe\">Monroe<\/option><option  value=\"Montgomery\">Montgomery<\/option><option  value=\"Morgan\">Morgan<\/option><option  value=\"Perry\">Perry<\/option><option  value=\"Pickens\">Pickens<\/option><option  value=\"Pike\">Pike<\/option><option  value=\"Randolph\">Randolph<\/option><option  value=\"Russell\">Russell<\/option><option  value=\"Shelby\">Shelby<\/option><option  value=\"St Clair\">St Clair<\/option><option  value=\"Sumter\">Sumter<\/option><option  value=\"Talladega\">Talladega<\/option><option  value=\"Tallapoosa\">Tallapoosa<\/option><option  value=\"Tuscaloosa\">Tuscaloosa<\/option><option  value=\"Walker\">Walker<\/option><option  value=\"Washington\">Washington<\/option><option  value=\"Wilcox\">Wilcox<\/option><option  value=\"Winston\">Winston<\/option>"'
5 et = etree.HTML(html)
6 result = et.xpath('//option/text()')
7 result = [i.rstrip('"') for i in result]
8 print(result)

  上面程式碼輸出的結果為:

['Autauga', 'Baldwin', 'Barbour', 'Bibb', 'Blount', 'Bullock', 'Butler', 'Calhoun', 'Chambers', 'Cherokee', 'Chilton', 'Choctaw', 'Clarke', 'Clay', 'Cleburne', 'Coffee', 'Colbert', 'Conecuh', 'Coosa', 'Covington', 'Crenshaw', 'Cullman', 'Dale', 'Dallas', 'Dekalb', 'Elmore', 'Escambia', 'Etowah', 'Fayette', 'Franklin', 'Geneva', 'Greene', 'Hale', 'Henry', 'Houston', 'Jackson', 'Jefferson', 'Lamar', 'Lauderdale', 'Lawrence', 'Lee', 'Limestone', 'Lowndes', 'Macon', 'Madison', 'Marengo', 'Marion', 'Marshall', 'Mobile', 'Monroe', 'Montgomery', 'Morgan', 'Perry', 'Pickens', 'Pike', 'Randolph', 'Russell', 'Shelby', 'St Clair', 'Sumter', 'Talladega', 'Tallapoosa', 'Tuscaloosa', 'Walker', 'Washington', 'Wilcox', 'Winston']

  獲取所有郡縣資訊的思路為分別選擇四個地區,然後遍歷每個地區下面的州,再遍歷每個州所包含的郡縣,最終得到所有郡縣資訊。

2.爬取農田資料

   在得到郡縣資訊之後,就可以構造獲取農田資料的請求所需要的引數了。在獲取農田資料之前,需要向伺服器傳送一個提交表單的請求,不然是得不到資料的。在我測試的時候,傳送提交表單的請求的時候,返回的狀態碼並不是302,不過這並不影響之後的操作,所以可以忽略掉。

  需要注意的是,引數中是有一個年份資訊的,前面我一直是預設用的2011,不過要爬取更多資訊的話,還需要改變這個年份資訊。而通過選擇頁面元素可以知道,這個網站提供了16個年份的農田資料資訊,這16個年份為:

[1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,2002,2004,2006,2007,2008,2011]

  得到這些年份資訊之後,就可以和前面的郡縣資訊進行排列組合得到所有提交表單的請求所需要的引數。說道排列組合,一般會用for迴圈來實現,不過這裡推薦一種方法,就是使用itertools.product,使用示例如下:

 1 from itertools import product
 2 
 3 a = [1, 2, 3]
 4 b = [2, 4]
 5 result = product(a, b)
 6 for i in result:
 7     print(i, end=" ")
 8 
 9 
10 # (1, 2) (1, 4) (2, 2) (2, 4) (3, 2) (3, 4) 

  下面是農田資料的部分截圖,其中包含了很多種類的作物,還有對應的耕地面積資訊,不過在這個表中有些我們不需要的資訊,比如耕地面積總量資訊,還有空白行,這都是干擾資料,在解析的時候要清洗掉。

  解析農田資料部分的程式碼如下:

1 et = etree.HTML(html)
2 crop_list = et.xpath('//*[@id="crm_results_eight"]/tbody/tr/td[1]/text()')  # 作物名稱
3 area_list = et.xpath('//*[@id="crm_results_eight"]/tbody/tr/td[2]/text()')  # 耕地面積
4 conservation_list = et.xpath('//*[@id="crm_results_eight"]/tbody/tr/td[6]/text()')  # 受保護耕地面積
5 crop_list = crop_list[:-3]
6 area_list = area_list[:-3]
7 conservation_list = conservation_list[:-3]

 

 完整程式碼已上傳到GitHub!