天天看點

scrapy-redis分布式爬蟲學習記錄

目錄

1. scrapy-redis是什麼?

2. scrapy-redis工作原理

3.分布式架構

4. scrapy-redis的源碼分析

5. 部署scrapy-redis

6. scrapy-redis的基本使用

6.1 redis資料庫基本表項

6.2 在scrapy項目的基礎進行更改

7. redis資料轉存入mysql資料庫

課程推薦:day07_01.scrapy-redis官方案例示範_recv_哔哩哔哩_bilibili

1. scrapy-redis是什麼?

scrapy是一個通用的爬蟲架構,但是不支援分布式,

scrapy-Redis是為了更友善地實作scrapy分布式爬取,而提供了一些Tredis為基礎的元件(僅有元件)

2. scrapy-redis工作原理

scrapy-redis分布式爬蟲學習記錄

與scrapy架構不同的是,scrapy-redis架構中request連結不再傳遞于排程器(Scheduler)中url隊列,而是儲存在redis資料庫中,再由過濾器進行過濾,符合要求的請求連結再傳遞于排程器(Scheduler),此外redis資料還可以存儲到本地資料庫(item processes)。其餘流程與scrapy流程基本相同。

(1) 引擎(Scrapy Engine)向爬蟲(Spiders)請求第一個要爬取的URL。

(2) 引擎從爬蟲中擷取到第一個要爬取的URL,封裝成請求(Request)并交給排程器(Scheduler)。

(3) 排程器通路Redis資料庫對請求進行判重,如果不重複,就把這個請求添加到Redis中。

(4) 當排程條件滿足時,排程器會從Redis中取出Request,交給引擎,引擎将這個Request通過下載下傳中間件轉發給下載下傳器。

(5) 一旦頁面下載下傳完畢,下載下傳器(Downloader)生成一個該頁面的響應(Response),并将其通過下載下傳中間件發送給引擎。

(6) 引擎從下載下傳器中接收到響應,并通過爬蟲中間件(Spider Middlewares)發送給爬蟲處理。

(7) Spider處理Response,并傳回爬取到的Item及新的Request給引擎。

(8) 引擎将爬取到的Item通過Item Pipeline給Redis資料庫,将Request給排程器。從(2) 開始重複,直到排程器中沒有更多的Request為止。

3.分布式架構

scrapy-redis分布式爬蟲學習記錄
 這種架構,主從式架構主要由一台url提供主機(master)向資源爬取主機(slave)提供url,由資源爬取主機依照url爬取相應的資源。這也是scrapy-redis的分布式模式。

4. scrapy-redis的源碼分析

(1) 源碼位址

GitHub - rmax/scrapy-redis: Redis-based components for Scrapy.

(2) 源碼案例詳解

example-project為源碼給予的案例,我們使用scrapy-redis以此為例。我們僅需分析example-project下的檔案即可。與scrapy相同,scrapy-redis主要檔案也是由items檔案、pipelines檔案、setting檔案和爬蟲檔案組成。

items檔案: 與scrapy一樣,為爬蟲檔案的資料結構

from scrapy.item import Item, Field
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose, TakeFirst, Join

# 與scrapy一樣,為爬蟲檔案的資料結構
class ExampleItem(Item):
    name = Field()
    description = Field()
    link = Field()
    crawled = Field()
    spider = Field()
    url = Field()

# 與scrapy中的ItemLoader作用一緻
class ExampleLoader(ItemLoader):
    default_item_class = ExampleItem
    default_input_processor = MapCompose(lambda s: s.strip())
    default_output_processor = TakeFirst()
    description_out = Join()
           

pipelines檔案: 沒有特殊需要,pipelines檔案不再需要寫任何的語句,return會自動将item資料傳入資料庫。

from datetime import datetime


