前言
Scrapy那么多模塊都是怎么結合的啊?明明在chrome上的xpath helper插件寫好了xpath,為什么到程序就讀取的是None?Scrapy可以直接寫多層response么?難道必須再使用requests庫??
沒關系,這篇文章一站式解答scrapy常見的坑
Scrapy各部分運行機制
-
Scrapy是用純Python實現一個為了爬取網站數據、提取結構性數據而編寫的應用框架,用途非常廣泛。
-
框架的力量,用戶只需要定制開發幾個模塊就可以輕松的實現一個爬蟲,用來抓取網頁內容以及各種圖片,非常之方便。
-
Scrapy 使用了 Twisted['twɪstɪd](其主要對手是Tornado)異步網絡框架來處理網絡通訊,可以加快我們的下載速度,不用自己去實現異步框架,並且包含了各種中間件接口,可以靈活的完成各種需求。
-
Scrapy Engine(引擎): 負責Spider、ItemPipeline、Downloader、Scheduler中間的通訊,信號、數據傳遞等。
-
Scheduler(調度器): 它負責接受引擎發送過來的Request請求,並按照一定的方式進行整理排列,入隊,當引擎需要時,交還給引擎。
-
Downloader(下載器):負責下載Scrapy Engine(引擎)發送的所有Requests請求,並將其獲取到的Responses交還給Scrapy Engine(引擎),由引擎交給Spider來處理,
-
Spider(爬蟲):它負責處理所有Responses,從中分析提取數據,獲取Item字段需要的數據,並將需要跟進的URL提交給引擎,再次進入Scheduler(調度器),
-
Item Pipeline(管道):它負責處理Spider中獲取到的Item,並進行進行后期處理(詳細分析、過濾、存儲等)的地方.
-
Downloader Middlewares(下載中間件):可以當作是一個可以自定義擴展下載功能的組件。
-
Spider Middlewares(Spider中間件):可以理解為是一個可以自定擴展和操作引擎和Spider中間通信的功能組件(比如進入Spider的Responses;和從Spider出去的Requests)
網上看到某位大佬生動形象的講述了如何進行工作,易於理解:
Scrapy的運作流程
代碼寫好,程序開始運行...
-
引擎:Hi!Spider, 你要處理哪一個網站?
-
Spider:老大要我處理xxxx.com。
-
引擎:你把第一個需要處理的URL給我吧。
-
Spider:給你,第一個URL是xxxxxxx.com。
-
引擎:Hi!調度器,我這有request請求你幫我排序入隊一下。
-
調度器:好的,正在處理你等一下。
-
引擎:Hi!調度器,把你處理好的request請求給我。
-
調度器:給你,這是我處理好的request
-
引擎:Hi!下載器,你按照老大的下載中間件的設置幫我下載一下這個request請求
-
下載器:好的!給你,這是下載好的東西。(如果失敗:sorry,這個request下載失敗了。然后引擎告訴調度器,這個request下載失敗了,你記錄一下,我們待會兒再下載)
-
引擎:Hi!Spider,這是下載好的東西,並且已經按照老大的下載中間件處理過了,你自己處理一下(注意!這兒responses默認是交給def parse()這個函數處理的)
-
Spider:(處理完畢數據之后對於需要跟進的URL),Hi!引擎,我這里有兩個結果,這個是我需要跟進的URL,還有這個是我獲取到的Item數據。
-
引擎:Hi !管道 我這兒有個item你幫我處理一下!調度器!這是需要跟進URL你幫我處理下。然后從第四步開始循環,直到獲取完老大需要全部信息。
-
管道``調度器:好的,現在就做!
注意:只有當調度器沒有request需要處理時,整個程序才會停止。(對於下載失敗的URL,Scrapy也會重新下載。)
Xpath問題
有的網站明明使用了xpath helper插件寫好了xpath語句並且chrome驗證沒問題,但是偏偏到response.xpath的時候就是取不出來值,一直為None,問題出在哪了呢????
首先我們需要知道,我們在瀏覽器中看到的html代碼可能與從scrapy得到的不一樣,所以有時候就會出現在瀏覽器的xpath無法在程序中使用的問題
解決方法
- 如果是國外網站,首先注意一點就是chrome的自動翻譯功能,建議選擇顯示原始網頁,然后進行編寫
- 據某些網友說,某些情況 xpath中含有 tbody 是瀏覽器規范化額外加上去的標簽,實際的網頁源碼並沒有 所以我們需要手動刪除
終極解決方案 scrapy shell
不是說scrapy得到的跟我們在瀏覽器看到的不同嗎?好的,那我們就直接去scrapy得到的響應去看看!
打開命令行,輸入命令 scrapy shell url 如百度
D:\pythonwork>scrapy shell www.baidu.com
會發現現在我們進入了scrapy的shell之中
接下來根據提示 輸入 view(response) 發現自動開啟了瀏覽器打開了本地的一個網頁
沒錯,這個就是通過scrapy得到的response 直接對於這個網頁操作,這下就可以找到適用於我們程序的xpath了 (xpath helper需要在設置中開啟允許訪問文件網址即本地文件)
肯定有小伙伴發現某些網頁403進不去,仔細想想為什么?因為命令行直接進去的是沒有用戶代理的,一些網站需要我們自己修改user-agent,怎么辦?
我們可以在已經編寫好各種設置settings的scrapy項目目錄下使用scrapy,不然只會使用默認settings
多層Response的編寫
我們在使用Scrapy做爬蟲的時候經常碰到數據分布在多個頁面,要發去多次請求才能收集到足夠的信息,例如第一個頁面只有簡單的幾個列表信息,更多的信息在其他request的response中。
而我們一般只寫如 yield scrapy.Request(next_link,callback=self.parse) 只是最表層的一頁之后就直接回調下一次的運行了,但是如果我們的信息還沒有處理完成怎么辦?需要更多的request和response處理
該怎么操作呢?總不能再引入使用requests庫吧?
解決方案:
我在爬取某個壁紙網站的時候遇到了這個問題,首頁只得到了各個圖片的略縮圖,而原圖地址需要點開另外的html文件才能獲得
解決代碼如下:
# -*- coding: utf-8 -*- import scrapy from wallpaper.items import WallpaperItem class WallspiderSpider(scrapy.Spider): name = 'wallspider' allowed_domains = ['wall.alphacoders.com'] start_urls = ['https://wall.alphacoders.com/'] def parse(self, response): picx_list = response.xpath("//div[@class='center']//div[@class='boxgrid']/a/@href").getall() for picx in picx_list: url = 'https://wall.alphacoders.com/'+str(picx) #回調給下一層處理函數 yield scrapy.Request(url,callback=self.detail_parse) def detail_parse(self, response): pic_url = response.xpath("//*[@id='page_container']/div[4]/a/@href").get() pic_size = response.xpath("//*[@id='wallpaper_info_table']/tbody//span/span[2]/a/text()").get() pic_name = response.xpath("//*[@id='page_container']/div/a[4]/span/text()").get() wall_item = WallpaperItem() wall_item['pic_url'] = pic_url wall_item['pic_size'] = pic_size.split()[0] wall_item['pic_name'] = pic_name print(wall_item) return wall_item
同時我在csdn中看到 名為 kocor的用戶寫的解決方案則更有教學性,解決方案如下:
yield scrapy.Request(item['url'], meta={'item': item}, callback=self.detail_parse)
Scrapy 用scrapy.Request發起請求可以帶上 meta={'item': item} 把之前已收集到的信息傳遞到新請求里,在新請求里用 item = response.meta('item') 接受過來,在 item 就可以繼續添加新的收集的信息了。
多少級的請求的數據都可以收集。
spider.py文件
# -*- coding: utf-8 -*- import scrapy from Tencent.items import TencentItem class TencentSpider(scrapy.Spider): # 爬蟲名稱 name = 'tencent' # 允許爬取的域名 allowed_domains = ['www.xxx.com'] # 爬蟲基礎地址 用於爬蟲域名的拼接 base_url = 'https://www.xxx.com/' # 爬蟲入口爬取地址 start_urls = ['https://www.xxx.com/position.php'] # 爬蟲爬取頁數控制初始值 count = 1 # 爬蟲爬取頁數 10為只爬取一頁 page_end = 1 def parse(self, response): nodeList = response.xpath("//table[@class='tablelist']/tr[@class='odd'] | //table[@class='tablelist']/tr[@class='even']") for node in nodeList: item = TencentItem() item['title'] = node.xpath("./td[1]/a/text()").extract()[0] if len(node.xpath("./td[2]/text()")): item['position'] = node.xpath("./td[2]/text()").extract()[0] else: item['position'] = '' item['num'] = node.xpath("./td[3]/text()").extract()[0] item['address'] = node.xpath("./td[4]/text()").extract()[0] item['time'] = node.xpath("./td[5]/text()").extract()[0] item['url'] = self.base_url + node.xpath("./td[1]/a/@href").extract()[0] # 根據內頁地址爬取 yield scrapy.Request(item['url'], meta={'item': item}, callback=self.detail_parse) # 有下級頁面爬取 注釋掉數據返回 # yield item # 循環爬取翻頁 nextPage = response.xpath("//a[@id='next']/@href").extract()[0] # 爬取頁數控制及末頁控制 if self.count < self.page_end and nextPage != 'javascript:;': if nextPage is not None: # 爬取頁數控制值自增 self.count = self.count + 1 # 翻頁請求 yield scrapy.Request(self.base_url + nextPage, callback=self.parse) else: # 爬蟲結束 return None def detail_parse(self, response): # 接收上級已爬取的數據 item = response.meta['item'] #一級內頁數據提取 item['zhize'] = response.xpath("//*[@id='position_detail']/div/table/tr[3]/td/ul[1]").xpath('string(.)').extract()[0] item['yaoqiu'] = response.xpath("//*[@id='position_detail']/div/table/tr[4]/td/ul[1]").xpath('string(.)').extract()[0] # 二級內頁地址爬取 yield scrapy.Request(item['url'] + "&123", meta={'item': item}, callback=self.detail_parse2) # 有下級頁面爬取 注釋掉數據返回 # return item def detail_parse2(self, response): # 接收上級已爬取的數據 item = response.meta['item'] # 二級內頁數據提取 item['test'] = "111111111111111111" # 最終返回數據給爬蟲引擎 return item
他的文章地址:https://blog.csdn.net/ygc123189/article/details/79160146