利用 scrapy爬知乎用戶關系網以及下載頭像

說起Python,我們或許自然而然的想到其在爬蟲方面的重大貢獻。Python的流行在于其語言的優美以及良好的氛圍。相對于Java,js等語言來說,Python API在封裝上面要好很多。今天我們要說的是Python的一個通用的開源爬蟲框架 scrapy。
scrapy在爬蟲界可謂是鼎鼎大名。其內部寫好的各種組件使用起來可謂是順風順水。各組件的功能我不在此一一列舉了,下面教程中會簡略提到。整體架構如下圖所示:

框架簡介:

引用自 http://blog.csdn.net/zbyufei/article/details/7554322 已經了解scrapy可以直接略過,也可以當復習使用

一、綠線是數據流向,首先從初始 URL 開始,Scheduler 會將其交給 Downloader 進行下載,下載之后會交給 Spider 進行分析,Spider 分析出來的結果有兩種:一種是需要進一步抓取的鏈接,例如之前分析的“下一頁”的鏈接,這些東西會被傳回 Scheduler ;另一種是需要保存的數據,它們則被送到 Item Pipeline 那里,那是對數據進行后期處理(詳細分析、過濾、存儲等)的地方。另外,在數據流動的通道里還可以安裝各種中間件,進行必要的處理。
二、組件
1、Scrapy Engine(Scrapy引擎)
Scrapy引擎是用來控制整個系統的數據處理流程,并進行事務處理的觸發。更多的詳細內容可以看下面的數據處理流程。
2、Scheduler(調度)
調度程序從Scrapy引擎接受請求并排序列入隊列,并在Scrapy引擎發出請求后返還給他們。
3、Downloader(下載器)
下載器的主要職責是抓取網頁并將網頁內容返還給蜘蛛( Spiders)。
4、Spiders(蜘蛛)
蜘蛛是有Scrapy用戶自己定義用來解析網頁并抓取制定URL返回的內容的類,每個蜘蛛都能處理一個域名或一組域名。換句話說就是用來定義特定網站的抓取和解析規則。
蜘蛛的整個抓取流程(周期)是這樣的:
首先獲取第一個URL的初始請求,當請求返回后調取一個回調函數。第一個請求是通過調用start_requests()方法。該方法默認從start_urls中的Url中生成請求,并執行解析來調用回調函數。
在回調函數中,你可以解析網頁響應并返回項目對象和請求對象或兩者的迭代。這些請求也將包含一個回調,然后被Scrapy下載,然后有指定的回調處理。
在回調函數中,你解析網站的內容,同程使用的是Xpath選擇器(但是你也可以使用BeautifuSoup, lxml或其他任何你喜歡的程序),并生成解析的數據項。
最后,從蜘蛛返回的項目通常會進駐到項目管道。
5、Item Pipeline(項目管道)
項目管道的主要責任是負責處理有蜘蛛從網頁中抽取的項目,他的主要任務是清晰、驗證和存儲數據。當頁面被蜘蛛解析后,將被發送到項目管道,并經過幾個特定的次序處理數據。每個項目管道的組件都是有一個簡單的方法組成的Python類。他們獲取了項目并執行他們的方法,同時他們還需要確定的是是否需要在項目管道中繼續執行下一步或是直接丟棄掉不處理。
項目管道通常執行的過程有:
清洗HTML數據
驗證解析到的數據(檢查項目是否包含必要的字段)
檢查是否是重復數據(如果重復就刪除)
將解析到的數據存儲到數據庫中
6、Downloader middlewares(下載器中間件)
下載中間件是位于Scrapy引擎和下載器之間的鉤子框架,主要是處理Scrapy引擎與下載器之間的請求及響應。它提供了一個自定義的代碼的方式來拓展Scrapy的功能。下載中間器是一個處理請求和響應的鉤子框架。他是輕量級的,對Scrapy盡享全局控制的底層的系統。
7、Spider middlewares(蜘蛛中間件)
蜘蛛中間件是介于Scrapy引擎和蜘蛛之間的鉤子框架,主要工作是處理蜘蛛的響應輸入和請求輸出。它提供一個自定義代碼的方式來拓展Scrapy的功能。蛛中間件是一個掛接到Scrapy的蜘蛛處理機制的框架,你可以插入自定義的代碼來處理發送給蜘蛛的請求和返回蜘蛛獲取的響應內容和項目。
8、Scheduler middlewares(調度中間件)
調度中間件是介于Scrapy引擎和調度之間的中間件,主要工作是處從Scrapy引擎發送到調度的請求和響應。他提供了一個自定義的代碼來拓展Scrapy的功能。


