【Python3爬蟲】當爬蟲碰到表單提交,有點意思
- 2019 年 10 月 3 日
- 筆記
一、寫在前面
我寫爬蟲已經寫了一段時間了,對於那些使用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!