跳到主要内容

scrapy分布式爬虫

目录

    做一下分布式爬虫的教程

    目前的scrapy分布式爬虫一般都是基于redis数据库的分布式爬虫。

    并且真正的分布式是在多台机器上运行同一个项目,但是应该没有富哥/富婆有多台机器(电脑)。

    所以还剩下两种方案:

    1.直接在你电脑主机上(windows系统)上写一个分布式项目,然后同时运行多次这个项目(相当于多进程)

    2.搞虚拟机来模拟多台机器

    由于我电脑硬盘空间十分滴宝贵(没空间了),所以我使用第一种方法。

    开始准备:下载redis数据库

    redis数据库是一种类似mongodb的非关系型数据库,优点就是实时性以及速度非常滴快,占用空间也很小。

    下面是下载链接:

    redis-2.8.9: https://pan.baidu.com/s/1uRleT7jweEoays2yqekh-w?pwd=m9kw

    只有redis数据库还是不够的,我们需要一个有操作界面的工具软件,类似mysql我们使用navicat来连接操控,毕竟没人喜欢用数据库自带的命令行一行一行的敲命令吧。

    Tiny RDM :https://pan.baidu.com/s/1eyDsKGwGEgv7E24qVEvpiw?pwd=e5a6

    redis数据库下载后是一个压缩包,解压后会出现一堆.exe文件,因为我们只是简单使用,所以不需要修改redis的配置(conf文件),直接双击 redis-server.exe 文件就启动了redis数据库。

    图片占位符 1

    然后就可以使用工具来连接redis数据库来看看数据库是否正常运行,Tiny RDM软件不需要安装,双击打开可以直接使用。

    图片占位符 2

    点击添加新连接。

    图片占位符 3

    因为我们是在本地运行多个分布式爬虫项目,所以没有配置redis数据库的远程连接,并且每有配置密码,所以连接地址默认使用127.0.0.1本地回环,端口也是默认6379,密码不需要填写,只需要填写一个连接名就可以了。

    图片占位符 4

    这是连接成功后的界面,如果连接失败看看自己是不是把redis服务启动后弹出的终端窗口关闭了,这个终端窗口在redis运行期间是不能关闭的。


    redis数据库配置完成,目前我们还用不到它,我们先去创建scrapy项目

    在PyCharm中创建一个新的项目,然后在左下角找到终端按钮打开,然后安装scrapy的库:pip install scrapy

    图片占位符 5

    图片占位符 6

    然后创建一个scrapy的项目:scrapy startproject douban

    然后进入scrapy项目的文件夹中:cd douban

    创建一个示例爬虫代码文件:scrapy genspider douban250 douban.com

    图片占位符 7

    先修改一下基础的示例代码文件,然后运行项目:scrapy crawl douban250

    测试一下是否能成功访问豆瓣250。

    图片占位符 8

    我们可以发现返回的状态码为空(当状态码为200时才是成功访问),原因是因为我们还没有进行访问头user-agent的配置,所以豆瓣250禁止我们访问,我们需要去修改settings.py配置文件。

    图片占位符 9

    USER_AGENT为访问头

    同时我们发现输出中有大量的DEBUG信息和INFO信息,但是这些信息只是日志信息,既不是报错,也不是我们需要的信息,所以使用LOG_LEVEL来设置日志等级,设置为”WARNING”即只有警告以及报错以上的信息才会提示你。

    同时我们可以顺便进行一下scrapy连接redis数据库的相关配置。

    图片占位符 10

    以及把管道打开(把ITEM_PIPELINES前面的注释删除掉),并且因为要使用scrapy-redis,所以还要把redis的管道加上

    REDIS_HOST:redis数据库的连接地址,因为在本地,所以使用127.0.0.1

    REDIS_PORT:为redis数据库服务用的端口号

    REDIS_DB:为要使用redis的哪个数据库(初始默认有15个数据库,这里使用7号数据库)

    REDIS_PASSWORD:redis数据库的密码,咱们没有配置密码,所以这一项可以不用添加

    下面三行是使用redis的调度器替换scrapy调度器列表的相关配置

    相关配置的文本

    LOG_LEVEL = "WARNING"
    # Crawl responsibly by identifying yourself (and your website) on the user-agent
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
    DOWNLOAD_DELAY = 2  # 设置请求之间的延迟为 2 秒
    # redis相关配置
    REDIS_HOST = '127.0.0.1'
    REDIS_PORT = 6379
    REDIS_DB = 7
    # REDIS_PASSWORD = ''
    
    # scrapy_redis相关配置
    SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
    SCHEDULER_PERSIST = True  # 如果为真,则在关闭时自动保存请求信息
    DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'  # 去重的逻辑,要使用redis的
    ITEM_PIPELINES = {
        'scrapy_redis.pipelines.RedisPipeline': 301,
        "douban.pipelines.DoubanPipeline": 300,
    }

    不知道上面的配置文件怎么搞?没事,可以直接复制下面的完整文件覆盖掉你的settings.py文件

    # Scrapy settings for douban project
    #
    # For simplicity, this file contains only settings considered important or
    # commonly used. You can find more settings consulting the documentation:
    #
    #     https://docs.scrapy.org/en/latest/topics/settings.html
    #     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
    #     https://docs.scrapy.org/en/latest/topics/spider-middleware.html
    
    BOT_NAME = "douban"
    
    SPIDER_MODULES = ["douban.spiders"]
    NEWSPIDER_MODULE = "douban.spiders"
    
    LOG_LEVEL = "WARNING"
    # Crawl responsibly by identifying yourself (and your website) on the user-agent
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
    DOWNLOAD_DELAY = 2  # 设置请求之间的延迟为 2 秒
    
    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False
    
    # Configure maximum concurrent requests performed by Scrapy (default: 16)
    # CONCURRENT_REQUESTS = 32
    
    # Configure a delay for requests for the same website (default: 0)
    # See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
    # See also autothrottle settings and docs
    # DOWNLOAD_DELAY = 3
    # The download delay setting will honor only one of:
    # CONCURRENT_REQUESTS_PER_DOMAIN = 16
    # CONCURRENT_REQUESTS_PER_IP = 16
    
    # Disable cookies (enabled by default)
    # COOKIES_ENABLED = False
    
    # Disable Telnet Console (enabled by default)
    # TELNETCONSOLE_ENABLED = False
    
    # Override the default request headers:
    # DEFAULT_REQUEST_HEADERS = {
    #    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    #    "Accept-Language": "en",
    # }
    
    # Enable or disable spider middlewares
    # See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
    # SPIDER_MIDDLEWARES = {
    #    "douban.middlewares.DoubanSpiderMiddleware": 543,
    # }
    
    # Enable or disable downloader middlewares
    # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
    # DOWNLOADER_MIDDLEWARES = {
    #    "douban.middlewares.DoubanDownloaderMiddleware": 543,
    # }
    
    # Enable or disable extensions
    # See https://docs.scrapy.org/en/latest/topics/extensions.html
    # EXTENSIONS = {
    #    "scrapy.extensions.telnet.TelnetConsole": None,
    # }
    
    # Configure item pipelines
    # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    ITEM_PIPELINES = {
        'scrapy_redis.pipelines.RedisPipeline': 301,
        "douban.pipelines.DoubanPipeline": 300,
    }
    
    # redis相关配置
    REDIS_HOST = '127.0.0.1'
    REDIS_PORT = 6379
    REDIS_DB = 7
    # REDIS_PASSWORD = ''
    
    # scrapy_redis相关配置
    SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
    SCHEDULER_PERSIST = True  # 如果为真,则在关闭时自动保存请求信息
    DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'  # 去重的逻辑,要使用redis的
    
    # Enable and configure the AutoThrottle extension (disabled by default)
    # See https://docs.scrapy.org/en/latest/topics/autothrottle.html
    # AUTOTHROTTLE_ENABLED = True
    # The initial download delay
    # AUTOTHROTTLE_START_DELAY = 5
    # The maximum download delay to be set in case of high latencies
    # AUTOTHROTTLE_MAX_DELAY = 60
    # The average number of requests Scrapy should be sending in parallel to
    # each remote server
    # AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
    # Enable showing throttling stats for every response received:
    # AUTOTHROTTLE_DEBUG = False
    
    # Enable and configure HTTP caching (disabled by default)
    # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
    # HTTPCACHE_ENABLED = True
    # HTTPCACHE_EXPIRATION_SECS = 0
    # HTTPCACHE_DIR = "httpcache"
    # HTTPCACHE_IGNORE_HTTP_CODES = []
    # HTTPCACHE_STORAGE = "scrapy.extensions.httpcache.FilesystemCacheStorage"
    
    # Set settings whose default value is deprecated to a future-proof value
    TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
    FEED_EXPORT_ENCODING = "utf-8"

    然后再回到爬虫代码

    图片占位符 11

    图片占位符 12

    图片占位符 13

    由于我在代码中使用了scrapy自带的items的数据结构,所以要将items.py导入到代码文件中。

    图片占位符 14

    右键scrapy项目”豆瓣”选择将其标记为源代码根目录

    图片占位符 15

    然后在代码中导入items.py

    图片占位符 16

    前面忘了说了,还需要导入scrapy-redis的第三方库

    下载命令:pip install scrapy-redis

    完整的爬虫douban250.py代码

    import scrapy
    from douban.items import DoubanItem
    from scrapy_redis.spiders import RedisSpider, RedisCrawlSpider
    
    class Douban250Spider(RedisSpider):  # 换成RedisSpider
        name = "douban250"
        allowed_domains = ["douban.com"]
        # start_urls = ["https://movie.douban.com/top250"]
        redis_key = "douban_start_url"  # 开始链接换成redis_key
    
        def parse(self, response, **kwargs):
            # 进入电影详情页
            lis = response.xpath("//ol[@class='grid_view']/li")
            for li in lis:
                href = li.xpath("./div/div[2]/div[1]/a/@href").extract_first()
                # 不用管是否重复,判断工作交给scrapy_redis完成
                yield scrapy.Request(url=href, callback=self.parse_detail)
    
            # 继续爬取下一页电影详情页的url
            next_page = response.xpath("//*[@id='content']/div/div[1]/div[2]/span[3]/a/@href").extract_first()
            yield scrapy.Request(url=response.urljoin(next_page), callback=self.parse)
    
        def parse_detail(self, response, **kwargs):
            douban = DoubanItem()
            name = response.xpath("//*[@id='content']/h1/span[1]/text()").extract_first()
            date = response.xpath("//*[@id='content']/h1/span[2]/text()").extract_first()
            daoyan = response.xpath("//*[@id='info']/span[1]/span[2]/a/text()").extract_first()
            bianjus = response.xpath("//*[@id='info']/span[2]/span[2]/a")
            bianju = ""
            for i in bianjus:
                test = i.xpath("./text()").extract_first()
                bianju += test + "/"
            pingfen = response.xpath("//*[@id='interest_sectl']/div[1]/div[2]/strong/text()").extract_first()
            pingjiarenshu = response.xpath(
                "//*[@id='interest_sectl']/div[1]/div[2]/div/div[2]/a/span/text()").extract_first()
    
            douban["name"] = name
            douban["date"] = date
            douban["daoyan"] = daoyan
            douban["bianju"] = bianju
            douban["pingfen"] = pingfen
            douban["pingjiarenshu"] = pingjiarenshu
    
            yield douban

    完整的items.py代码

    # Define here the models for your scraped items
    #
    # See documentation in:
    # https://docs.scrapy.org/en/latest/topics/items.html
    
    import scrapy
    
    class DoubanItem(scrapy.Item):
        # define the fields for your item here like:
        name = scrapy.Field()  # 电影名
        date = scrapy.Field()  # 首映时间
        daoyan = scrapy.Field()  # 导演
        bianju = scrapy.Field()  # 编剧
        pingfen = scrapy.Field()  # 豆瓣评分
        pingjiarenshu = scrapy.Field()  # 评价人数

    开始启动项目

    1.先启动redis数据库服务

    2.打开连接redis数据库的软件

    3.切换到7号数据库

    图片占位符 17

    4.使用终端启动多个scrapy爬虫项目

    先使用 cd douban 进入项目文件夹

    然后使用命令启动项目:scrapy crawl douban250

    图片占位符 18

    图片占位符 19

    运行项目后你会发现终端里面一直是空白的,无论过多久都没有任何信息,这是正常的,因为爬虫还没有真正的开始运行,去查看一下代码

    图片占位符 20

    可以看到爬虫的开始url被注释掉了,因为我们需要完成分布式的爬取,也就是说多台设备共同做一个爬虫任务,那么scrapy本身自带的调度器我们就通过redis将其“合并”到了一起,多台设备共用一个用于存取需要爬取的url的列表,这个列表位于redis的7号数据库中,目前这个列表是空的,所以爬虫任务不能开始。

    我们需要将第一个要爬的页面的url添加到调度器列表当中。

    键:douban_start_url

    元素:https://movie.douban.com/top250

    图片占位符 21

    这是在分别查看两个终端,两个终端都开始输出爬取的电影信息,且不一样(代表着两个进程在爬取不同的电影,最后都会将爬取到的信息返回到redis数据库中,完成了分布式爬虫的目的)

    可以在redis的7号数据库中查看爬取到的电影信息

    图片占位符 22

    图片占位符 23

    什么,你报错了?

    那就对了,我也报错,报错内容如下

    图片占位符 24

    报错原因是因为scrapy-redis新版本中不用string了,推荐你使用json类型的数据,这里有三种解决办法:

    1.按照人家的推荐把url相关的数据换成json类型(气抖冷,居然不让我想用什么数据用什么数据,当然不能惯着你,我就不用json)

    2.先往redis中添加开始url,然后再启动项目(欸!官方代码更新了,但是没更新完全!先添加开始url再启动项目他就不报错正常运行了)

    3.使用老版本的scrapy库,先pip uninstall scrapy卸载

    然后再 pip install scrapy==2.90 下载老版本的库来使用(没办法,我跟着学的是21年的课程,那时候的库都是老版本,我只会些老版本的语法)

    推荐使用第二种方法,啥也不用管,换个顺序就行了


    注意本文代码只适用于本爬虫项目,你要照抄的话,连项目名都不能改动,要完全按照我的来,不然很容易报错