class ExamplePipeline(object):
    def process_item(self, item, spider):
        # 時間戳
        item["crawled"] = datetime.utcnow()
        # 爬蟲名,分布式爬蟲有多個爬蟲,規定爬蟲名易于區分
        item["spider"] = spider.name

        return item
           

setting檔案: scrapy_redis所必須的設定

SPIDER_MODULES = ['example.spiders']
NEWSPIDER_MODULE = 'example.spiders'

USER_AGENT = 'scrapy-redis (+https://github.com/rolando/scrapy-redis)'

# 去重類,使用scrapy_redis中的去重類
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 排程器,使用scrapy_redis中的排程器元件
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 是否允許暫停,redis中請求記錄不丢失
SCHEDULER_PERSIST = True

# 預設的scrapy-redis請求集合
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"

# 隊列形式,請求url會先進先出
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"

# 棧形式,請求url會先進後出
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"

# 'scrapy_redis.pipelines.RedisPipeline' 支援存儲到redis資料庫中,必須開啟
ITEM_PIPELINES = {
    'example.pipelines.ExamplePipeline': 300,
    # redis管道,必須設定,不然資料無法傳入資料庫
    'scrapy_redis.pipelines.RedisPipeline': 400,
}

# 日志等級
LOG_LEVEL = 'DEBUG'

# 下載下傳延遲
DOWNLOAD_DELAY = 1

# redis配置
# 指定資料庫ip
REDIS_HOST = "127.0.0.1"
# 指定資料庫端口号
REDIS_PORT = 6379
           

爬蟲檔案(三個不同類型的樣例):

domz.py:  非分布式,有連結提取器

# domz.py

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

# 非分布式,有連結提取器
# 使用的是CrawlSpider連結提取器
class DmozSpider(CrawlSpider):
    """Follow categories and extract links."""
    name = 'dmoz'
    allowed_domains = ['dmoz-odp.org']
    start_urls = ['http://www.dmoz-odp.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(),
            }
           

mycrawler_redis.py:  分布式,有連結提取器

# mycrawler_redis.py

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

from scrapy_redis.spiders import RedisCrawlSpider


class MyCrawler(RedisCrawlSpider):
    name = 'mycrawler_redis'
    # 分布式爬蟲開啟指令,第一個要爬前的url,與資料庫中格式需一緻
    redis_key = 'mycrawler:start_urls'

    # 提取連結規則
    rules = (
        # follow all links
        Rule(LinkExtractor(), callback='parse_page', follow=True),
    )

    # 動态域名,與allowed_domains作用一緻,規定可爬url域,可設定多個可爬域
    # __init__方法必須按規定寫,使用時隻需修改super()裡的類名參數即可
    def __init__(self, *args, **kwargs):
        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,
        }
           

myspider_redis.py:  分布式,無連結提取器

from scrapy_redis.spiders import RedisSpider

# 沒有連結提取器
class MySpider(RedisSpider):
    """Spider that reads urls from redis queue (myspider:start_urls)."""
    name = 'myspider_redis'
    # 分布式爬蟲開啟指令,第一個要爬前的url,與資料庫中格式需一緻
    redis_key = 'myspider:start_urls'

    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,
        }
           

5. 部署scrapy-redis

(1) slave端(2台linux系統)

軟體配置:python 3 + pip + scrapy + scrapy-redis

安裝linux系統:https://shuijinglan.blog.csdn.net/article/details/105604065

配置yum網絡源:https://pokes.blog.csdn.net/article/details/124627065

安裝python 3(版本要9.9):Linux系統安裝Python3環境(超詳細) - 知乎

安裝scrapy-redis:

pip install scrapy-redis -i https://pypi.douban.com/simple

(2)master端(windows系統)

軟體配置: redis + scrapy + scrapy-redis

redis下載下傳:

https://github.com/tporadowski/redis/releases/download/v5.0.14.1/Redis-x64-5.0.14.1.zip

redis.windows.conf配置:

1. 在redis.windows.conf檔案中,禁用bind 127.0.0.1

