1. 程式人生 > >使用 scrapy-redis實現分散式爬蟲

使用 scrapy-redis實現分散式爬蟲

Scrapy 和 scrapy-redis的區別

Scrapy 是一個通用的爬蟲框架,但是不支援分散式,Scrapy-redis是為了更方便地實現Scrapy分散式爬取,而提供了一些以redis為基礎的元件(僅有元件)。

pip install scrapy-redis

Scrapy-redis提供了下面四種元件(components):(四種元件意味著這四個模組都要做相應的修改)

  • Scheduler
  • Duplication Filter
  • Item Pipeline
  • Base Spider

scrapy-redis架構

這裡寫圖片描述

如上圖所⽰示,scrapy-redis在scrapy的架構上增加了redis,基於redis的特性拓展瞭如下元件:

Scheduler

Scrapy改造了python本來的collection.deque(雙向佇列)形成了自己的Scrapy queue(https://github.com/scrapy/queuelib/blob/master/queuelib/queue.py)),但是Scrapy多個spider不能共享待爬取佇列Scrapy queue, 即Scrapy本身不支援爬蟲分散式,scrapy-redis 的解決是把這個Scrapy queue換成redis資料庫(也是指redis佇列),從同一個redis-server存放要爬取的request,便能讓多個spider去同一個資料庫裡讀取。

Scrapy中跟“待爬佇列”直接相關的就是排程器Scheduler

,它負責對新的request進行入列操作(加入Scrapy queue),取出下一個要爬取的request(從Scrapy queue中取出)等操作。它把待爬佇列按照優先順序建立了一個字典結構,比如:

    {
        優先順序0 : 佇列0
        優先順序1 : 佇列1
        優先順序2 : 佇列2
    }

然後根據request中的優先順序,來決定該入哪個佇列,出列時則按優先順序較小的優先出列。為了管理這個比較高階的佇列字典,Scheduler需要提供一系列的方法。但是原來的Scheduler已經無法使用,所以使用Scrapy-redis的scheduler元件。

Duplication Filter

Scrapy中用集合實現這個request去重功能,Scrapy中把已經發送的request指紋放入到一個集合中,把下一個request的指紋拿到集合中比對,如果該指紋存在於集合中,說明這個request傳送過了,如果沒有則繼續操作。這個核心的判重功能是這樣實現的:

    def request_seen(self, request):
        # self.request_figerprints就是一個指紋集合  
        fp = self.request_fingerprint(request)


        # 這就是判重的核心操作  
        if fp in self.fingerprints:
            return True
        self.fingerprints.add(fp)
        if self.file:
            self.file.write(fp + os.linesep)

在scrapy-redis中去重是由Duplication Filter元件來實現的,它通過redis的set 不重複的特性,巧妙的實現了Duplication Filter去重。scrapy-redis排程器從引擎接受request,將request的指紋存⼊redis的set檢查是否重複,並將不重複的request push寫⼊redis的 request queue。

引擎請求request(Spider發出的)時,排程器從redis的request queue佇列⾥里根據優先順序pop 出⼀個request 返回給引擎,引擎將此request發給spider處理。

Item Pipeline

引擎將(Spider返回的)爬取到的Item給Item Pipeline,scrapy-redis 的Item Pipeline將爬取到的 Item 存⼊redis的 items queue。

修改過Item Pipeline可以很方便的根據 key 從 items queue 提取item,從⽽實現items processes叢集。

Base Spider

不再使用scrapy原有的Spider類,重寫的RedisSpider繼承了Spider和RedisMixin這兩個類,RedisMixin是用來從redis讀取url的類。

當我們生成一個Spider繼承RedisSpider時,呼叫setup_redis函式,這個函式會去連線redis資料庫,然後會設定signals(訊號):

  • 一個是當spider空閒時候的signal,會呼叫spider_idle函式,這個函式呼叫schedule_next_request函式,保證spider是一直活著的狀態,並且丟擲DontCloseSpider異常。
  • 一個是當抓到一個item時的signal,會呼叫item_scraped函式,這個函式會呼叫schedule_next_request函式,獲取下一個request。

Scrapy-Redis分散式策略:

假設有四臺電腦:Windows 10、Mac OS X、Ubuntu 16.04、CentOS 7.2,任意一臺電腦都可以作為 Master端 或 Slaver端,比如:

  • Master端(核心伺服器) :使用 Windows 10,搭建一個Redis資料庫,不負責爬取,只負責url指紋判重、Request的分配,以及資料的儲存
  • Slaver端(爬蟲程式執行端) :使用 Mac OS X 、Ubuntu 16.04、CentOS 7.2,負責執行爬蟲程式,執行過程中提交新的Request給Master

這裡寫圖片描述

  1. 首先Slaver端從Master端拿任務(Request、url)進行資料抓取,Slaver抓取資料的同時,產生新任務的Request便提交給 Master 處理;
  2. Master端只有一個Redis資料庫,負責將未處理的Request去重和任務分配,將處理後的Request加入待爬佇列,並且儲存爬取的資料。

Scrapy-Redis預設使用的就是這種策略,我們實現起來很簡單,因為任務排程等工作Scrapy-Redis都已經幫我們做好了,我們只需要繼承RedisSpider、指定redis_key就行了。

缺點是,Scrapy-Redis排程的任務是Request物件,裡面資訊量比較大(不僅包含url,還有callback函式、headers等資訊),可能導致的結果就是會降低爬蟲速度、而且會佔用Redis大量的儲存空間,所以如果要保證效率,那麼就需要一定硬體水平。

一、安裝Redis

安裝完成後,拷貝一份Redis安裝目錄下的redis.conf到任意目錄,建議儲存到:/etc/redis/redis.conf(Windows系統可以無需變動)

二、修改配置檔案 redis.conf

#是否作為守護程序執行
daemonize no
#Redis 預設監聽埠
port 6379
#客戶端閒置多少秒後,斷開連線
timeout 300
#日誌顯示級別
loglevel verbose
#指定日誌輸出的檔名,也可指定到標準輸出埠
logfile redis.log
#設定資料庫的數量,預設最大是16,預設連線的資料庫是0,可以通過select N 來連線不同的資料庫
databases 32
#Dump持久化策略
#當有一條Keys 資料被改變是,900 秒重新整理到disk 一次
#save 900 1
#當有10 條Keys 資料被改變時,300 秒重新整理到disk 一次
save 300 100
#當有1w 條keys 資料被改變時,60 秒重新整理到disk 一次
save 6000 10000
#當dump     .rdb 資料庫的時候是否壓縮資料物件
rdbcompression yes
#dump 持久化資料儲存的檔名
dbfilename dump.rdb
###########    Replication #####################
#Redis的主從配置,配置slaveof則例項作為從伺服器
#slaveof 192.168.0.105 6379
#主伺服器連線密碼
# masterauth <master-password>
############## 安全性 ###########
#設定連線密碼
#requirepass <password>
############### LIMITS ##############
#最大客戶端連線數
# maxclients 128
#最大記憶體使用率
# maxmemory <bytes>
########## APPEND ONLY MODE #########
#是否開啟日誌功能
appendonly no
# AOF持久化策略
#appendfsync always
#appendfsync everysec
#appendfsync no
################ VIRTUAL MEMORY ###########
#是否開啟VM 功能
#vm-enabled no
# vm-enabled yes
#vm-swap-file logs/redis.swap
#vm-max-memory 0
#vm-page-size 32
#vm-pages 134217728
#vm-max-threads 4

開啟你的redis.conf配置檔案,示例:

  • 非Windows系統:sudo vi /etc/redis/redis.conf

  • Windows系統:C:\Intel\Redis\conf\redis.conf

  • Master端redis.conf裡註釋bind 127.0.0.1,Slave端才能遠端連線到Master端的Redis資料庫。

    • daemonize no表示Redis預設不作為守護程序執行,即在執行redis-server /etc/redis/redis.conf時,將顯示Redis啟動提示畫面;

    • daemonize yes

      則預設後臺執行,不必重新啟動新的終端視窗執行其他命令,看個人喜好和實際需要。

三、測試Slave端遠端連線Master端

測試中,Master端Windows 10 的IP地址為:192.168.199.108

  1. Master端按指定配置檔案啟動redis-server,示例:
    • 非Windows系統:sudo redis-server /etc/redis/redis.conf
    • Windows系統:命令提示符(管理員)模式下執行redis-server C:\Intel\Redis\conf\redis.conf讀取預設配置即可。
  2. Master端啟動本地redis-cli
  3. slave端啟動redis-cli -h 192.168.199.108,-h 引數表示連線到指定主機的redis資料庫
注意:Slave端無需啟動redis-server,Master端啟動即可。只要 Slave 端讀取到了 Master 端的 Redis 資料庫,則表示能夠連線成功,可以實施分散式。

四、Redis資料庫桌面管理工具

這裡推薦 Redis Desktop Manager,支援 Windows、Mac OS X、Linux 等平臺:

這裡寫圖片描述

這裡寫圖片描述

五、Redis的資料型別

  • 字串
  • 雜湊/雜湊
  • 列表
  • 集合
  • 可排序集合

1.字串命令

set mykey ”cnblogs” 建立變數

get mykey 檢視變數

getrange mykey start end 獲取字串,如:get name 2 5 #獲取name2~5的字串

strlen mykey 獲取長度

incr/decr mykey 加一減一,型別是int

append mykey ”com” 新增字串,新增到末尾

2.雜湊命令

hset myhash name “cnblogs” 建立變數,myhash類似於變數名,name類似於key,”cnblogs”類似於values

hgetall myhash 得到key和values兩者

hget myhash name 得到values

hexists myhash name 檢查是否存在這個key

hdel myhash name 刪除這個key

hkeys myhash 檢視key

hvals myhash 檢視values

3.列表命令

lpush/rpush mylist “cnblogs” 左新增/右新增值

lrange mylist 0 10 檢視列表0~10的值

blpop/brpop key1[key2] timeout 左刪除/右刪除一個,timeout是如果沒有key,等待設定的時間後結束。

lpop/rpop key 左刪除/右刪除,沒有等待時間。

llen key 獲得長度

lindex key index 取第index元素,index是從0開始的

4.集合命令(不重複)

sadd myset “cnblogs” 新增內容,返回1表示不存在,0表示存在

scard key 檢視set中的值

sdiff key1 [key2] 2個set做減法,其實就是減去了交際部分

sinter key1 [key2] 2個set做加法,其實就是留下了兩者的交集

spop key 隨機刪除值

srandmember key member 隨機獲取member個值

smember key 獲取全部的元素

5.可排序集合命令

zadd myset 0 ‘project1’ [1 ‘project2’] 新增集合元素;中括號是沒有的,在這裡是便於理解

zrangebyscore myset 0 100 選取分數在0~100的元素

zcount key min max 選取分數在min~max的元素的個數

原始碼自帶專案說明:

使用scrapy-redis的example來修改

先從github上拿到scrapy-redis的示例,然後將裡面的example-project目錄移到指定的地址:

# clone github scrapy-redis原始碼檔案
git clone https://github.com/rolando/scrapy-redis.git


# 直接拿官方的專案範例,改名為自己的專案用(針對懶癌患者)
mv scrapy-redis/example-project ~/scrapyredis-project

我們clone到的 scrapy-redis 原始碼中有自帶一個example-project專案,這個專案包含3個spider,分別是dmoz, myspider_redis,mycrawler_redis。

一、dmoz (class DmozSpider(CrawlSpider))

這個爬蟲繼承的是CrawlSpider,它是用來說明Redis的持續性,當我們第一次執行dmoz爬蟲,然後Ctrl + C停掉之後,再執行dmoz爬蟲,之前的爬取記錄是保留在Redis裡的。

分析起來,其實這就是一個 scrapy-redis 版CrawlSpider類,需要設定Rule規則,以及callback不能寫parse()方法。

執行方式:scrapy crawl dmoz

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

class DmozSpider(CrawlSpider):
    """Follow categories and extract links."""
    name = 'dmoz'
    allowed_domains = ['dmoz.org']
    start_urls = ['http://www.dmoz.org/']

    rules = [
        Rule(LinkExtractor(
            restrict_css=('.top-cat', '.sub-cat', '.cat-item')
        ), callback='parse_directory', follow=True),
    ]


    def parse_directory(self, response):
        for div in response.css('.title-and-desc'):
            yield {
                'name': div.css('.site-title::text').extract_first(),
                'description': div.css('.site-descr::text').extract_first().strip(),
                'link': div.css('a::attr(href)').extract_first(),
            }

二、myspider_redis (class MySpider(RedisSpider))

這個爬蟲繼承了RedisSpider, 它能夠支援分散式的抓取,採用的是basic spider,需要寫parse函式。

其次就是不再有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis裡pop出來,成為請求的url地址。

from scrapy_redis.spiders import RedisSpider

class MySpider(RedisSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'myspider_redis'
    # 注意redis-key的格式:
    redis_key = 'myspider:start_urls'

    # 可選:等效於allowd_domains(),__init__方法按規定格式寫,使用時只需要修改super()裡的類名引數即可
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '') 
        self.allowed_domains = filter(None, domain.split(','))

        # 修改這裡的類名為當前類名
        super(MySpider, self).__init__(*args, **kwargs)

    def parse(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

注意:

RedisSpider類 不需要寫allowd_domainsstart_urls

  1. scrapy-redis將從在構造方法__init__()裡動態定義爬蟲爬取域範圍,也可以選擇直接寫allowd_domains
  2. 必須指定redis_key,即啟動爬蟲的命令,參考格式:redis_key = 'myspider:start_urls'
  3. 根據指定的格式,start_urls將在 Master端的 redis-cli 裡 lpush 到 Redis資料庫裡,RedisSpider 將在資料庫裡獲取start_urls。

執行方式:

  1. 通過runspider方法執行爬蟲的py檔案(也可以分次執行多條),爬蟲(們)將處於等待準備狀態:

    scrapy runspider myspider_redis.py

  2. 在Master端的redis-cli輸入push指令,參考格式:

    $redis > lpush myspider:start_urls http://www.dmoz.org/

  3. Slaver端爬蟲獲取到請求,開始爬取。

    lrange mycrawler:start_url 0 -1

三、mycrawler_redis (class MyCrawler(RedisCrawlSpider))

這個RedisCrawlSpider類爬蟲繼承了RedisCrawlSpider,能夠支援分散式的抓取。因為採用的是crawlSpider,所以需要遵守Rule規則,以及callback不能寫parse()方法。

同樣也不再有start_urls了,取而代之的是redis_key,scrapy-redis將key從Redis裡pop出來,成為請求的url地址。

from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor

from scrapy_redis.spiders import RedisCrawlSpider

class MyCrawler(RedisCrawlSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'mycrawler_redis'
    redis_key = 'mycrawler:start_urls'

    rules = (
        # follow all links
        Rule(LinkExtractor(), callback='parse_page', follow=True),
    )


    # __init__方法必須按規定寫,使用時只需要修改super()裡的類名引數即可
    def __init__(self, *args, **kwargs):
        # Dynamically define the allowed domains list.
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))

        # 修改這裡的類名為當前類名
        super(MyCrawler, self).__init__(*args, **kwargs)


    def parse_page(self, response):
        return {
            'name': response.css('title::text').extract_first(),
            'url': response.url,
        }

注意:

同樣的,RedisCrawlSpider類不需要寫allowd_domainsstart_urls

  1. scrapy-redis將從在構造方法__init__()裡動態定義爬蟲爬取域範圍,也可以選擇直接寫allowd_domains
  2. 必須指定redis_key,即啟動爬蟲的命令,參考格式:redis_key = 'myspider:start_urls'
  3. 根據指定的格式,start_urls將在 Master端的 redis-cli 裡 lpush 到 Redis資料庫裡,RedisSpider 將在資料庫裡獲取start_urls。

執行方式:

  1. 通過runspider方法執行爬蟲的py檔案(也可以分次執行多條),爬蟲(們)將處於等待準備狀態:

    scrapy runspider mycrawler_redis.py

  2. 在Master端的redis-cli輸入push指令,參考格式:

    $redis > lpush mycrawler:start_urls http://www.dmoz.org/

  3. 爬蟲獲取url,開始執行。

總結:

  1. 如果只是用到Redis的去重和儲存功能,就選第一種;
  2. 如果要寫分散式,則根據情況,選擇第二種、第三種;
  3. 通常情況下,會選擇用第三種方式編寫深度聚焦爬蟲。

例項:爬取百度百科

settings

#
指定使用scrapy-redis的排程器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 指定使用scrapy-redis的去重
DUPEFILTER_CLASS = 'scrapy_redis.dupefilters.RFPDupeFilter'

# 指定排序爬取地址時使用的佇列,
# 預設的 按優先順序排序(Scrapy預設),由sorted set實現的一種非FIFO、LIFO方式。
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
# 可選的 按先進先出排序(FIFO)
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderQueue'
# 可選的 按後進先出排序(LIFO)
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderStack'

# 在redis中保持scrapy-redis用到的各個佇列,從而允許暫停和暫停後恢復,也就是不清理redis queues
SCHEDULER_PERSIST = True

# 只在使用SpiderQueue或者SpiderStack是有效的引數,指定爬蟲關閉的最大間隔時間
# SCHEDULER_IDLE_BEFORE_CLOSE = 10

# 去重規則,在redis中儲存時對應的key
# SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter'
# 去重規則對應處理的類
# SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
#預設情況下,RFPDupeFilter只記錄第一個重複請求。將DUPEFILTER_DEBUG設定為True會記錄所有重複的請求。
DUPEFILTER_DEBUG =True


# 通過配置RedisPipeline將item寫入key為 spider.name : items 的redis的list中,供後面的分散式處理item
# 這個已經由 scrapy-redis 實現,不需要我們寫程式碼
ITEM_PIPELINES = {
'example.pipelines.ExamplePipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400
}

# 指定redis資料庫的連線引數
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
# 密碼登陸
# REDIS_URL="redis://[user]:[email protected]:port"

# LOG等級
LOG_LEVEL = 'DEBUG'

# 覆蓋預設請求頭,可以自己編寫Downloader Middlewares設定代理和UserAgent
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8',
'Connection': 'keep-alive',
'Accept-Encoding': 'gzip, deflate, sdch'
}