三、數據處理流程
Scrapy的整個數據處理流程有Scrapy引擎進行控制,其主要的運行方式為:
引擎打開一個域名,時蜘蛛處理這個域名,并讓蜘蛛獲取第一個爬取的URL。
引擎從蜘蛛那獲取第一個需要爬取的URL,然后作為請求在調度中進行調度。
引擎從調度那獲取接下來進行爬取的頁面。
調度將下一個爬取的URL返回給引擎,引擎將他們通過下載中間件發送到下載器。
當網頁被下載器下載完成以后,響應內容通過下載中間件被發送到引擎。
引擎收到下載器的響應并將它通過蜘蛛中間件發送到蜘蛛進行處理。
蜘蛛處理響應并返回爬取到的項目,然后給引擎發送新的請求。
引擎將抓取到的項目項目管道,并向調度發送請求。
系統重復第二部后面的操作,直到調度中沒有請求,然后斷開引擎與域之間的聯系。

模擬登陸

無恥的引用完別人的介紹之后,我們來說說怎樣去爬知乎。
我的思路:通過當前用戶的主頁拿到其用戶數據,如下圖:


查找其關注的用戶和粉絲列表,以其關注者為例:


然后依次取其關注者或者粉絲的數據,最后將爬到的數據進行處理,存入數據庫(我這里使用mongodb,mongodb相關知識就不細說了)

如果你嘗試過去爬知乎的話,當你爬到起粉絲列表你會發現無論你怎么努力,最后爬到的也只不過是知乎的登錄頁面:


因為知乎是在線用戶才能訪問其他用戶的關注列表和粉絲列表,所以基于此,我們必須偽裝在線用戶才能拿到數據。
首先我們得知道在線用戶在訪問知乎的時候向其發送了什么數據。我們在chrome里打開調試器,然后點擊network選項卡:

現在幾乎是沒有什么數據的,此時刷新一下當前頁面


就這樣我們看到下載的源碼就是已經登錄用戶的源碼了。

代碼測試

下面我們要找到發送的參數點擊上邊的headers選項卡,往下拉倒RequestHeaders,這就是我們要訪問知乎所需要的數據:


這里主要說一下幾個參數:

  • User-Agent: 用戶代理,主要給服務器提供目前瀏覽器的參數,不同的用戶代理用戶獲取的數據可能會不一樣。服務器也可以根據是否存在此字段來防爬,也可以通過robots.txt來屏蔽掉某些爬蟲。
  • cookie: 服務器返回的標志用戶信息的一些鍵值對。大多數網站也是基于此驗證用戶身份的
  • Referer:HTTP參照位址,用以表示從哪連接到目前的網頁。通過這個字段可以做些圖片防盜鏈處理,這也常被用來對付偽造的跨網站請求。

我們將數據粘貼到接口測試工具里(這里我使用的paw)結果如下:


看,我們已經拿到了結果

既然已經拿到了我們想要的數據,那么其粉絲列表自然也就不在話下:


現在我們可以測試下用代碼獲取數據啦
先利用paw給出的demo來測試下:


代碼如上圖所示(這里的代碼我就不給出了,沒什么東西)



可以證明,我們拿到的數據是對的
當然,這個列表最長只有20個,我會在以后的教程里給出如何獲取所有的關注者和粉絲(這里有點小坑)

scrapy上場

好了,萬事俱備,就差去爬東西了。
scrapy框架的具體使用方式官方有中文文檔,我這里就不詳說了
這里直接開始寫代碼:

設置settings文件

settings文件是爬蟲項目的配置文件,我們可以把cookie和header放在這里,用于調用
在這里我做成了兩個字典

在這里提供一個把一行的cookie分成字典的小工具

