分佈式+增量式爬蟲

 


CrawlSpider(爬取多頁面數據)

scrapy startproject baike #建立 baike 的項目

cd baike
scrapy genspider -t crawl duanzi www.xxx.com     #建立爬蟲文件 duanzi

scrapy crawl suanzi #執行程序,要找對duanzi.py的路徑 這裏這個執行跟其餘的執行有點不一樣

CrawlSpider的介紹

引入php

提問:若是想要經過爬蟲程序去爬取」糗百「全站數據新聞數據的話,有幾種實現方法?css

方法一:基於Scrapy框架中的Spider的遞歸爬取進行實現(Request模塊遞歸回調parse方法)。html

方法二:基於CrawlSpider的自動爬取進行實現(更加簡潔和高效)。java

今日概要python

  • CrawlSpider簡介
  • CrawlSpider使用
    • 基於CrawlSpider爬蟲文件的建立
    • 連接提取器
    • 規則解析器

今日詳情正則表達式

一.簡介redis

  CrawlSpider實際上是Spider的一個子類,除了繼承到Spider的特性和功能外,還派生除了其本身獨有的更增強大的特性和功能。其中最顯著的功能就是」LinkExtractors連接提取器「。Spider是全部爬蟲的基類,其設計原則只是爲了爬取start_url列表中網頁,而從爬取到的網頁中提取出的url進行繼續的爬取工做使用CrawlSpider更合適。數據庫

二.使用服務器

  1.建立scrapy工程:scrapy startproject projectName

  2.建立爬蟲文件:scrapy genspider -t crawl spiderName www.xxx.com

    --此指令對比之前的指令多了 "-t crawl",表示建立的爬蟲文件是基於CrawlSpider這個類的,而再也不是Spider這個基類。

  3.觀察生成的爬蟲文件

# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class ChoutidemoSpider(CrawlSpider): name = 'choutiDemo' #allowed_domains = ['www.chouti.com'] start_urls = ['http://www.chouti.com/'] rules = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), ) def parse_item(self, response): i = {} #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract() #i['name'] = response.xpath('//div[@id="name"]').extract() #i['description'] = response.xpath('//div[@id="description"]').extract() return i

  - 2,3行:導入CrawlSpider相關模塊

  - 7行:表示該爬蟲程序是基於CrawlSpider類的

  - 12,13,14行:表示爲提取Link規則

  - 16行:解析方法

  CrawlSpider類和Spider類的最大不一樣是CrawlSpider多了一個rules屬性,其做用是定義」提取動做「。在rules中能夠包含一個或多個Rule對象,在Rule對象中包含了LinkExtractor對象。

3.1 LinkExtractor:顧名思義,連接提取器。

    LinkExtractor(

         allow=r'Items/',# 知足括號中「正則表達式」的值會被提取,若是爲空,則所有匹配。

         deny=xxx,  # 知足正則表達式的則不會被提取。

 

         restrict_xpaths=xxx, # 知足xpath表達式的值會被提取

         restrict_css=xxx, # 知足css表達式的值會被提取

         deny_domains=xxx, # 不會被提取的連接的domains。 

    )

    - 做用:提取response中符合規則的連接。

    

  3.2 Rule : 規則解析器。根據連接提取器中提取到的連接,根據指定規則提取解析器連接網頁中的內容。

     Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True)

    - 參數介紹:

      參數1:指定連接提取器

      參數2:指定規則解析器解析數據的規則(回調函數)

      參數3:是否將連接提取器繼續做用到連接提取器提取出的連接網頁中。當callback爲None,參數3的默認值爲true。

  3.3 rules=( ):指定不一樣規則解析器。一個Rule對象表示一種提取規則。

  3.4 CrawlSpider總體爬取流程:

    a)爬蟲文件首先根據起始url,獲取該url的網頁內容

    b)連接提取器會根據指定提取規則將步驟a中網頁內容中的連接進行提取

    c)規則解析器會根據指定解析規則將連接提取器中提取到的連接中的網頁內容根據指定的規則進行解析

    d)將解析數據封裝到item中,而後提交給管道進行持久化存儲

需求:爬取趣事百科中全部的段子(包含1-35頁)

 # qiubai.py

複製代碼
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class ChoutiSpider(CrawlSpider):
    name = 'qiubai'        # 執行的時候根據這個name名來執行該文件,不是該文件的名稱,不過默認生成的該名稱和文件名一致
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.qiushibaike.com/pic/']    # 起始url

    # rules規則解析器:將連接提取器提取到的鏈接所對應的頁面數據進行指定形式的解析,正則表達式
    # follow =True ,讓鏈接提取器繼續做用到連接提取器提取到的鏈接所對應的頁面中,False只獲取起始url頁面中鏈接
    rules = (
        Rule(LinkExtractor(allow=r'/pic/page/\d+\?s=\d+'), callback='parse_item', follow=True),    #正則匹配的是第幾頁按鈕中的,a標籤中的href所對應的鏈接
        Rule(LinkExtractor(allow=r"/pic/$"),callback='parse_item',follow=True)
    )
    def parse_item(self, response):
        print('==>>>>',response)  #能夠打印出解析出來的頁面的鏈接
        # response.xapth()    #這裏直接用來解析想要的數據便可
