背景
使用Scrapy分布式爬取知乎所有用戶個(gè)人信息!
項(xiàng)目地址 爬取知乎所有用戶
大規(guī)模抓取靜態(tài)網(wǎng)頁(yè)Scrapy絕對(duì)是利器!當(dāng)然也可以使用requests庫(kù)來(lái)自己實(shí)現(xiàn),但是要自己寫(xiě)過(guò)濾器等組件,既然有現(xiàn)成的輪子并且還是很好的輪子就沒(méi)必要再造一個(gè)了!
使用Scrapy時(shí)單一進(jìn)程抓取的時(shí)候速度太慢,可以使用多進(jìn)程,但是如果覺(jué)得還是慢,那么我們可以使用基于redis的分布式抓取,幾臺(tái)電腦或則服務(wù)器同時(shí)抓取來(lái)提升抓取效率,詳情看下一篇!
文章結(jié)構(gòu)
- Scrapy 簡(jiǎn)介
- Scrapy 是怎么工作的
- Scrapy 實(shí)例
- 遇到的問(wèn)題
Scrapy 簡(jiǎn)介
An open source and collaborative framework for extracting the data you need from websites. In a fast, simple, yet extensible way這是官方對(duì)scrapy的描述. 從這里我們可以看到幾個(gè)關(guān)鍵詞 fast , simple , extensible 這也是我們使用scrapy的幾個(gè)需求出發(fā)點(diǎn).我們啟動(dòng)好了項(xiàng)目后建了APP后只需要簡(jiǎn)單的更改和設(shè)置就能完成一個(gè)網(wǎng)站的爬取,對(duì)于新手也是非常友好的!這使得開(kāi)發(fā)更加快速便捷.整個(gè)過(guò)程中我們只要專(zhuān)注于如何提取數(shù)據(jù)就好了!Scrapy框架使用了異步的模式,可以加快我們的下載速度,并且內(nèi)置了去重的過(guò)濾器,這也簡(jiǎn)化了我們的開(kāi)發(fā)過(guò)程. 我們這里提供了部署工具Scrapyd這讓我們?cè)诓渴鸬臅r(shí)候更方便.
詳細(xì)信息可以移步官方網(wǎng)站Scrapy,文檔在這里中文官方文檔, 英文官方文檔
說(shuō)了那么多,其實(shí)就想表達(dá)一件事:使用Scrapy,網(wǎng)絡(luò)爬蟲(chóng)不再困難! 既然Scrapy有那么多好處,那么我們先來(lái)了解下它的原理吧
Scrapy 是怎么工作的
引用網(wǎng)上的一張圖來(lái)說(shuō)明整個(gè)工作原理:
可以看到整個(gè)Scrapy 有幾個(gè)組件:
Spider(蜘蛛),Scheduler(調(diào)度器),Downloader(下載器),Item Pipeline(管道),Engine(引擎),Downloader Middlewares(下載中間件),Spider Middlewares(蜘蛛中間件).我們引用網(wǎng)上的一個(gè)小故事來(lái)說(shuō)明各個(gè)組件之間的關(guān)系:
代碼寫(xiě)好桨啃,程序開(kāi)始運(yùn)行...
引擎:Hi呢蔫!Spider, 你要處理哪一個(gè)網(wǎng)站?
Spider:老大要我處理xxxx.com峡迷。
引擎:你把第一個(gè)需要處理的URL給我吧。
Spider:給你硬贯,第一個(gè)URL是xxxxxxx.com岛马。
引擎:Hi缀辩!調(diào)度器,我這有request請(qǐng)求你幫我排序入隊(duì)一下(這里的是request對(duì)象,中間包含了url,下載工作是交給下載器來(lái)處理的)弱睦。
調(diào)度器:好的百姓,正在處理你等一下。
引擎:Hi况木!調(diào)度器垒拢,把你處理好的request請(qǐng)求給我。
調(diào)度器:給你火惊,這是我處理好的request
引擎:Hi求类!下載器,你按照老大的下載中間件的設(shè)置幫我下載一下這個(gè)request請(qǐng)求
下載器:好的屹耐!給你尸疆,這是下載好的東西。(如果失敗:sorry寿弱,這個(gè)request下載失敗了犯眠。然后引擎告訴調(diào)度器,這個(gè)request下載失敗了症革,你記錄一下筐咧,我們待會(huì)兒再下載)
引擎:Hi!Spider地沮,這是下載好的東西嗜浮,并且已經(jīng)按照老大的下載中間件處理過(guò)了,你自己處理一下(注意摩疑!這兒responses默認(rèn)是交給def parse()這個(gè)函數(shù)處理的)
Spider:(處理完畢數(shù)據(jù)之后對(duì)于需要跟進(jìn)的URL)危融,Hi!引擎雷袋,我這里有兩個(gè)結(jié)果吉殃,這個(gè)是我需要跟進(jìn)的URL,還有這個(gè)是我獲取到的Item數(shù)據(jù)楷怒。
引擎:Hi 蛋勺!管道 我這兒有個(gè)item你幫我處理一下!調(diào)度器鸠删!這是需要跟進(jìn)URL你幫我處理下抱完。然后從第四步開(kāi)始循環(huán),直到獲取完老大需要全部信息刃泡。
管道調(diào)度器:好的巧娱,現(xiàn)在就做!
注意烘贴!只有當(dāng)調(diào)度器中不存在任何request了禁添,整個(gè)程序才會(huì)停止,(也就是說(shuō)桨踪,對(duì)于下載失敗的URL老翘,Scrapy也會(huì)重新下載。)
通過(guò)這個(gè)小故事可以清楚的知道--spider獲取連接交給調(diào)度器,調(diào)度器來(lái)去重后將下載連接交給下載器,下載器下載好了東西交給蜘蛛,然后需要的內(nèi)容就交給管道,如果還有連接就再次交給調(diào)度器...
總結(jié)一下:
- 引擎(Scrapy): 用來(lái)處理整個(gè)系統(tǒng)的數(shù)據(jù)流處理, 觸發(fā)事務(wù)(框架核心)
- 調(diào)度器(Scheduler): 用來(lái)接受引擎發(fā)過(guò)來(lái)的請(qǐng)求, 壓入隊(duì)列中, 并在引擎再次請(qǐng)求的時(shí)候返回. 可以想像成一個(gè)URL(抓取網(wǎng)頁(yè)的網(wǎng)址或者說(shuō)是鏈接)的優(yōu)先隊(duì)列, 由它來(lái)決定下一個(gè)要抓取的網(wǎng)址是什么, 同時(shí)去除重復(fù)的網(wǎng)址
- 下載器(Downloader): 用于下載網(wǎng)頁(yè)內(nèi)容, 并將網(wǎng)頁(yè)內(nèi)容返回給蜘蛛(Scrapy下載器是建立在twisted這個(gè)高效的異步模型上的)
- 爬蟲(chóng)(Spiders): 爬蟲(chóng)是主要干活的, 用于從特定的網(wǎng)頁(yè)中提取自己需要的信息, 即所謂的實(shí)體(Item)锻离。用戶也可以從中提取出鏈接,讓Scrapy繼續(xù)抓取下一個(gè)頁(yè)面
- 項(xiàng)目管道(Pipeline): 負(fù)責(zé)處理爬蟲(chóng)從網(wǎng)頁(yè)中抽取的實(shí)體铺峭,主要的功能是持久化實(shí)體、驗(yàn)證實(shí)體的有效性纳账、清除不需要的信息逛薇。當(dāng)頁(yè)面被爬蟲(chóng)解析后,將被發(fā)送到項(xiàng)目管道疏虫,并經(jīng)過(guò)幾個(gè)特定的次序處理數(shù)據(jù)永罚。
- 下載器中間件(Downloader Middlewares): 位于Scrapy引擎和下載器之間的框架啤呼,主要是處理Scrapy引擎與下載器之間的請(qǐng)求及響應(yīng)。
- 爬蟲(chóng)中間件(Spider Middlewares): 介于Scrapy引擎和爬蟲(chóng)之間的框架呢袱,主要工作是處理蜘蛛的響應(yīng)輸入和請(qǐng)求輸出官扣。
- 調(diào)度中間件(Scheduler Middewares): 介于Scrapy引擎和調(diào)度之間的中間件,從Scrapy引擎發(fā)送到調(diào)度的請(qǐng)求和響應(yīng)羞福。
理解了整個(gè)工作原理我們開(kāi)始動(dòng)手! 還是那句話:如果你想了解一個(gè)框架,Just Do It ! 這里我們使用一個(gè)爬取知乎所有用戶的爬蟲(chóng)來(lái)演示!
Scrapy 實(shí)例
思路分析:
拋開(kāi)框架我們來(lái)分析下我們爬取的原理: 我們從一個(gè)關(guān)注的人開(kāi)始,獲取這個(gè)關(guān)注的人的信息并儲(chǔ)存下來(lái),然后獲取這個(gè)關(guān)注的人的的關(guān)注的人和粉絲,再去獲取關(guān)注人的人的信息并存儲(chǔ)循環(huán)往復(fù)下去就實(shí)現(xiàn)了從一個(gè)人開(kāi)始層層抓取下去.來(lái)張圖-借鑒傳銷(xiāo)圖:
當(dāng)然如果一個(gè)人沒(méi)有關(guān)注人沒(méi)有粉絲那就算了,放過(guò)他們!我們可以利用遞歸的思想來(lái)實(shí)現(xiàn)這個(gè)思路
環(huán)境配置:
推薦使用虛擬環(huán)境來(lái)創(chuàng)建環(huán)境 虛擬環(huán)境教程在這里
python3, Scrapy, mongodb, pymongo, redis, python_redis, Scrapyd, scrapyd-client
安裝過(guò)程中可能出現(xiàn)報(bào)錯(cuò),我把我安裝過(guò)程中報(bào)錯(cuò)的信息以及解決方法寫(xiě)到了文章的結(jié)尾
1.安裝Scrapy系列以及連接數(shù)據(jù)庫(kù)的包
$ pip install Scrapy Scrapyd scrapyd-client pymongo redis python_redis
如果你沒(méi)有安裝pip 請(qǐng)按照下面的方法來(lái)安裝:
sudo python get-pip.py install
2.安裝mongodb:
這里使用官方的教程:官方教程
選擇自己的系統(tǒng)查看教程進(jìn)行選擇教程安裝
創(chuàng)建項(xiàng)目
以上環(huán)境安裝好里以后,在終端下輸入:
$ scrapy startproject zhihuScrapy
創(chuàng)建爬蟲(chóng)
scrapy genspider zhihu www.zhihu.com
第三個(gè)參數(shù)為爬蟲(chóng)名字,第四個(gè)為爬取的范圍
這是整個(gè)項(xiàng)目的結(jié)構(gòu)
image
在pycharm中打開(kāi)項(xiàng)目(選擇你喜歡的編輯器,這里我們使用pycharm)
改寫(xiě)項(xiàng)目文件
手動(dòng)翻譯設(shè)置文件
# -*- coding: utf-8 -*-
#
# Scrapy settings for zhihuScrapy project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# http://doc.scrapy.org/en/latest/topics/settings.html
# http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
# http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
# 項(xiàng)目名稱
BOT_NAME = 'zhihuScrapy'
# Scrapy搜索spider的模塊列表
SPIDER_MODULES = ['zhihuScrapy.spiders']
# 默認(rèn)使用 genspider 命令創(chuàng)建新spider的模塊
NEWSPIDER_MODULE = 'zhihuScrapy.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
# 爬取的默認(rèn)User-Agent 可以被覆蓋
# USER_AGENT = 'zhihuScrapy (+http://www.yourdomain.com)'
# Obey robots.txt rules
# 如果啟用惕蹄,Scrapy將遵守robots.txt策略
ROBOTSTXT_OBEY = True
# Configure maximum concurrent requests performed by Scrapy (default: 16)
# Scrapy downloader 并發(fā)請(qǐng)求(concurrent requests)的最大值
# CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
# 下載器在下載同一個(gè)網(wǎng)站下一個(gè)頁(yè)面前需要等待的時(shí)間
# DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
# 對(duì)單個(gè)網(wǎng)站進(jìn)行并發(fā)請(qǐng)求的最大值
# CONCURRENT_REQUESTS_PER_DOMAIN = 16
# 對(duì)單個(gè)IP進(jìn)行并發(fā)請(qǐng)求的最大值。如果非0治专,則忽略
# CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
# 是否啟用cookie
# COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
# 表明 telnet 終端 (及其中間件)是否啟用的布爾值
# TELNETCONSOLE_ENABLED = False
# Override the default request headers:
# 默認(rèn)request請(qǐng)求頭信息
# DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
# }
# Enable or disable spider middlewares
# 啟用或者不啟用spider中間件
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
# Spider 中間件
# SPIDER_MIDDLEWARES = {
# 'zhihuScrapy.middlewares.ZhihuscrapySpiderMiddleware': 543,
# }
# Enable or disable downloader middlewares
# 啟用或者不啟用下載中間件
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
# DOWNLOADER_MIDDLEWARES = {
# 'zhihuScrapy.middlewares.MyCustomDownloaderMiddleware': 543,
# }
# Enable or disable extensions
# 保存項(xiàng)目中啟用的中間件及其順序的字典卖陵。
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
# EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
# }
# Configure item pipelines
# 項(xiàng)目管道配置
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
# ITEM_PIPELINES = {
# 'zhihuScrapy.pipelines.ZhihuscrapyPipeline': 300,
# }
# Enable and configure the AutoThrottle extension (disabled by default)
# 啟用AutoThrottle配置列表
# See http://doc.scrapy.org/en/latest/topics/autothrottle.html
# 啟用AutoThrottle擴(kuò)展。
# AUTOTHROTTLE_ENABLED = True
# The initial download delay
# 初始下載延遲
# AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
# 起用AutoThrottle調(diào)試(debug)模式张峰,展示每個(gè)接收到的response
# AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
# AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
# 起用AutoThrottle調(diào)試(debug)模式泪蔫,展示每個(gè)接收到的response
# AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# 啟用HTTP配置緩存
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
# 啟用緩存
# HTTPCACHE_ENABLED = True
# 緩存的request的超時(shí)時(shí)間,單位秒喘批。
# HTTPCACHE_EXPIRATION_SECS = 0
# 存儲(chǔ)(底層的)HTTP緩存的目錄
# HTTPCACHE_DIR = 'httpcache'
# 不緩存設(shè)置中的HTTP返回值(code)的request
# HTTPCACHE_IGNORE_HTTP_CODES = []
# 實(shí)現(xiàn)緩存存儲(chǔ)后端的類(lèi)
# HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
我們需要改寫(xiě)的幾個(gè)地方列出來(lái)大家可以去上面代碼中Ctrl+f搜索
ROBOTSTXT_OBEY = False # 這個(gè)不禁用,遵守協(xié)議還怎么爬,人家默認(rèn)不讓你爬啊
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
}
# 這里填寫(xiě)你請(qǐng)求頭,否則爬取報(bào)錯(cuò)
改好了讓我們啟用一下爬蟲(chóng):
$ scrapy crawl zhihu
狀態(tài)碼為200!這說(shuō)明我們的爬蟲(chóng)可以啟用成功了, 下面就開(kāi)始寫(xiě)爬蟲(chóng)的spider吧!
編寫(xiě)spider
首先我們?nèi)シ治鲞@個(gè)知乎的用戶信息:首先我們找個(gè)用戶(這里我們使用帶三個(gè)表這個(gè)人的首頁(yè))先去看下知乎的network并刷新,查看返回的首頁(yè)(answers)file,發(fā)現(xiàn)并沒(méi)有我們想要的內(nèi)容,那我們就開(kāi)始一個(gè)一個(gè)network里的信息了,通常情況下是使用XHR這個(gè)來(lái)查看能獲得意外驚喜,因?yàn)楹芏嗑W(wǎng)站使用json來(lái)傳遞信息!當(dāng)然這樣也為我們獲取數(shù)據(jù)提供了便捷,我們只要找到這個(gè)api就行了!經(jīng)過(guò)一番查找我們得到了幾個(gè)請(qǐng)求有用:
個(gè)人信息:
通過(guò)這個(gè)圖我們就要定義要獲取的個(gè)人用戶的信息喲那些了:定義這些都是在item.py中實(shí)現(xiàn)的:
from scrapy import Item
from scrapy import Field # 這里可以直接導(dǎo)入方法節(jié)省力氣
class UserItem(Item):
''' 定義需要保存的字段有哪些'''
account_status = Field()
allow_message = Field()
answer_count = Field()
articles_count = Field()
avatar_hue = Field()
avatar_url = Field()
avatar_url_template = Field()
badge = Field()
business = Field()
columns_count = Field()
commercial_question_count = Field()
cover_url = Field()
description = Field()
educations = Field()
employments = Field()
favorite_count = Field()
favorited_count = Field()
follower_count = Field()
following_columns_count = Field()
following_count = Field()
following_favlists_count = Field()
following_question_count = Field()
following_topic_count = Field()
gender = Field()
headline = Field()
hosted_live_count = Field()
id = Field()
is_active = Field()
is_advertiser = Field()
is_bind_sina = Field()
is_blocked = Field()
is_blocking = Field()
is_followed = Field()
is_following = Field()
is_force_renamed = Field()
is_org = Field()
is_privacy_protected = Field()
locations = Field()
logs_count = Field()
marked_answers_count = Field()
marked_answers_text = Field()
message_thread_token = Field()
mutual_followees_count = Field()
name = Field()
participated_live_count = Field()
pins_count = Field()
question_count = Field()
show_sina_weibo = Field()
thank_from_count = Field()
thank_to_count = Field()
thanked_count = Field()
type = Field()
url = Field()
url_token = Field()
user_type = Field()
vote_from_count = Field()
vote_to_count = Field()
voteup_count = Field()
關(guān)注人列表:
我們?cè)倏搓P(guān)注的人的列表里返回的內(nèi)容有什么信息:
這里我們要特別關(guān)注一下這個(gè) url_token 因?yàn)檫@個(gè)是我們獲取下一個(gè)人的接口!
列表有下一頁(yè)怎么辦,別著急看這請(qǐng)求的返回結(jié)果這里:
我們翻到最后一頁(yè):
發(fā)現(xiàn)沒(méi)有:如果有下一頁(yè)返回的結(jié)果中會(huì)返回一個(gè) 'next' 并且 is_end 為false!
通過(guò)上面的分析我們大致有這樣一個(gè)思路,我們看下用戶詳情接口在哪里撩荣,我們將鼠標(biāo)放到關(guān)注列表任意一個(gè)頭像上面,觀察下網(wǎng)絡(luò)請(qǐng)求饶深,可以發(fā)現(xiàn)又會(huì)出現(xiàn)一個(gè)Ajax請(qǐng)求:
https://www.zhihu.com/api/v4/members/wangxiaofeng?include=....
這個(gè)網(wǎng)址,只需要將members/ 后的名字換為我們要訪問(wèn)的人的url_token就好了至于后面的include本來(lái)是什么就是什么,貌似不影響,關(guān)于個(gè)人信息的獲取我們就到這,那么這個(gè)url_token怎么獲得呢?查看他的粉絲列表信息里是不是有這個(gè)信息,我們只需要得到?jīng)]個(gè)粉絲的url_token然后穿進(jìn)去就可以得到這個(gè)粉絲的個(gè)人信息啦. 然后就是關(guān)于翻頁(yè)的,我們?cè)讷@取完這個(gè)人的當(dāng)前粉絲列表后去判斷一下返回的 paging 里面的is_end 是否為true 就好啦,如果為true 那就不要再翻頁(yè)了!
那么我們?cè)趺传@取另外一個(gè)人的粉絲列表呢看這個(gè)接口
https://www.zhihu.com/api/v4/members/wangxiaofeng/publications?include...
我們只要把 members/ 后面的人名換成url_token里的人名就可以獲得這個(gè)人的粉絲列表啦!到這里整個(gè)分析就結(jié)束了,是不是很簡(jiǎn)單. 那么我們?cè)趺磳?shí)現(xiàn)整個(gè)知乎所有用戶的爬取呢?我畫(huà)了一個(gè)簡(jiǎn)圖,不好看,湊合著看吧:
整個(gè)流程使用了 遞歸的思想 來(lái)實(shí)現(xiàn),為了方便查看我們將流程拆分為兩個(gè)方法,用Python當(dāng)然是面向?qū)ο驝lass來(lái)寫(xiě),下面就上spider.py的代碼:
# -*- coding: utf-8 -*-
from scrapy import Spider, Request
import json
from zhihu.items import UserItem
class ZhihuSpider(Spider):
name = 'zhihu'
allowed_domains = ['www.zhihu.com'] # 定義爬蟲(chóng)能爬取的范圍
start_urls = ['http://www.zhihu.com/'] # 開(kāi)始的url
start_user = 'hypnova' # 這是我們傳進(jìn)去的第一個(gè)人,我們將從他開(kāi)始獲取他的粉絲,然后獲取他粉絲的粉絲,然后獲取他粉絲的粉絲的粉絲,然后.....
# 個(gè)人信息接口
user_info_url = 'https://www.zhihu.com/api/v4/members/{user}?include={include}' # 使用.format方法來(lái)動(dòng)態(tài)獲取每個(gè)用戶的信息
# include 內(nèi)容單獨(dú)取出來(lái)
user_query = 'locations,employments,gender,educations,business,voteup_count,thanked_Count,follower_count,following_count,cover_url,following_topic_count,following_question_count,following_favlists_count,following_columns_count,avatar_hue,answer_count,articles_count,pins_count,question_count,columns_count,commercial_question_count,favorite_count,favorited_count,logs_count,marked_answers_count,marked_answers_text,message_thread_token,account_status,is_active,is_bind_phone,is_force_renamed,is_bind_sina,is_privacy_protected,sina_weibo_url,sina_weibo_name,show_sina_weibo,is_blocking,is_blocked,is_following,is_followed,mutual_followees_count,vote_to_count,vote_from_count,thank_to_count,thank_from_count,thanked_count,description,hosted_live_count,participated_live_count,allow_message,industry_category,org_name,org_homepage,badge[?(type=best_answerer)].topics'
# 用戶關(guān)注信息接口
follower_url = 'https://www.zhihu.com/api/v4/members/{user}/followees?include={include}&offset={offset}&limit={limit}'
# include 內(nèi)容單獨(dú)取出來(lái)
follower_query = 'data[*].is_normal,admin_closed_comment,reward_info,is_collapsed,annotation_action,annotation_detail,collapse_reason,collapsed_by,suggest_edit,comment_count,can_comment,content,voteup_count,reshipment_settings,comment_permission,mark_infos,created_time,updated_time,review_info,relationship.is_authorized,voting,is_author,is_thanked,is_nothelp,upvoted_followees;data[*].author.badge[?(type=best_answerer)].topics'
# 關(guān)注用戶的人接口
followee_url = 'https://www.zhihu.com/api/v4/members/{user}/followees?include={include}&offset={offset}&limit={limit}'
# include 內(nèi)容單獨(dú)取出來(lái)
followee_query = 'data[*].answer_count,articles_count,gender,follower_count,is_followed,is_following,badge[?(type=best_answerer)].topics'
def start_requests(self):
'''這個(gè)方法用來(lái)獲取啟動(dòng)各個(gè)方法'''
yield Request(self.user_info_url.format(user=self.start_user, include=self.user_query),callback=self.user_info_parse)
yield Request(self.follower_url.format(user=self.start_user, include=self.follower_query, offset=0, limit=20),callback=self.follower_info_parse)
yield Request(self.followee_url.format(user=self.start_user, include=self.followee_query, offset=0, limit=20),callback=self.followee_info_parse)
def user_info_parse(self, response):
'''用來(lái)獲取用戶個(gè)人信息的方法,并將這個(gè)人的url_token傳遞給獲取用戶粉絲和關(guān)注列表的函數(shù)以獲得這個(gè)人的粉絲和關(guān)注列表'''
# 將獲取到的Python對(duì)象轉(zhuǎn)換為json對(duì)象
result = json.loads(response.text)
# 實(shí)例化一個(gè)item用來(lái)傳遞信息
item = UserItem()
# 這個(gè)方法很有用可以快速取得自己要的內(nèi)容(json返回),然后在使用判斷進(jìn)行快速賦值
for field in item.fields:
# 保證取到了我們定義好的數(shù)據(jù)而沒(méi)有定義的數(shù)據(jù)不會(huì)出現(xiàn)
if field in result.keys():
# 依次給item賦值
item[field] = result.get(field)
# 返回給item
yield item
# 將url_token傳遞給獲取用戶粉絲列表的函數(shù)
yield Request(
self.follower_url.format(user=result['url_token'], include=self.follower_query, offset=0, limit=20),
callback=self.follower_info_parse)
# 將url_token傳遞給獲取用戶關(guān)注列表的函數(shù)
yield Request(
self.followee_url.format(user=result['url_token'], include=self.followee_query, offset=0, limit=20),
callback=self.followee_info_parse)
def follower_info_parse(self, response):
'''當(dāng)我們得到了用戶的關(guān)注者后,將這些關(guān)注者再次調(diào)用這個(gè)方法,繼續(xù)得到關(guān)注者, 這里采用了遞歸的思想'''
# 將Python對(duì)象轉(zhuǎn)換為json對(duì)象
result = json.loads(response.text)
# 判斷返回的數(shù)據(jù)中是否有data如果有就獲取這個(gè)人的url,如果沒(méi)有就去判斷是否有下一頁(yè)
if 'data' in result.keys():
# 循環(huán)遍歷data中的每個(gè)人,然后獲取他的url_token傳給user_info_parse函數(shù)處理
for user in result.get('data'):
# 傳遞url_token給個(gè)人信息處理函數(shù)進(jìn)行處理
yield Request(self.user_info_url.format(user=user.get('url_token'), include=self.user_query),
callback=self.user_info_parse)
# 判斷是否有下一頁(yè)
if 'paging' in result.keys() and result.get('paging').get('is_end') == False:
'''這里判斷用戶的列表有么有下一頁(yè),這個(gè)功能在每次取完本頁(yè)后調(diào)用,沒(méi)有就結(jié)束,有就將下一頁(yè)的網(wǎng)址傳給自己繼續(xù)獲得永不'''
next_url = result.get('paging').get('next')
# 有下一頁(yè)就調(diào)用自己將下一頁(yè)的信息繼續(xù)獲取
yield Request(next_url, callback=self.follower_info_parse)
def followee_info_parse(self, response):
'''這里同上面的分析'''
result = json.loads(response.text)
if 'data' in result.keys():
for user in result.get('data'):
yield Request(self.user_info_url.format(user=user.get('url_token'), include=self.followee_query),
callback=self.user_info_parse)
if 'paging' in result.keys() and result.get('paging').get('is_end') == False:
next_url = result.get('paging').get('next')
yield Request(next_url, callback=self.followee_info_parse)
只是爬下來(lái)為了以后做數(shù)據(jù)分析,我們要存起來(lái)來(lái),這里我使用了mongodb這個(gè)非關(guān)系型數(shù)據(jù)庫(kù)來(lái)存儲(chǔ),而這些存儲(chǔ)的過(guò)程都是在piplines.py中完成的,并且在scrapy中為我們提供了多中接口供我們使用,這里我們直接使用他的pymongo接口官方文檔舉例:
# In this example we’ll write items to MongoDB using pymongo. MongoDB address and database name are specified in Scrapy settings; MongoDB collection is named after item class.
# The main point of this example is to show how to use from_crawler() method and how to clean up the resources properly.:
import pymongo
class MongoPipeline(object):
collection_name = 'scrapy_items'
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db[self.collection_name].insert_one(dict(item))
return item
還支持
- Write items to a JSON file
- Write items to MongoDB
更多方法請(qǐng)看這里,所以我們可以改寫(xiě)這個(gè)例子然后在設(shè)置中設(shè)置數(shù)據(jù)庫(kù):
import pymongo
class MongoPipeline(object):
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri # 這里可以在setting中指定數(shù)據(jù)庫(kù)和集合
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db['user'].update({'url_token': item['url_token']}, {'$set': item}, True)
# 這里是mongod 的更新操作,如果查詢到了那么就使用第一個(gè)參數(shù)為查詢條件,第二個(gè)通過(guò)$set指定更新的條件,第三個(gè)參數(shù)表示如果存在則更新如果不存在則插入
return item
然后在setting.py中寫(xiě)入數(shù)據(jù)庫(kù)的設(shè)置:
MONGO_URI = 'localhost' # 這里設(shè)置本地?cái)?shù)據(jù)庫(kù)
MONGO_DATABASE = 'zhihu' # 這里指定數(shù)據(jù)庫(kù)的名字,如果不存在就會(huì)自動(dòng)創(chuàng)建
另外在setting.py中記得開(kāi)啟一下Item Pileline
ITEM_PIPELINES = {
'zhihu.pipelines.MongoPipeline': 300,
}
然后重新運(yùn)行爬蟲(chóng)查看數(shù)據(jù)庫(kù)就可以看到這里有數(shù)據(jù)存進(jìn)來(lái)了!
到這里我們單一的爬蟲(chóng)就結(jié)束了,當(dāng)然你會(huì)發(fā)現(xiàn)雖然很快但是如果對(duì)于百萬(wàn)或者千萬(wàn)級(jí)別的數(shù)據(jù)開(kāi)說(shuō)還是太慢了,多進(jìn)程可以解決這個(gè)問(wèn)題,但是又會(huì)有另外一個(gè)問(wèn)題,那就是數(shù)據(jù)重復(fù)的問(wèn)題.下篇我們就來(lái)說(shuō)說(shuō)分布式的好處!