scrapy實(shí)戰(zhàn)--爬取知乎用戶信息(上)

背景

使用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è)了!

image

使用Scrapy時(shí)單一進(jìn)程抓取的時(shí)候速度太慢,可以使用多進(jìn)程,但是如果覺(jué)得還是慢,那么我們可以使用基于redis的分布式抓取,幾臺(tái)電腦或則服務(wù)器同時(shí)抓取來(lái)提升抓取效率,詳情看下一篇!

文章結(jié)構(gòu)

  1. Scrapy 簡(jiǎn)介
  2. Scrapy 是怎么工作的
  3. Scrapy 實(shí)例
  4. 遇到的問(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è)工作原理:

scrapy

可以看到整個(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)安裝:

wget https://bootstrap.pypa.io/get-pip.py ... sudo python get-pip.py install

2.安裝mongodb:
這里使用官方的教程:官方教程

選擇自己的系統(tǒng)查看教程進(jìn)行選擇教程安裝

image

創(chuàng)建項(xiàng)目

以上環(huán)境安裝好里以后,在終端下輸入:

$ scrapy startproject zhihuScrapy

image

創(chuàng)建爬蟲(chóng)

cd zhihuScrapy scrapy genspider zhihu www.zhihu.com
第三個(gè)參數(shù)為爬蟲(chóng)名字,第四個(gè)為爬取的范圍

image

這是整個(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

image

狀態(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è)人信息:

個(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)注人列表:

關(guān)注人列表

我們?cè)倏搓P(guān)注的人的列表里返回的內(nèi)容有什么信息:

image

這里我們要特別關(guān)注一下這個(gè) url_token 因?yàn)檫@個(gè)是我們獲取下一個(gè)人的接口!

列表有下一頁(yè)怎么辦,別著急看這請(qǐng)求的返回結(jié)果這里:

判斷是否有下一頁(yè)

我們翻到最后一頁(yè):

最后一頁(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)求:

image

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)了!

image

image

到這里我們單一的爬蟲(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ō)分布式的好處!

image

遇到的問(wèn)題以及解決方案

  • 安裝scrapy報(bào)錯(cuò)' error: command 'x86_64-linux-gnu-gcc' failed with exit status 1 ' 這里 或者這里
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末餐曹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子敌厘,更是在濱河造成了極大的恐慌台猴,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俱两,死亡現(xiàn)場(chǎng)離奇詭異卿吐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)锋华,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)箭窜,“玉大人毯焕,你說(shuō)我怎么就攤上這事』怯#” “怎么了纳猫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)竹捉。 經(jīng)常有香客問(wèn)我芜辕,道長(zhǎng),這世上最難降的妖魔是什么块差? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任侵续,我火速辦了婚禮倔丈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘状蜗。我一直安慰自己需五,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布轧坎。 她就那樣靜靜地躺著宏邮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缸血。 梳的紋絲不亂的頭發(fā)上蜜氨,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音捎泻,去河邊找鬼飒炎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛族扰,可吹牛的內(nèi)容都是我干的厌丑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼渔呵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼怒竿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起扩氢,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤耕驰,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后录豺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體朦肘,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年双饥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了媒抠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咏花,死狀恐怖趴生,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昏翰,我是刑警寧澤苍匆,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站棚菊,受9級(jí)特大地震影響浸踩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜统求,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一检碗、第九天 我趴在偏房一處隱蔽的房頂上張望据块。 院中可真熱鬧,春花似錦后裸、人聲如沸瑰钮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浪谴。三九已至,卻和暖如春因苹,著一層夾襖步出監(jiān)牢的瞬間苟耻,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工扶檐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凶杖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓款筑,卻偏偏與公主長(zhǎng)得像智蝠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奈梳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容