複製代碼

# settings.py

USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
ROBOTSTXT_OBEY = False

 

總結:

  一、該crawlSpider適用於解析多頁面的網頁,好比含有第一頁,第二頁... 等等,頁面數據同樣,採用的解析方法也是同樣的,

  二、當頁碼中所對應的鏈接url,不一致時,能夠寫兩條Rule規則來匹配

  三、follow=True,讓鏈接提取器繼續做用到連接提取器提取到的鏈接所對應的頁面中

基於scrapy-redis分佈式爬蟲

文件的建立和執行的命令:

複製代碼
scrapy startproject redis_chouti #建立分佈式項目redis_chouti
scrapy genspider -t crawl chouti www.xxx.com   #建立爬蟲文件chouti

scrapy runspider ./redis_chouti/spiders/chouti.py #執行程序,文件名,執行以後會夯住,等待給定的起始url


#redis-cli客戶端中執行:給一個起始的url lpush chouti http:
//dig.chouti.com #chouti指的是redis_key調度器,不是爬蟲的文件名稱
複製代碼

1、redis分佈式部署

- 爲何原生的scrapy不能實現分佈式?
    - 調度器不能被共享
    - 管道沒法被共享

- scrapy-redis組件的做用是什麼?
    - 提供了能夠被共享的調度器和管道

- 分佈式爬蟲實現流程
1.環境安裝:pip install scrapy-redis
2.建立工程
3.建立爬蟲文件:RedisCrawlSpider  RedisSpider
    - scrapy genspider -t crawl xxx www.xxx.com
4.對爬蟲文件中的相關屬性進行修改:
    - 導報:from scrapy_redis.spiders import RedisCrawlSpider
    - 將當前爬蟲文件的父類設置成RedisCrawlSpider
    - 將起始url列表替換成redis_key = 'xxx'(調度器隊列的名稱)
5.在配置文件中進行配置:
    - 使用組件中封裝好的能夠被共享的管道類:
        ITEM_PIPELINES = {
            'scrapy_redis.pipelines.RedisPipeline': 400
            }
    - 配置調度器(使用組件中封裝好的能夠被共享的調度器)
        # 增長了一個去重容器類的配置, 做用使用Redis的set集合來存儲請求的指紋數據, 從而實現請求去重的持久化
        DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
        # 使用scrapy-redis組件本身的調度器
        SCHEDULER = "scrapy_redis.scheduler.Scheduler"
        # 配置調度器是否要持久化, 也就是當爬蟲結束了, 要不要清空Redis中請求隊列和去重指紋的set。若是是True, 就表示要持久化存儲, 就不清空數據, 不然清空數據
        SCHEDULER_PERSIST = True

     - 指定存儲數據的redis:
        REDIS_HOST = 'redis服務的ip地址'
        REDIS_PORT = 6379

     - 配置redis數據庫的配置文件
        - 取消保護模式:protected-mode no
        - bind綁定: #bind 127.0.0.1

     - 啓動redis

6.執行分佈式程序
    scrapy runspider xxx.py

7.向調度器隊列中仍入一個起始url:
    在redis-cli中執行:
流程

    1.scrapy框架是否能夠本身實現分佈式?

    - 不能夠。緣由有二。

      其一:由於多臺機器上部署的scrapy會各自擁有各自的調度器,這樣就使得多臺機器沒法分配start_urls列表中的url。(多臺機器沒法共享同一個調度器)

      其二:多臺機器爬取到的數據沒法經過同一個管道對數據進行統一的數據持久出存儲。(多臺機器沒法共享同一個管道)

 

    2.基於scrapy-redis組件的分佈式爬蟲

        - scrapy-redis組件中爲咱們封裝好了能夠被多臺機器共享的調度器和管道,咱們能夠直接使用並實現分佈式數據爬取。

        - 實現方式:

            1.基於該組件的RedisSpider類

            2.基於該組件的RedisCrawlSpider類

 

    3.分佈式實現流程:上述兩種不一樣方式的分佈式實現流程是統一的

        - 3.1 下載scrapy-redis組件:pip install scrapy-redis

        - 3.2 redis配置文件的配置:

