【爬蟲】用Scrapy爬取知乎用戶信息
這一篇記得相當凌亂。
更一個Scrapy手冊:Scrapy入門教程 - Scrapy 0.24.6 文檔
使用Scrapy的基本步驟是:
- 創建一個Scrapy項目
- 定義提取的Item
- 編寫爬取網站的 spider 並提取 Item
- 編寫 Item Pipeline 來存儲提取到的Item(即數據)
其中,第三步為了創建一個Spider,您必須繼承 scrapy.Spider
類, 且定義以下三個屬性:
name
: 用於區別Spider。start_urls
: 包含了Spider在啟動時進行爬取的url列表, 後續的URL則從初始的URL獲取到的數據中提取。parse()
是spider的一個方法,輸入是 Response 對象,輸出是Item對象或新的Request對象。
執行流程:
1、Scrapy為Spider的 start_urls
屬性中的每個URL創建了 scrapy.Request
對象,它的回調函數默認是parse()。
2、Request對象經過調度,執行生成 scrapy.http.Response
對象並送回給spider parse()
方法。
parse()中從網頁中提取數據,會用到Selectors選擇器。
Selector有四個基本的方法:
- response.selector.xpath(): 傳入xpath表達式,返回該表達式所對應的所有節點的selector list列表 。
- response.selector.css(): 傳入CSS表達式,返回該表達式所對應的所有節點的selector list列表.
- response.selector.extract(): 序列化該節點為unicode字元串並返回list。
- response.selector.re() 根據傳入的正則表達式對數據進行提取,返回unicode字元串list列表。
注意:當返回是selector對象時,需要用extract()或者extract_one()提取,才是內容
幾乎照搬:
騰訊雲技術社區:利用Scrapy爬取所有知乎用戶詳細信息並存至MongoDB
1、首先是創建project-創建genspider
這樣就有了基本的目錄結構:
不過這時候嘗試請求知乎主頁一次,返回的狀態碼是500:
因為……Request Headers中還沒有加User-Agent,改!
2、這裡我們來看一下scrapy的源碼中,關於requests請求的生成,有兩個方法:
一個是Spider.start_requests(self),還有一個是Spider.make_requests_from_url(self, url)。
順便來分別看看二者的實現,以及關係:
可以看出,如果兩個方法都不做重載的話,是等效的。但往往我們會根據需求改寫,而且一般是寫在start_requests()。原因是:
"Spider.make_requests_from_url method is deprecated; it ""wont be called in future Scrapy releases. Please ""override Spider.start_requests method instead (see %s.%s)."也就是說,make_requests_from_url()方法只會在第一輪爬取時被調用,往後就不會再調用了。所以如果後面還想調用的話,應該寫在start_requests()裡面。
現在回到剛剛的問題來,我們要解決請求失敗的問題,猜想是因為request headers裡面沒有User-Agent而不成功,到底是不是這樣呢?
查看Request類的源碼,看到默認確實headers=None
於是,我們就重寫start_requests()方法,其中構造Request實例的時候,傳入headers參數。
重寫start_requests方法,在請求的header中加上User-Agent,再爬一次,可以正常拿到響應了!
(提醒這裡除了yield Request一句,還要記得在開頭引入class Request,原本沒有引入是因為原本這一頁沒有構造Request,另外url也記得要顯示的給,原本這些都是封裝在start_requests方法裡面的)
對了,我們要爬的是用戶的關注者,這樣才能從1個用戶-10個關注者-100個關注者……一直一直爬下去。
所以url得換成這個:
3、我們的任務是爬用戶信息,那麼既然已經可以拿到response,現在這一步就是抽取數據。
根據response的不同,這裡有兩種情況:
(1)response是用戶信息,回調parse_user(),需要做的僅僅提取響應的欄位(相對簡單)
(2)response是follows或者followers,回調parsefollows()或者parse_followers()。這時需要對每個follow或者folloer,再次發送一次請求,才可以拿到每個人的用戶信息。
首先是要有一個自己的Item對象,因為parse()方法中需要構造它的實例,用於裝在parse後的數據。
於是,首先在iterms.py構造自己的Item類,指定一個Item會包含哪些field。之後在class ZhihuSpider的parse()方法中實例化這個類。
parse()方法是start_requests()的默認回調方法,在start_requests()r請求到response後,就會回調parse()並將response作為參數傳進去,實現對response的解析。
(當然這裡是為了使方法的名稱具有更明確的含義,上述方法的名稱修改過了。)
定義UserItem後記得在zhihu.py中引入:
from zhihuuser.items import UserItem
這些欄位構成了用戶的信息,其中爬到的url_token是一個比較關鍵的欄位,會參與url構造、
然後對用戶followers和follows的解析是同樣的道理,所以總共:
這裡start_requests()中將有3個yield,對應回調的parse(方法也是3個.
然後這裡既然涉及到offset構造url了,那我們順便把url的公式構造出來,剛才一直是用固定url來的。
請求的url很長,看起來雜亂無章,其實參數可以在瀏覽器下面找到:
3個parse方法的代碼:
def parse_user(self, response): data=json.loads(response.text)#json格式反序列化 item=UserItem() for field in item.fields:#item.fields列出item的所有的屬性 if field in data.keys():#如果我們想要的欄位,確實爬到了相應的數據,就用它填充 item[field]=data.get(field) yield item def parse_follows(selfs,response): data = json.loads(response.text) # json格式反序列化 if data in data.keys():#有關注的人,才有data欄位 for follow in data.get(data): url_token=follow.get(url_token) yield Request(self.user_url.format(user=url_token, include=self.user_query), callback=self.parse_user) if paging in data.keys() and data.get(paging).get(is_end)==False: yield Request(data.get(paging).get(next),callback=self.parse_follows) def parse_followers(self,response): data = json.loads(response.text) # json格式反序列化 if data in data.keys():#有關注者,才有data欄位 for follower in data.get(data): url_token=follower.get(url_token) yield Request(self.user_url.format(user=url_token, include=self.user_query), callback=self.parse_user) if paging in data.keys() and data.get(paging).get(is_end)==False: yield Request(data.get(paging).get(next),callback=self.parse_followers)
4、至此,數據的抽取已經完成了,終端運行spider可以看到源源不斷的數據正在產生……
最後一步是保存到MongDB,這裡需要用到Pipeline這個組件。
首先是自定義一個Pipiline對象,用於存庫,這裡我們命名為MongoPipeline,其中最關鍵的是實現process.item()方法,真正的寫入數據就在這裡。
同時還實現了其他幾個方法,用於在spider啟動的時候連接到db,做好準備,以及spider關閉的時候連接也一起關閉。
最後要記得在settings.py中加入MongoPipeline,它才能生效。
命令時有敲錯,可以在scrapy shell下檢查:
scrapy check
原因是,忘記把這兩個配置信息寫進去了……加上再scrapy check就好了
5、最後,讓程序運行起來……
我們用Robomongo這個工具連接到MongoDB看看效果。發現一個問題:
爬了很久,庫里始終只有50個人!主要原因還是程序里有錯誤:
返回了200,但沒有parse到數據。檢查發現中途有報錯……但是看不出來錯在哪裡啊。。
另外一個影響用戶數量的原因是parse_user(0方法除了解析完自己yield item以外,還要跟著yield2個Request對象,用於請求它的follows和followers。
否則,程序首先從1個用戶爬到了他的關注者和關注的人,然後對這些人進行一次請求,拿到他們的詳細信息,也就是都來到了parse_user()這是因為運行了,這一步完就結束了,不會有然後了。
而實際上,我們還應該再對這些人的關注者和關注的人進行爬取,這樣源源不斷地爬到所有用戶……所以加上……
yield Request(self.follows_url.format(user=self.start_user, include=self.follows_query, limit=20, offset=0), self.parse_follows) yield Request(self.followers_url.format(user=self.start_user, include=self.followers_query, limit=20, offset=0), self.parse_followers)
======待續
推薦閱讀:
※【Python爬蟲實戰】為啥學Python,BOSS告訴你
※Python 的函數是怎麼傳遞參數的?
※用 Python 玩轉 Facebook 數據
※左手用R右手Python系列——多進程/線程數據抓取與網頁請求
※Python爬蟲聯想詞視頻和代碼
TAG:Python |