print u'請輸入你要分割的 cookie,回車開始分割'
str = raw_input()
print  '\n\n\n\n\n\n'
arr = str.split(';')
for i in arr:
    i = '"' + i
    if not i.startswith('"'):
        i = '"' + i
    if not i.endswith('"'):
        i += '"'
    i += ','
    arrs = i.split('=', 1)
    if not arrs[0].endswith('"'):
        arrs[0] += '"'
    if not arrs[1].startswith('"'):
        arrs[1] = '"' + arrs[1]
    print ':'.join(arrs)

接下來配置 AutoThrottle,用來設置爬蟲的延遲,防止內服務器ban掉ip

AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 1
AUTOTHROTTLE_MAX_DELAY = 10

創建spider文件

在spiders文件夾里創建一個名為ZhihuSpider.py 文件

代碼如下 :

# -*- coding: utf-8 -*-
from zhihu_spider.settings import *
import scrapy
class ZhihuSpider(scrapy.Spider):
    # 爬蟲的名字,啟動爬蟲時需要用
    name = 'zhihu'
    # 從哪開始爬
    start_urls = ['https://www.zhihu.com/people/chi-chu-63']
    # 只能爬數組內的域名
    allowed_domains = ['www.zhihu.com']

上面寫好了之后,我們需要用上我們設置的COOKIE
這里我們重寫下make_requests_from_url方法,把COOKIE傳入:

def make_requests_from_url(self, url):
        return Request(url, method='GET',headers=ZHIHU_HEADER, cookies=ZHIHU_COOKIE)

接下來,在parse方法里通過response參數我們就能通過xpath或者css selector里拿到需要的參數了

# 拿到用戶名
response.css('.title-section .name::text').extract_first()

配置 item

拿到參數我們需要通過item包裝起來,yield出去才能保證數據通過scrapy框架進入下一個流程(處理item)
我們需要寫一個類繼承 scrapy.Item,代碼如下:

class ZhihuSpiderItem(scrapy.Item):
    user_name = scrapy.Field()  # 用戶名
    followees = scrapy.Field()  # 用戶粉絲
    followers = scrapy.Field()  # 用戶關注的人
    introduce = scrapy.Field()  # 簡介
    ellipsis = scrapy.Field()  # 用戶詳細介紹
    location = scrapy.Field()  # 住址
    major = scrapy.Field()  # 主修
    head_image = scrapy.Field()  # 頭像url
    views = scrapy.Field()  # 瀏覽次數
    ask = scrapy.Field()  # 提問
    answer = scrapy.Field()  # 回答
    articles = scrapy.Field()  # 文章
    collected = scrapy.Field()  # 收藏
    public_editor = scrapy.Field()  # 公共編輯
    main_page = scrapy.Field()
    _id = scrapy.Field()
    image_urls = scrapy.Field()
    images = scrapy.Field()

接下來,我們在 spider.py文件里將取到的參數放到item里:

  def parse(self, response):
        item = ZhihuSpiderItem()
        user_name = response.css('.title-section .name::text').extract_first()
        print user_name
        if user_name:
            item['user_name'] = user_name
        follow = response.css(
            'body > div.zg-wrap.zu-main.clearfix > div.zu-main-sidebar > div.zm-profile-side-following.zg-clear > a> strong::text').extract()

        if follow:
            if follow[0]:
                item['followees'] = int(follow[0])
            if follow[1]:
                item['followers'] = int(follow[1])

        item['introduce'] = ''.join(response.css(
            'div.zg-wrap.zu-main.clearfix > div.zu-main-content > div > div.zm-profile-header.ProfileCard > div.zm-profile-header-main > div > div.zm-profile-header-info > div > div.zm-profile-header-description.editable-group > span.info-wrap.fold-wrap.fold.disable-fold > span.fold-item > span::text').extract())
        item['ellipsis'] = ''.join(response.css(
            'body > div.zg-wrap.zu-main.clearfix > div.zu-main-content > div > div.zm-profile-header.ProfileCard > div.zm-profile-header-main > div > div.top > div.title-section > div::text').extract())

        item['location'] = ''.join(response.css('.location .topic-link::text').extract())

        item['major'] = ''.join(response.css('.business .topic-link::text').extract())

        head_url = re.sub(r'_l\.', '.', ''.join(response.css('.body .Avatar--l::attr(src)').extract()))
        arr = []
        arr.append(head_url)
        item['head_image'] = head_url
        item['image_urls'] = arr
        item['ask'] = int(''.join(response.css('.active+ .item .num::text').extract()))

        item['answer'] = int(''.join(response.css('.item:nth-child(3) .num::text').extract()))

        item['articles'] = int(''.join(response.css('.item:nth-child(4) .num::text').extract()))

        item['collected'] = int(''.join(response.css('.item:nth-child(5) .num::text').extract()))

        item['public_editor'] = int(''.join(response.css('.item:nth-child(6) .num::text').extract()))

        item['views'] = int(''.join(response.css('.zg-gray-normal strong::text').extract()))

        if response.url:
            item['main_page'] = response.url
        print response.url
        item['_id'] = hashlib.sha1(response.url).hexdigest()
        yield item

