scrapy爬蟲注意事項
- item數據只有最后一條
- 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) # 可以獲取到登陸后頁面的內容