Scrapy + Mongo 構建一個網頁爬蟲

Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。 能夠應用在包括數據挖掘,信息處理或存儲歷史數據等一系列的程序中。javascript

Scrapy官方架構圖 Scrapy架構圖css

各部件職能

  • Scrapy Engine 控制數據流在系統組件中的流動,並回調相關動做事件
  • Scheduler 從引擎接受request併入隊,當引擎請求他們時返回request
  • Downloader 獲取頁面數據並提供給引擎,然後提供給spider
  • Spiders 用戶定義的爬蟲模塊
  • Item Pipeline 處理被spider提取出來的item,包括丟棄、驗證和持久化等等
  • Downloader middlewares 引擎及下載器之間的hook,處理Downloader傳遞給引擎的response,同時支持用戶掛載自定義邏輯
  • Spider middlewares 引擎及Spider之間的hook,處理spider的輸入(response)和輸出(items及requests),同時支持用戶掛載自定義邏輯

數據流轉

  1. 核心引擎從爬蟲獲取初始url,並生成一個Request任務投入Scheduler調度計劃裏
  2. 引擎向調度器請求一個新的Request爬取任務並轉發給downloader下載器
  3. 下載器載入頁面並返回一個Response響應給引擎
  4. 引擎將Response轉發給Spider爬蟲作 數據提取 和 搜索新的跟進地址
  5. 處理結果由引擎作分發:提取的數據 -> ItemPipeline管道,新的跟進地址Request -> 調度器
  6. 流程返回第二步循環執行,直至調度器中的任務被處理完畢

這裏咱們以爬取馬蜂窩問答頁面(www.mafengwo.cn/wenda)的文章爲例,說明如何構建一個Scrapy爬蟲java

搭建環境,安裝依賴

#沙箱環境
virtualenv mafengwoenv
source mafengwoenv/bin/activate #進入沙箱環境
#安裝兩個兼容包
pip install cryptography ndg-httpsclient

#pip安裝本次爬蟲項目依賴的包
pip install scrapy #爬蟲框架
pip install pymongo #mongo引擎的python驅動

建立Scrapy項目

#建立一個名爲 mafengwo 的項目
scrapy startproject mafengwo

將默認建立以下結構層次的項目node

mafengwo
├── mafengwo
│   ├── __init__.py
│   ├── items.py #定義抽象數據模型
│   ├── pipelines.py #定義數據處理管道
│   ├── settings.py #配置文件
│   └── spiders #存放項目全部爬蟲
│       └── __init__.py
└── scrapy.cfg

Item建模

爲告終構化數據,咱們須要定義爬取數據結構的抽象模型(嚴格的說,Item不是必須的,你也能夠直接在spider中返回dict數據,可是使用Item能得到額外的數據驗證機制)
在 mafengwo/mafengwo/items.py 文件中定義咱們須要爬取的標題、做者、時間和內容屬性:python

# -*- coding: utf-8 -*-
import scrapy

class WendaItem(scrapy.Item):
    title = scrapy.Field()
    author = scrapy.Field()
    time = scrapy.Field()
    content = scrapy.Field()

編寫問答頁爬蟲主程序解析頁面

#從基礎爬蟲模板建立咱們的爬蟲
scrapy genspider --template basic wenda www.mafengwo.cn/wenda

編輯生成的爬蟲程序 mafengwo/mafengwo/spiders/wenda.py,完善咱們的數據爬取邏輯:正則表達式

# -*- coding: utf-8 -*-
import scrapy
from mafengwo import items

class WendaSpider(scrapy.Spider):
    name = "wenda"
    allowed_domains = ["www.mafengwo.cn"] #必須是和start_urls一致的域名,且不能跟上目錄
    start_urls = [
        'http://www.mafengwo.cn/wenda/',
    ]

    #框架默認的頁面解析器入口,start_urls頁面將被傳入
    def parse(self, response):
        #遍歷文章列表
        for link in response.xpath("//ul[@class='_j_pager_box']/li"):
            url = link.xpath("div[@class='wen']/div[@class='title']/a/@href").extract_first()
            url = response.url + url[7:] #詳情頁地址
            yield scrapy.Request(url, callback=self.parse_detail) #跟進詳情頁
        #當前頁條目抓取完畢後,跟進下一頁
        # next = response.xpath('xxx').extract_first()
        # if next:
        #     yield scrapy.Request(next, self.parse)


    #咱們自定義的詳情頁解析器
    def parse_detail(self, response):
        item = items.WendaItem()
        #抽取頁面信息存入模型
        item['author'] = response.xpath("//div[@class='pub-bar fr']/a[@class='name']/text()").extract_first()
        item['title'] = response.xpath("//div[@class='q-title']/h1/text()").extract_first()
        item['time'] = response.xpath("//div[@class='pub-bar fr']/span[@class='time']/span/text()").extract_first()
        item['content'] = response.xpath("//div[@class='q-desc']/text()").extract_first()

        yield item

DOM調試
scrapy使用Scrapy Selectors(基於XPath 和 CSS )從網頁提取數據shell