2. 在redis.windows.conf檔案中,将daemonize no改為yes

啟動redis:先運作redis-server再運作redis-cli

永久啟動方法:

Redis下載下傳和安裝(Windows系統)

6. scrapy-redis的基本使用

6.1 redis資料庫基本表項

爬蟲名:dupelines        # 已過濾的連結,已爬過的連結不再爬取

爬蟲名:items               # 爬取的資料,對應items資料項

爬蟲名:requests          # 請求連結,要爬的url

爬蟲名:start_urls         # 分布式爬蟲開啟指令,第一個要爬的url,與資料庫中格式需一緻

6.2 在scrapy項目的基礎進行更改

(1) 在settings中添加配置資訊

# scrapy-redis
USER_AGENT = 'scrapy-redis (+https://github.com/rolando/scrapy-redis)'

# 過濾配置
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 排程引擎
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 是否允許暫停
SCHEDULER_PERSIST = True

# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"
      
ITEM_PIPELINES = {
    'DistrubutedSpider.pipelines.DistrubutedSpiderPipeline': 300,
    # redis管道,item資料直接入庫,必須添加
    'scrapy_redis.pipelines.RedisPipeline': 400,
}      

(2)更改爬蟲檔案

第一種:帶有連結提取器

# 讀書網案例
from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor
from scrapy_redis.spiders import RedisCrawlSpider

# 帶有連結提取器
class slaveSpider(RedisCrawlSpider):
    name = 'slave'
    # 起始連接配接,放在資料庫中第一批要爬取得連結,Requests從這批連結的頁面中擷取
    redis_key = 'slave:start_urls'

    # 提取連結規則
    rules = (
        Rule(LinkExtractor(allow=r'/book/1188_\d+\.html'), callback='parse_item', follow=False),
    )

    # 類似allow_domain,準許多個域名域
    # 動态域,__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(Master02Spider, self).__init__(*args, **kwargs)

    def parse_item(self,response):
        # xpath
        img_list = response.xpath('//ul/li/div[@class="book-info"]/div/a')
        for i in img_list:
            src = i.xpath('./img/@data-original').extract_first()
            name = i.xpath('./img/@alt').extract_first()
            # item
            yield {'src': src, 'name': name}
           

(3)運作程式

在2台linux虛拟機中分别部署爬蟲檔案,運作,并在redis輸入start_urls  。

分布式爬蟲檔案啟動指令:

scrapy runspider 爬蟲檔案名.py

 例:scrapy runspider slave.py

進入Redis資料庫輸入:

lpush 爬蟲檔案名:start_urls   要爬的第一條連結

例:lpush slave:start_urls https://www.dushu.com/book/1188_01.html

7. redis資料轉存入mysql資料庫

(1)安裝MySQLdb (python版本為3.7)

pip install mysqlclient==1.3.14

(2)redis資料轉存入mysql資料庫

# !/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
import MySQLdb
import json

def process_item():
    while(True):
        # 1.建立redis資料庫連結
        redis_con = redis.Redis(host="127.0.0.1",port=6379,db=0)
        # 2.建立mysql資料庫連結
        mysql_con = MySQLdb.connect(host="127.0.0.1",port=3306,user="root",password="123456",db="spider01", charset="utf8")
        # 3.将資料從redis資料庫中pop出來
        source,data = redis_con.blpop("demo02:items")
        # 4.json轉換字元串
        item = json.loads(data)
        ## mysql
        # 5.建立mysql的遊标對象,執行sql語句
        cursor = mysql_con.cursor()
        # 6.sql語句
        sql = 'insert into book(name,src) values ("{}","{}")'.format(item['name'],item['src'])
        # 7.執行sql,%等同于格式化語句.format()
        cursor.execute(sql)
        # 8.送出
        mysql_con.commit()
        # 9.結束
        cursor.close()

if __name__ == '__main__':
    process_item()