這里我使用了 用戶url的sha1作為主鍵_id,用來存入數據庫排重
目前我們已經拿到了一個用戶的數據,并且yield出去,接下來我們要拿到用戶關注的列表:
首先我們先拿到用戶關注人數所對應的URL,同樣這里用CSS Selector取到URL之后通過一個回調方法進入下一個頁面取到關注的用戶列表,這里就不贅述了

   urls = response.css(
            'body > div.zg-wrap.zu-main.clearfix > div.zu-main-sidebar > div.zm-profile-side-following.zg-clear > a:nth-child(1)::attr(href)').extract()

        if urls:
            for url in urls:
                url = 'https://www.zhihu.com' + url
                yield scrapy.Request(url=url, callback=self.parse_followers,headers=ZHIHU_HEADER, cookies=ZHIHU_COOKIE)

    def parse_followers(self, response):
        urls = response.xpath('//*[@id="zh-profile-follows-list"]/div/div/a/@href').extract()
        if urls:
            for url in urls:
                url = self.base_url + url
                yield scrapy.Request(url=url, callback=self.parse,headers=ZHIHU_HEADER, cookies=ZHIHU_COOKIE)

由此,知乎就可以一直運行下去了。

將item數據存至數據庫

處理item主要是由pipeline完成的,在這里我們需要自定義一個pipeline

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html


import pymongo
from zhihu_spider.items import ZhihuSpiderItem
# from scrapy.contrib.pipeline.images import ImagesPipeline
from scrapy.exceptions import DropItem
class ZhihuSpiderPipeLine(object):
    def __init__(self):
        import pymongo
        connection = pymongo.MongoClient('127.0.0.1', 27017)
        self.db = connection["zhihu"]
        self.zh_user = self.db['zh_user']
    def process_item(self, item, spider):
        if isinstance(item, ZhihuSpiderItem):
            self.saveOrUpdate(self.zh_user, item)
    def saveOrUpdate(self, collection, item):
        try:
            collection.insert(dict(item))
            return item
        except:
            raise DropItem('重復嘍')

我們在init方法中初始化了數據庫,然后設置相關的 db_name和collection_name
然后將pipeline路徑放置到settings.py文件中,scrapy會自動調用

ITEM_PIPELINES = {
    'zhihu_spider.pipelines.ZhihuSpiderPipeLine': 300,
}

下載用戶頭像

首先將settings.py中的ROBOTSTXT_OBEY設置為False,讓爬蟲不遵循robots.txt中的規定。不然圖片是拿不到的,因為:


如上圖,按照規則,知乎的robots.txt是不允許我們抓取/people/文件夾下的東西的,但是用戶頭像就是在這。所以不得不卑鄙一回啦~
scrapy已經提供給我們幾個寫好的的下載器了,我這里使用 默認的ImagePipeline
使用方式很簡單,只需要在item里加入

 image_urls = scrapy.Field()
 images = scrapy.Field()

兩個字段,然后將頭像以數組形式傳入image_urls里,然后在settings.py里面添加
'scrapy.contrib.pipeline.images.ImagesPipeline': 100
ITEM_PIPELINES字典中便可以了,pipeline路徑后邊的數字越小,越先被處理,越大越后處理,如果配置有覆蓋,數字大的會覆蓋小的。

最后一步:設置文件下載路徑:將IMAGES_STORE = '/path/to/image'添加至settings.py文件中

在命令行中通過 scrapy crawl zhihu 啟動爬蟲就可以爬取數據了

接下來查詢數據庫:


上圖就是我的數據了
頭像也拿到了

源碼地址 https://github.com/dengqiangxi/zhihu_spider.git

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容