Selector有四個基本方法數據庫

  1. xpath() 根據xpath規則返回全部節點的selector list
  2. css() 根據css規則返回全部節點的selector list
  3. extract() 序列化該節點爲unicode字符串並返回list
  4. re() 根據傳入的正則表達式對數據進行提取,返回unicode字符串list列表

xpath簡要瀏覽器

  • xpath分爲 絕對路徑相對路徑 兩種,並由 路徑表達式 組成:
    • /根節點
    • //匹配節點
    • .當前節點
    • ..父節點
  • 路徑表達式步進表達式 組成(軸::節點測試[謂語]):
    • 軸(節點層級的相對關係):precedingpreceding-siblingselffollowing-siblingfollowingancestorparentchildattribute...
    • 節點測試(節點匹配):節點名*text()node()...
    • 謂語(過濾):[索引數字][last()][@class="hot"] ...

咱們能夠經過如下指令進入ScrapyShell來測試xpath規則bash

scrapy shell --nolog 'http://www.mafengwo.cn/wenda/'  #進入交互式工具

>>>sel.xpath('/div/span')  #shell中測試xpath
>>>fetch("http://www.mafengwo.cn") #切換頁面,將會刷新response等對象

咱們也能夠直接在爬蟲解析器中嵌入一個鉤子 scrapy.shell.inspect_response(response, self),從而在爬取過程當中回調到ScrapyShell,並在當時特定場景下進行調試

特別的,對於xpath,咱們也能夠在瀏覽器console中經過以下方法來測試xpath

$x('規則')

parse解析器調試
咱們能夠經過如下命令來調試解析器對頁面數據的分析狀況

scrapy parse  --spider=爬蟲名  -c 解析器名  -d 跟進深度  -v  調試地址

數據處理管道Item Pipeline

當Item數據在Spider中被收集以後,它將會被傳遞到Item Pipeline,並按序執行全部管道 管道接收到Item後能夠執行自定義邏輯,同時也決定此Item是否繼續經過pipeline,或是被丟棄 管道典型的運用:

  • 爬取結果持久化
  • 清理HTML數據
  • 驗證爬取的數據
  • 查重(並丟棄)

爬蟲數據經常使用的持久化策略是mongo引擎:

  1. 它支持海量採集數據的錄入
  2. 有很好的伸縮性拓展性,在後期數據變更調整字段的時候能最小化縮減開發成本。

下面咱們經過一個mongo管道作爬蟲數據的持久化
固然,你也能夠直接將持久化邏輯寫入爬蟲主程序,可是ItemPipline中的持久化邏輯能避免低配IO對爬蟲的阻塞

# -*- coding: utf-8 -*-
import pymongo
from scrapy import exceptions

class MongoPipeline(object):
	#不用事先建立mongo數據庫、集合 和 定義文檔,即插即用
	mongo_uri = 'localhost'
    mongo_database = 'mafengwo'
    collection_name = 'wenda_pages'
	
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    #下面這個類方法定義瞭如何由Crawler對象建立這個管道實例
    #在這裏咱們能夠經過crawler參數相似於 `crawler.settings.get()` 形式訪問到諸如settings、signals等全部scrapy框架內核組件
    @classmethod
    def from_crawler(cls, crawler):
        return cls(cls.mongo_uri, cls.mongo_database)

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.collection = self.client[self.mongo_db][self.collection_name]

    def close_spider(self, spider):
        self.client.close()

    # 管道必須實現的一個方法,在此實現具體的持久化邏輯
    def process_item(self, item, spider):
        if (not item['title']):
		    raise exceptions.DropItem('丟棄一個標題不存在頁面')
	    else:
	        self.collection.insert(dict(item))
        return item

啓用mongo持久化管道

咱們能夠在 mafengwo/mafengwo/settings.py 文件中寫入配置

ITEM_PIPELINES = {
    'mafengwo.pipelines.MongoPipeline': 1, #數字肯定了不一樣管道運行的前後順序,從低到高
}

可是爲了避免污染全局管道配置,咱們把setting寫入爬蟲自配置中,即爬蟲主程序的 custom_settings 屬性中:

class WendaSpider(scrapy.Spider):
    custom_settings = {
        'ITEM_PIPELINES': {'mafengwo.pipelines.MongoPipeline': 1}
    }

運行咱們的問答頁爬蟲

scrapy crawl wenda

當運行 scrapy爬蟲模塊時,scrapy嘗試從中查找Spider的定義,而且在爬取引擎中運行它。 爬取啓動後,scrapy首先依據模塊的 start_urls 屬性建立請求,並將請求的response做爲參數傳給默認回調函數 parse 。 在回調函數 parse中,咱們能夠產生(yield)更多的請求,並將響應傳遞給下一層次的回調函數。

mongo中查看數據

mongo
>show dbs
>use mafengwo #切換數據庫
>show collections
>db.wenda_pages.findOne()
>db.wenda_pages.find().limit(3).pretty()

數據庫中能夠看到,問答頁面數據已經成功抓取並錄入了

相關文章
相關標籤/搜索