#REDIS_ENCODING = 'utf-8'

修改dmoz

1、修改items

class BaidubaikeItem(Item):
    # define the fields for your item here like:
    url = Field()
    masterTitle = Field()
    secondTitle = Field()
    content = Field()
    # crawled = Field()  # 什麼時間抓取的
    # spider = Field()  # 誰抓取的

2、修改pipelines

class ExamplePipeline(object):
    def __init__(self):
        self.file = open("tencent.txt", "w", encoding="utf-8")


    def process_item(self, item, spider):


        self.file.write(str(item) + "\r\n")
        self.file.flush()
        print(item)
        return item

    def __del__(self):
        self.file.close()

3、修改setting

# 連線redis,預設監聽127.0.0.1:6379
REDIS_URL = "redis://:[email protected]:6379"

修改dmoz.py

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

import scrapy
from bs4 import BeautifulSoup
from example import items
from scrapy.spiders import CrawlSpider, Rule  # 爬取規則
from scrapy.linkextractors import LinkExtractor  # 提取超連結

class MybaikeSpider(CrawlSpider):
    name = 'mybaike'
    allowed_domains = ['baike.baidu.com']
    start_urls = ['https://baike.baidu.com/item/Python/407313']

    rules = [Rule(LinkExtractor(allow=("item/.*")), callback="parse_page", follow=True)]

    # 獲取頁面資訊
    def getInf(self, pagedata):
        soup = BeautifulSoup(pagedata, "lxml")

        # 獲取主標題和副標題
        masterTitle = soup.select(".lemmaWgt-lemmaTitle-title > h1")

        if len(masterTitle) == 0:
            masterTitle = soup.select(".lemma-title-container > span")[0].get_text()
        else:
            masterTitle = masterTitle[0].get_text()
        secondTitle = soup.select(".lemmaWgt-lemmaTitle-title > h2")

        if len(secondTitle) == 0:
            secondTitle = "鎖定"
        else:
            secondTitle = secondTitle[0].get_text()

        # print(masterTitle, secondTitle)
        # 獲取文字
        content = soup.find_all("div", class_="lemma-summary")
        if len(content) == 0:
            content = soup.find_all("div", class_="summary-content")[0].get_text()
        else:
            content = content[0].get_text()
        # print(content)
        if len(masterTitle) == 0:
            masterTitle, secondTitle, content = '沒有'


        return masterTitle, secondTitle, content


    def parse_page(self, response):
        result = self.getInf(response.body)
        item = items.BaidubaikeItem()
        item["url"] = response.url
        item["masterTitle"] = result[0]
        item["secondTitle"] = result[1]
        item["content"] = result[2]
        yield item