- 註釋該行:bind 127.0.0.1,表示可讓其餘ip訪問redis - 將yes該爲no:protected-mode no,表示可讓其餘ip操做redis

        3.3 修改爬蟲文件中的相關代碼:

            - 將爬蟲類的父類修改爲基於RedisSpider或者RedisCrawlSpider。注意:若是原始爬蟲文件是基於Spider的,則應該將父類修改爲RedisSpider,若是原始爬蟲文件是基於CrawlSpider的,則應該將其父類修改爲RedisCrawlSpider。

            - 註釋或者刪除start_urls列表,切加入redis_key屬性,屬性值爲scrpy-redis組件中調度器隊列的名稱

 

        3.4 在settings.py配置文件中進行相關配置,開啓使用scrapy-redis組件中封裝好的管道

ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400
}

        3.5 在settings.py配置文件中進行相關配置,開啓使用scrapy-redis組件中封裝好的調度器

# 使用scrapy-redis組件的去重隊列 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis組件本身的調度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 是否容許暫停 SCHEDULER_PERSIST = True

        3.6 在settings.py配置文件中進行爬蟲程序連接redis的配置:

REDIS_HOST = 'redis服務的ip地址' REDIS_PORT = 6379 REDIS_ENCODING = ‘utf-8REDIS_PARAMS = {‘password’:’123456’}

        3.7 開啓redis服務器:redis-server 配置文件

        3.8 開啓redis客戶端:redis-cli

        3.9 運行爬蟲文件:scrapy  runspider  SpiderFile.py

        3.10 向調度器隊列中扔入一個起始url(在redis客戶端中操做):lpush   redis_key屬性值   起始url     

需求:分佈式爬取抽屜網中的標題(存儲到redis中)

 爬蟲文件 chouti.py

 

複製代碼
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider

from fenbushi_choutiPro.items import FenbushiChoutiproItem

