scrapy的一些容易忽視的點(模擬登陸,傳遞item等)
scrapy爬蟲注意事項
- item欄位傳遞後錯誤,混亂
- 對一個頁面要進行兩種或多種不同的解析
- xpath中contains的使用
- 提取不在標籤內的文字內容
- 使用css、xpath提取倒數第n個標籤
- 提取表格資訊(含合併單元格)
一、item資料只有最後一條
這種情況一般存在於對標籤進行遍歷時,將item物件放置在了for迴圈的外部。解決方式:將item放置在for迴圈裡面。
def parse(self,response): #item = ExampleItem()# 存在for迴圈時,item不要放置在這裡 for result in result_list: item = ExampleItem()# 放置在for迴圈裡面 item['name'] = result.css('div a::text').extract_first() item['age'] = result.css('div #id').extract_first() yield item
二、item欄位傳遞後錯誤,混亂
有時候會遇到這樣的情況,item傳遞幾次之後,發現不同頁面的資料被混亂的組合在了一起。這種情況一般存在於item的傳遞過程中,沒有使用深拷貝。解決方式:使用深拷貝來傳遞item。
import copy def parse_base(self,response): base_url = 'https://www.base_url.com' for result in result_list: item = ExampleItem() item['name'] = result.css('div a::text').extract_first() item['age'] = result.css('div #id').extract_first() yield scrapy.Request(url=base_url,meta=copy.deepcopy({'item':item}),callback=self.parse_detail) # 使用深拷貝將item存在meta中 def parse_detail(self,response): item = response.meta['item'] # 取出之前傳遞的item """ do some thing """ yield item
三、對一個頁面要進行兩種或多種不同的解析
這種情況一般出現在對同一頁面有不同的解析要求時,但預設情況下只能得到第一個parse的結果。產生這個結果的原因是scrapy預設對擁有相同的url,相同的body以及相同的請求方法視為一個請求。解決方式:設定引數dont_filter='True'。
def start_requests(self): base_url = 'https://www.base_url.com' yield scrapy.Request(url=base_url,dont_filter='True',callback=self.parse_one) yield scrapy.Request(url=base_url,dont_filter='True',callback=self.parse_two)
四、xpath中contains的使用
這種情況一般出現在標籤沒有特定屬性值但是文字中包含特定漢字的情況,當然也可以用來包含特定的屬性值來使用(只不過有特定屬性值的時候我也不會用contains了)。
作者:村上春樹
書名:挪威的森林
以上面這兩個標籤為例(自行F12檢視),兩個span標籤沒有特定的屬性值,但裡面一個包含作者,一個包含書名,就可以考慮使用contains來進行提取。
def parse(self,response): item = BookItem() item['author'] = response.xpath('//span[contains(.//text(),"作者:")]//text()').split('作者:')[-1]# 先用contains限定好特定的span標籤,然後取出文字字串並進行字串切片得到需要的資訊。下同 item['book_name'] = response.xpath('//span[contains(.//text(),"書名:")]//text()').split('書名:')[-1] yield item
五、提取不在標籤中的文字
有時候會遇到這樣的情況,文字在兩個標籤之間,但不屬於這兩個標籤的任何一個。此時可以考慮使用xpath的contains和following共同協助完成任務。
示例:
作者:
"村上春樹"
書名
"挪威的森林"
def parse(self,response): item = BookItem() item['author'] = response.xpath('//span[contains(.//text(),"作者")]/following::text()[1]')# 先用contains限定好特定的span標籤,然後提取出接下來的文字,並選擇第一個。下同 item['book_name'] = response.xpath('//span[contains(.//text(),"書名")]/following::text()[1]') yield item
六、使用css、xpath提取倒數第n個標籤
對於很多頁面,標籤的數量有時候無法保證是一致的。如果用正向的下標進行提取,很可能出現數組越界的情況。這種時候可以考慮反向提取,必要時加一些判斷。
def parse(self,response) ''' 使用css和xpath分別提取資訊 ''' item = ExampleItem() item['name'] = response.css('span::text').extract()[-1] # 使用css獲取最後一個 item['age'] = response.xpath('//span[last()-2]//text()').extract_first() # 使用xpath獲取倒數第二個,類似的last()-3是倒數第三個 yield item
七、提取表格資訊
其實對於資訊抓取,很多時候我們需要對錶格頁面進行抓取。一般的方方正正的表格提取相對簡單,這裡不討論。只說下含有合併單元格的情況。
以這個 網頁的表格為例,定義5個欄位批次,招生程式碼,專業,招生數量以及費用,注意到合併單元格的標籤裡有個rowspan屬性,可以用來辨識出有幾行被合併。我的思路是有多少行資料,就將batch批次擴充套件到多少個,形成一個新的列表,然後進行遍歷提取資料
def parse(self, response): batch_list = response.css('tr[class="pc"] td::text').extract() # batch批次的列表,本來這裡可以直接用extract_first()提取出來,這裡我用extract()是為了讓程式更通用化,也就是處理存在多個合併單元格的情況 rowspan_list = response.css('tr[class="pc"] td::attr(rowspan)').extract() # rowspan值形成的列表,本例中只有一個值 tr_list = response.css('tbody tr')[-2:] # 選擇最後兩個tr標籤 info_list = [] # 用來‘盛放’code、major、number、cost組合起來的列表 for tr in tr_list: code = tr.css('td::text').extract_first() major = tr.css('td::text').extract()[1] number = tr.css('td::text').extract()[2] cost = tr.css('td::text').extract()[3] info_list.append([code,major,number,cost]) batch_middle_list = list(map(lambda a, b: (int(a) - 1) * [b], rowspan_list, batch_list)) # python3 map要搭配list使用形成列表。將batch批次與number-1數量分別相乘得到一個新的列表(由列表組成的列表) batch_target_list = reduce(lambda a, b: a + b, batch_middle_list) # 使用reduce將列表的各項拼接成一個新的列表(由字元組成)。python3 reduce使用需要先匯入,from functools import reduce for i in range(len(batch_target_list)):# 此時patch批次新列表的元素個數與info_list的元素個數相同,可以進行遍歷提取 item = ExampleProjectItem() item['batch'] = batch_target_list[i] item['code'] = info_list[i][0] item['major'] = info_list[i][1] item['number'] = info_list[i][2] item['cost'] = info_list[i][3] yield item
八、模擬登陸
當頁面資料需要登陸進行抓取時,就需要模擬登陸了。常見的方式有:使用登陸後的cookie來抓取資料;傳送表單資料進行登陸;使用自動化測試工具登陸,比如selenium配合chrome、firefox等,不過聽說selenium不再更新,也可以使用chrome的無頭模式。鑑於自動化測試的抓取效率比較低,而且我確實很久沒使用過這個了。本次只討論使用cookie和傳送表單兩種方式來模擬登陸。
-
使用cookie
使用cookie的方式比較簡單,基本思路就是登陸後用抓包工具或者類似chrome的F12除錯介面檢視cookie值,傳送請求時帶上cookie值即可
base_url = 'http://www.example.com' cookies_str = 'xxxxxxxx' # 登陸後的cookie字串 cookies_dict = cookies_dict = {i.split('=')[0]: i.split('=')[1] for i in cookies_str.split('; ')} # 使用字典推導式將cookies_str轉化為字典形式 def parse(self,response): yield scrapy.Request(url=base_url,cookies=self.cookies_dict,callback=self.parse_detail) # 預設使用get方式進行請求 def parse_detail(self,response): detail_url = 'http://www.xxx.com' yield scrapy.Request(url=detail_url,method='POST',cookies=self.cookies_dict,callback=self.parse_target)# 每次發起請求帶上cookie def parse_target(self,response) target_url = 'http://www.ooo.com' yield scrapy.FormRequest(url=target_url,cookies=self.cookies_dict,callback=self.parse_final) # 使用FormRequest同樣也是可以的
-
傳送表單方式進行登陸
cookie是有有效期的,對於大量資料的抓取,更好的方式是傳送表單進行模擬登陸。scrapy有專門的函式scrapy.FormRequest()用來處理表單提交。網上有些人說cookie沒法保持,可以考慮用我下面的方式。
import copy def start_requests(self): base_url = 'http://www.example.com' formdata = { 'user':'1822233xxxx', 'password':'test123' } yield scrapy.FormRequest(url=base_url,formdata=formdata,meta=copy.deepcopy{'cookiejar':1},callback=self.parse)# 傳送表單資料,注意將cookiejar放到meta中進行傳送,也就是保持cookie。 def parse(self,response): yield scrapy.Request(url=detail_url,meta=copy.deepcopy({'cookiejar':response.meta['cookiejar']}),callback=self.parse_detail) # 每次傳送請求都帶上cookiejar,可以保持cookie def parse_detail(self,response) print(response.text)# 可以獲取到登陸後頁面的內容