修改myspider_redis

from scrapy_redis.spiders import RedisSpider
from example import items


class TencentSpider(RedisSpider):
"""Spider that reads urls from redis queue (myspider:start_urls)."""
  name = 'mybaike'
  redis_key = 'baike:start_urls'


  def __init__(self, *args, **kwargs):
    # Dynamically define the allowed domains list.
    domain = kwargs.pop('https://baike.baidu.com', '')
    self.allowed_domains = filter(None, domain.split(','))
    super(TencentSpider, self).__init__(*args, **kwargs)

  # 獲取頁面資訊
  def getInf(self, pagedata):
      soup = BeautifulSoup(pagedata, "lxml")

      # 獲取主標題和副標題
      masterTitle = soup.select(".lemmaWgt-lemmaTitle-title > h1")

      if len(masterTitle) == 0:
          masterTitle = soup.select(".lemma-title-container > span")[0].get_text()
      else:
          masterTitle = masterTitle[0].get_text()
          secondTitle = soup.select(".lemmaWgt-lemmaTitle-title > h2")

      if len(secondTitle) == 0:
        secondTitle = "鎖定"
      else:
        secondTitle = secondTitle[0].get_text()

      # print(masterTitle, secondTitle)
      # 獲取文字
      content = soup.find_all("div", class_="lemma-summary")
      if len(content) == 0:
        content = soup.find_all("div", class_="summary-content")[0].get_text()
      else:
        content = content[0].get_text()
      # print(content)
      if len(masterTitle) == 0:
        masterTitle, secondTitle, content = '沒有'

      return masterTitle, secondTitle, content

  def parse(self, response):
    result = self.getInf(response.body)
    item = items.BaidubaikeItem()
    item["url"] = response.url
    item["masterTitle"] = result[0]
    item["secondTitle"] = result[1]
    item["content"] = result[2]
    yield item