class ChoutiSpider(RedisCrawlSpider):
    name = 'chouti'
    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
 redis_key = "chouti"    #調度器隊列的名稱

    rules = (
        Rule(LinkExtractor(allow=r'/all/hot/recent/\d+'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        div_list = response.xpath('//div[@class="news-content"]')
        for div in div_list:
            author = div.xpath('./div[2]/a[4]/b/text()').extract_first()
            title = div.xpath('./div[1]/a/text()').extract_first()
            item = FenbushiChoutiproItem()
            item["author"] = author
            item["title"] = title
            print('===>>',item["author"])

            yield item
複製代碼

- items.py

import scrapy


class FenbushiChoutiproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    author = scrapy.Field()
View Code

 

- settings.py

USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False
View Code

 

 

分佈式的啓動:

scrapy runspider chouti.py   #找對路徑

redis-server    #記住要修改配置文件,具體修改見前面所說

redis-cli
lpush chouti https://dig.chouti.com/     # 其中chouti指的是redis_key中內容 

 

 

 

 

 

 

 

 

增量式爬蟲

增量式爬蟲

引言:

    當咱們在瀏覽相關網頁的時候會發現,某些網站定時會在原有網頁數據的基礎上更新一批數據,例如某電影網站會實時更新一批最近熱門的電影。小說網站會根據做者創做的進度實時更新最新的章節數據等等。那麼,相似的情景,當咱們在爬蟲的過程當中遇到時,咱們是否是須要定時更新程序以便能爬取到網站中最近更新的數據呢?

一.增量式爬蟲

  • 概念:經過爬蟲程序監測某網站數據更新的狀況,以即可以爬取到該網站更新出的新數據。
  • 如何進行增量式的爬取工做:
    • 在發送請求以前判斷這個URL是否是以前爬取過
    • 在解析內容後判斷這部份內容是否是以前爬取過
    • 寫入存儲介質時判斷內容是否是已經在介質中存在
      • 分析:

              不難發現,其實增量爬取的核心是去重, 至於去重的操做在哪一個步驟起做用,只能說各有利弊。在我看來,前兩種思路須要根據實際狀況取一個(也可能都用)。第一種思路適合不斷有新頁面出現的網站,好比說小說的新章節,天天的最新新聞等等;第二種思路則適合頁面內容會更新的網站。第三個思路是至關因而最後的一道防線。這樣作能夠最大程度上達到去重的目的。

  • 去重方法
    • 將爬取過程當中產生的url進行存儲,存儲在redis的set中。當下次進行數據爬取時,首先對即將要發起的請求對應的url在存儲的url的set中作判斷,若是存在則不進行請求,不然才進行請求。
    • 對爬取到的網頁內容進行惟一標識的制定,而後將該惟一表示存儲至redis的set中。當下次爬取到網頁數據的時候,在進行持久化存儲以前,首先能夠先判斷該數據的惟一標識在redis的set中是否存在,在決定是否進行持久化存儲。

需求:爬取4567tv網站中全部的電影詳情數據。(有更新的url時)

- 爬蟲文件 movie.py

複製代碼
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

from redis import Redis
from increment1_move_Pro.items import Increment1MoveProItem

class MoveSpider(CrawlSpider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567tv.tv/index.php/vod/show/id/7.html']    #起始url # 解析出其餘頁碼中的電影數據
    rules = (
        Rule(LinkExtractor(allow=r'/index.php/vod/show/id/7/page/\d+.html'), callback='parse_item', follow=True),
    )


    def parse_item(self, response):
        li_list = response.xpath('//li[@class="col-md-6 col-sm-4 col-xs-3"]')
        conn = Redis(host="127.0.0.1",port=6379)
for li in li_list: #獲取詳情頁的數據 detail_url = 'https://www.4567tv.tv' + li.xpath('./div/a/@href').extract_first() ex = conn.sadd("movie_url",detail_url) #redis中添加set集合,有去重做用,若是已經存在會返回0,沒有存在會添加並返回1 if ex == 1: yield scrapy.Request(url=detail_url,callback=self.parse_detail) #爲1,說明是新添加的,而後去解析數據便可 else: print("沒有要更新的數據") def parse_detail(self,response): #解析數據 title = response.xpath('/html/body/div[1]/div/div/div/div[2]/h1/text()').extract_first() actor = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[3]/span/text()').extract_first()
item
= Increment1MoveProItem() item["title"] = title item["actor"] = actor yield item
複製代碼

 

- items.py 

複製代碼
import scrapy


class Increment1MoveProItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    actor = scrapy.Field()
複製代碼

 

- 管道文件 pipelines.py

複製代碼
from redis import Redis
class Increment1MoveProPipeline(object):
    conn = None
    def open_spider(self,spider):       #該方法只被調用一次
        self.conn = Redis(host="127.0.0.1",port=6379)

    def process_item(self, item, spider):    #能夠被調用屢次

        print("爬取的數據正在入庫......")
        self.conn.lpush("movie_data",item.__dict__)   # 將解析到的數據存入redis中 lpush是添加列表,能夠直接使用item.__dict__存儲
        return item
複製代碼

 

- 配置文件 settings.py

  使用管道要在settings中添加

複製代碼
ITEM_PIPELINES = {
   'increment1_move_Pro.pipelines.Increment1MoveProPipeline': 300,
}

USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False
複製代碼

 

需求:爬取糗事百科中的段子和做者數據。(有更新的內容,同一個url)

 好比新聞和段子都是實時更新的,若是想要獲取最新的內容,能夠採用如下的方法

  將內容進行哈希,存入redis中的集合,由於集合是不重複的,

    若是redis中以前沒有,會添加到redis中並返回1

    若是以前就有了,那麼會返回0,不作什麼操做

 

 - 爬蟲文件:qiubai.py

複製代碼
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

from increment2_Pro.items import Increment2ProItem
from redis import Redis
import hashlib

class QiubaiSpider(CrawlSpider):
    name = 'qiubai'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    rules = (
        Rule(LinkExtractor(allow=r'/text/page/\d+/'), callback='parse_item', follow=True),    #獲取其餘頁碼
    )

    def parse_item(self, response):

        div_list = response.xpath('//div[@class="article block untagged mb15 typs_hot"]')
        conn = Redis(host='127.0.0.1',port=6379)
        for div in div_list:
            item = Increment2ProItem()
            item['content'] = div.xpath('.//div[@class="content"]/span//text()').extract()
            item['content'] = ''.join(item['content'])
            item['author'] = div.xpath('./div/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first()
            source = item['author']+item['content']
            #本身制定了一種形式的數據指紋
            hashValue = hashlib.sha256(source.encode()).hexdigest()

            ex = conn.sadd('qiubai_hash',hashValue) if ex == 1:
                yield item else:
                print('沒有更新數據可爬!!!')
複製代碼

 

 - items.py

複製代碼
import scrapy


class Increment2ProItem(scrapy.Item):
    # define the fields for your item here like:
    content = scrapy.Field()
    author = scrapy.Field()
複製代碼

 

 

- 管道文件:pipelines.py

複製代碼
from redis import Redis

class Increment2ProPipeline(object):
    conn = None
    def open_spider(self,spider):
        self.conn = Redis(host='127.0.0.1',port=6379)
    def process_item(self, item, spider):
        dic = {
            'author':item['author'],
            'content':item['content']
        }
        self.conn.lpush('qiubaiData',dic) # self.conn.lpush('qiubaiDta',item.__dict__)    #也能夠直接使用 __dict__屬性
        print('爬取到一條數據,正在入庫......')
        return item
複製代碼

 

- 配置文件 settings.py

  使用管道要在settings中添加

複製代碼
ITEM_PIPELINES = {
   'increment1_move_Pro.pipelines.Increment1MoveProPipeline': 300, } USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' # Obey robots.txt rules ROBOTSTXT_OBEY = False
複製代碼

 

 

 

================

相關文章
相關標籤/搜索