2、在Master端的redis-cli輸⼊push指令,參考格式

$redis> lpush baike:start_urls https://baike.baidu.com/item/Python/407313
$redis> lrange baike:start_urls 0 -1
$redis> keys *
  • dqdspider:request待爬佇列
  • dqdspider:dupefilter用來過濾重複的請求
  • dqdspider:items爬取的資訊內容

修改mycrawl_redis

新增/example/redis_client.py

使用帶密碼訪問redis資料庫(auth)

修改setting檔案

REDIS_URL = "redis://:123456[email protected]:6379"
#!C:\Python36\python.exe
# -*- coding:utf-8 -*-
import redis

myredis = redis.Redis(host="127.0.0.1", password="123456", port=6379)
print(myredis.info())
url = "https://baike.baidu.com/item/Python/407313"
myredis.lpush("baike_redis:start_urls", url)

from bs4 import BeautifulSoup
from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor
from scrapy_redis.spiders import RedisMixin
from scrapy.spiders import CrawlSpider
from scrapy_redis.spiders import RedisCrawlSpider
from example import items

class MyCrawler(RedisCrawlSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'mybaike_redis'
    redis_key = 'baike:start_urls'

    rules = [Rule(LinkExtractor(allow=("item/.*")), callback="parse_page", follow=True)]

    def set_crawler(self, crawer):
        CrawlSpider.set_crawler(self, crawer)  # 設定預設爬去
        RedisMixin.setup_redis(self)  # url由redis

    # 獲取頁面資訊
    def getInf(self, pagedata):
        soup = BeautifulSoup(pagedata, "lxml")

        # 獲取主標題和副標題
        masterTitle = soup.select(".lemmaWgt-lemmaTitle-title > h1")

        if len(masterTitle) == 0:
            masterTitle = soup.select(".lemma-title-container > span")[0].get_text()
        else:
            masterTitle = masterTitle[0].get_text()
        secondTitle = soup.select(".lemmaWgt-lemmaTitle-title > h2")

        if len(secondTitle) == 0:
            secondTitle = "鎖定"
        else:
            secondTitle = secondTitle[0].get_text()

        # print(masterTitle, secondTitle)
        # 獲取文字
        content = soup.find_all("div", class_="lemma-summary")
        if len(content) == 0:
            content = soup.find_all("div", class_="summary-content")[0].get_text()
        else:
            content = content[0].get_text()
        # print(content)
        if len(masterTitle) == 0:
            masterTitle, secondTitle, content = '沒有'

        return masterTitle, secondTitle, content

    def parse_page(self, response):
        result = self.getInf(response.body)
        item = items.BaidubaikeItem()
        item["url"] = response.url
        item["masterTitle"] = result[0]
        item["secondTitle"] = result[1]
        item["content"] = result[2]
        yield item

處理redis資料庫內容(dupefilter)

1.存入mysql


# baike_mysql.py
import json
import redis
import MySQLdb

def main():
    # 指定redis資料庫資訊
    rediscli = redis.StrictRedis(host='127.0.0.1', port=6379, password='123456', db=0)
    # 指定mysql資料庫
    mysqlcli = MySQLdb.connect(host='127.0.0.1', user='root', passwd='123456', db='fate', port=3306,
                               charset='utf8')
    print(rediscli)
    while True:
        # FIFO模式為 blpop,LIFO模式為 brpop,獲取鍵值
        source, data = rediscli.blpop(["mybaike_redis:items"])
        item = json.loads(data)
        print(item)
        try:
            # 使用cursor()方法獲取操作遊標
            cur = mysqlcli.cursor()
            sql = 'INSERT INTO BAIKE(url,masterTitle,secondTitle,content)  \
                         VALUES("%s","%s","%s","%s")' % (
            item["url"], item["masterTitle"], item["secondTitle"], item["content"])
            print(sql)
            cur.execute(sql)
            mysqlcli.commit()
            # 關閉本次操作
            cur.close()
            print("inserted %s" % item['source_url'])
        except MySQLdb.Error as e:
            print("Mysql Error %d: %s" % (e.args[0], e.args[1]))

if __name__ == '__main__':
    main()

2.存入MongoDB

存入MongoDB
baike_mongodb.py
exit: Ctrl + ↩
# -*- coding: utf-8 -*-

import json
import redis
import pymongo

def main():

    # 指定Redis資料庫資訊
    rediscli = redis.StrictRedis(host='192.168.199.108', port=6379, db=0)
    # 指定MongoDB資料庫資訊
    mongocli = pymongo.MongoClient(host='localhost', port=27017)

    # 建立資料庫名
    db = mongocli['baidu']
    # 建立表名
    sheet = db['baike']

    while True:
        # FIFO模式為 blpop,LIFO模式為 brpop,獲取鍵值
        source, data = rediscli.blpop(["mybaike_redis:items"])

        item = json.loads(data)
        sheet.insert(item)

        try:
            print("inserted %s" % item['source_url'])

        except KeyError:
            print("Error procesing: %r" % item)

if __name__ == '__main__':
    main()

例項2

1、建立工程

scrapy startproject people
cd people
scrapy genspider mypeople people.com.cn

2、修改items.py

class PeopleItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    #新聞標題、時間、url、文章內容
    newsTitle = scrapy.Field()
    newsTime = scrapy.Field()
    newsUrl = scrapy.Field()
    article = scrapy.Field()
    pass

3、修改spider資料夾下的mypeople.py

import scrapy

from people.items import PeopleItem
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from scrapy_redis.spiders import RedisCrawlSpider


class MypeopleSpider(RedisCrawlSpider):
    name = 'mypeople'
    allowed_domains = ['people.com.cn']
    # start_urls = ['http://politics.people.com.cn/GB/1024/index1.html']
    redis_key = "mypeople:start_url"

    rules = (Rule(LinkExtractor(allow=("index(\d+).html")), callback="get_parse", follow=True),)

    def get_parse(self, response):
        newsList = response.xpath('//ul/li')
        print(len(newsList))

        for news in newsList:
            newsTitle = news.xpath('./a/text()').extract()[0]
            newsTime = news.xpath('./em/text()').extract()[0]
            newsUrl = "http://politics.people.com.cn" + news.xpath('./a/@href').extract()[0]

            print(newsUrl, response.url)

            request = scrapy.Request(url=newsUrl, callback=self.get_article)

            request.meta['newsTitle'] = newsTitle
            request.meta['newsTime'] = newsTime
            request.meta['newsUrl'] = newsUrl

            yield request

    def get_article(self, response):

        articleList = response.xpath('//div[@id="rwb_zw"]//text()')
        article = ""
        for a in articleList:
            article += a.extract().strip()

        item = PeopleItem()
        # 新聞標題、時間、url、文章內容
        item['newsTitle'] = response.meta['newsTitle']
        item['newsTime'] = response.meta['newsTime']
        item['newsUrl'] = response.meta['newsUrl']
        item['article'] = article

        yield item

4、修改settings.py

增加4行程式碼

# 分散式配置
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True

REDIS_URL = "redis://:[email protected]:6379"

下面的也可以增加,表示把資料儲存在redis中

ITEM_PIPELINES = {
   'people.pipelines.PeoplePipeline': 300,
   'scrapy_redis.pipelines.RedisPipeline': 400, # 通向redis

}

5、在cmd命令視窗輸入

開啟redis服務,然後在另一個cmd視窗輸入:

 >>>redis-cli
 >>>auth 123456   
 >>>lpush mypeople:start_url http://politics.people.com.cn/GB/1024/index1.html

相關推薦

利用scrapy-redis實現分散式爬蟲

環境要求 Python 2.7, 3.4 or 3.5 Redis >= 2.8 Scrapy >= 1.1 redis-py >= 2.10 1. 先安裝scrapy-redis sudo pip3 in

使用 scrapy-redis實現分散式爬蟲

Scrapy 和 scrapy-redis的區別 Scrapy 是一個通用的爬蟲框架,但是不支援分散式,Scrapy-redis是為了更方便地實現Scrapy分散式爬取,而提供了一些以redis為基礎的元件(僅有元件)。 pip install scr

基於Python+scrapy+redis分散式爬蟲實現框架

        爬蟲技術,無論是在學術領域,還是在工程領域,都扮演者非常重要的角色。相比於其他技術,爬蟲技術雖然在實現上比較簡單,沒有那麼多深奧的技術難點,但想要構建一套穩定、高效、自動化的爬蟲框架,也並不是一件容易的事情。這裡筆者打算就個人經驗,介紹一種分散式爬蟲框架的實

使用Scrapy-redis實現分散式爬取

Scrapy是一個比較好用的Python爬蟲框架,你只需要編寫幾個元件就可以實現網頁資料的爬取。但是當我們要爬取的頁面非常多的時候,單個主機的處理能力就不能滿足我們的需求了(無論是處理速度還是網路請求的併發數),這時候分散式爬蟲的優勢就顯現出來。 而Scrapy-Redis則是一個基於Redis的

python下使用scrapy-redis模組分散式爬蟲爬蟲專案部署詳細教程————————gerapy

1.使用gerapy進行分散式爬蟲管理 準備工作: 首先將你使用scrapy-redis寫的分散式爬蟲全部完善 模組準備: 安裝: pip install pymongo【依賴模組】 pip install gerapy  2.在本地建立部署專案的資料夾

scrapy | scrapy-redis實現分散式爬取:原理,實戰案例(虛擬機器)

1.概念:分散式爬蟲 由於需要爬取的資料量大,任務多,一臺機器效率太低,需要多臺機器共同協作處理。分散式爬蟲將多臺主機組合起來, 共同完成一個爬取任務,快速高效地提高爬取效率。 分散式爬蟲可以分為若干個分散式層級,不同的應用可能由其中部分層級構成。 大型分散式爬蟲主

使用scrapy-redis實現分散式

  要實現分散式,主機之間需要共享爬取佇列和去衝擊和,scrapy_redis就是將request排程佇列、請求佇列和獲取的item放在了一個多臺主機可以同時訪問的Redis資料庫中。   剖析原始碼的工作請根據需要自行學習,直接給出settings中的配置。

scrapy-redis實現爬蟲分散式爬取分析與實現

一 scrapy-redis實現分散式爬取分析 所謂的scrapy-redis實際上就是scrapy+redis其中對redis的操作採用redis-py客戶端。這裡的redis的作用以及在scrapy-redis的方向我在自己fork的repository(連結:htt

Scrapy基於scrapy_redis實現分散式爬蟲部署

準備工作1.安裝scrapy_redis包,開啟cmd工具,執行命令pip install scrapy_redis2.準備好一個沒有BUG,沒有報錯的爬蟲專案3.準備好redis主伺服器還有跟程式相關的mysql資料庫前提mysql資料庫要開啟允許遠端連線,因為mysql安

scrapy-redis實現scrapy分散式爬取分析

(1)在“http://www.zhihu.com/question/20899988”中,提到的: “那麼,假設你現在有100臺機器可以用,怎麼用python實現一個分散式的爬取演算法呢? 我們把這100臺中的99臺運算能力較小的機器叫作slave,另外一臺較大的機器叫作

springboot+redis實現分散式session共享

    官方文件,它是spring session專案的redis相關的一個子文件:https://docs.spring.io/spring-session/docs/2.0.0.BUILD-SNAPSHOT/reference/html5/guides/boot

redis】使用redisTemplate優雅地操作redis及使用redis實現分散式

前言: 上篇已經介紹了redis及如何安裝和叢集redis,這篇介紹如何通過工具優雅地操作redis. Long Long ago,程式猿們還在通過jedis來操作著redis,那時候的猿類,一個個累的沒日沒夜,重複的造著輪子,忙得沒時間陪家人,終於有一天猿類的春天來了,spring家族的r

爬蟲(三) redis&分散式爬蟲

redis redis, 稱為記憶體資料庫, 以key-value的形式存放資料, 是一個非關係型資料庫 redis 提供類豐富的資料型別, 其有 string list map set sortSet 五種資料型別 redis 的資料型別指的是value的

基於Redis實現分散式

背景 在很多網際網路產品應用中,有些場景需要加鎖處理,比如:秒殺,全域性遞增ID,樓層生成等等。大部分的解決方案是基於DB實現的,Redis為單程序單執行緒模式,採用佇列模式將併發訪問變成序列訪問,且多客戶端對Redis的連線並不存在競爭關係。其次Redis提供一些命令SETNX,GETSET,可以方便

利用Redis實現分散式鎖 使用mysql樂觀鎖解決併發問題

寫在最前面 我在之前總結冪等性的時候,寫過一種分散式鎖的實現,可惜當時沒有真正應用過,著實的心虛啊。正好這段時間對這部分實踐了一下,也算是對之前填坑了。 分散式鎖按照網上的結論,大致分為三種:1、資料庫樂觀鎖; 2、基於Redis的分散式鎖;3.、基於ZooKeeper的分散式鎖; 關於樂觀鎖的實現其實

教你用 redis 實現分散式冪等服務中介軟體

背景 在程式設計領域,冪等性是指對同一個系統,使用同樣的條件,一次請求和重複的多次請求對系統資源的影響是一致的。 在分散式系統裡,client 呼叫 server 提供的服務,由於網路環境的複雜性,呼叫可能有以下幾種情況: server 收到 client 的請求,client 也收到

利用Redis實現分散式

為什麼需要分散式鎖? 在傳統單體應用單機部署的情況下,可以使用Java併發相關的鎖,如ReentrantLcok或synchronized進行互斥控制。但是,隨著業務發展的需要,原單體單機部署的系統,漸漸的被部署在多機器多JVM上同時提供服務,這使得原單機部署情況下的併發控制鎖策略失效了,為了解決這個問

如何用 Redis 實現分散式鎖和超時情況處理

目前各種分散式的架構和微服務架構無處不在,在這種類似架構中處理併發和分散式併發問題,本場 Chat 就主要以 Redis 為例,使用分散式鎖的方式如何處理併發問題和避免超時情況的出現,主要從以下幾個方面講述: Redis 的 Setnx 命令是如何實現分散式鎖的; Setnx 的實現鎖的

scrapy-redis增量式爬蟲

1 在scrapy爬蟲的框架上setting.py中加上這四句 DUPEFILTER_CLASS = “scrapy_redis.dupefilter.RFPDupeFilter” #指定了排程器的類 SCHEDULER = “scrapy_redis.scheduler.Schedul

基於Scrapy-Redis分散式以及cookies池

基於Scrapy-Redis的分散式以及cookies池   轉載自:靜覓 » 小白進階之Scrapy第三篇(基於Scrapy-Redis的分散式以及cookies池) ==================================================