• Python 爬虫入门(十一):Scrapy高级应用之并发与分布式「详细介绍」


    前言

    • 欢迎来到“Python 爬虫入门”系列文章。在前面的文章中,我们已经学习了如何使用 Scrapy 来构建基本的爬虫项目。本篇文章将深入探讨 Scrapy 的高级应用,特别是如何实现并发爬取和分布式爬取。

    • 并发爬取分布式爬取是提升爬虫效率的两大关键技术。并发爬取允许我们同时发出多个请求,大幅提高爬取速度;分布式爬取则让我们能够将爬取任务分散到多个机器上执行,从而处理大规模数据的抓取任务。

    1. 并发爬取

    1.1 并发爬取的基本概念

    并发爬取是指同时发出多个 HTTP 请求,以提高数据抓取的效率。

    Scrapy 中,并发爬取的实现非常简单,主要通过调整配置项来控制并发请求的数量。

    1.2 Scrapy 中的并发配置

    在 Scrapy 中,可以通过修改 settings.py 文件中的配置项来实现并发爬取。

    以下是一些常用的配置项:

    • CONCURRENT_REQUESTS: 控制 Scrapy 同时处理的最大并发请求数。默认值是 16。
    • CONCURRENT_REQUESTS_PER_DOMAIN: 控制 Scrapy 同时处理的每个域名的最大并发请求数。默认值是 8。
    • CONCURRENT_REQUESTS_PER_IP: 控制 Scrapy 同时处理的每个 IP 的最大并发请求数。默认值是 0(表示不限制)。

    示例配置:

    # settings.py
    
    CONCURRENT_REQUESTS = 32
    CONCURRENT_REQUESTS_PER_DOMAIN = 16
    CONCURRENT_REQUESTS_PER_IP = 16
    

    1.3 示例项目:抓取 JSONPlaceholder 的数据

    接下来,我们将创建一个 Scrapy 项目,从 JSONPlaceholder 抓取用户数据,并实现并发爬取。

    首先,创建 Scrapy 项目:

    scrapy startproject jsonplaceholder
    cd jsonplaceholder
    

    创建爬虫:

    scrapy genspider users jsonplaceholder.typicode.com
    

    修改爬虫文件 users.py

    import scrapy
    
    class UsersSpider(scrapy.Spider):
        name = 'users'
        allowed_domains = ['jsonplaceholder.typicode.com']
        start_urls = ['https://jsonplaceholder.typicode.com/users']
    
        def parse(self, response):
            users = response.json()
            for user in users:
                yield {
                    'id': user['id'],
                    'name': user['name'],
                    'username': user['username'],
                    'email': user['email'],
                    'address': user['address'],
                    'phone': user['phone'],
                    'website': user['website'],
                    'company': user['company'],
                }
    

    配置并发设置:

    # settings.py
    
    CONCURRENT_REQUESTS = 32
    CONCURRENT_REQUESTS_PER_DOMAIN = 16
    CONCURRENT_REQUESTS_PER_IP = 16
    

    运行爬虫:

    scrapy crawl users
    

    以上配置将允许 Scrapy 同时发出最多 32 个请求,每个域名和每个 IP 的最大并发请求数分别为 16。

    2. 分布式爬取

    2.1 分布式爬取的基本概念

    分布式爬取是指将爬取任务分布到多个机器上执行,从而提升数据抓取的效率和规模。

    在 Scrapy 中,分布式爬取通常通过结合分布式任务队列(如 Redis)来实现。

    2.2 Scrapy-Redis 的安装与配置

    Scrapy-Redis 是一个用于将 Scrapy 爬虫转换为分布式爬虫的扩展。
    通过 Scrapy-Redis,我们可以将请求队列和抓取结果存储在 Redis 中,从而实现分布式爬取。

    安装 Scrapy-Redis:

    pip install scrapy-redis
    

    2.3 修改爬虫实现分布式爬取

    修改 settings.py 文件:

    # settings.py
    
    # 使用 Scrapy-Redis 的调度器和去重类
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    
    # Redis 连接配置
    REDIS_URL = 'redis://localhost:6379'
    
    # 爬取过程中可以暂停和恢复
    SCHEDULER_PERSIST = True
    
    # 使用 Scrapy-Redis 的 item pipeline
    ITEM_PIPELINES = {
        'scrapy_redis.pipelines.RedisPipeline': 300,
    }
    

    修改爬虫文件 users.py

    import scrapy
    from scrapy_redis.spiders import RedisSpider
    
    class UsersSpider(RedisSpider):
        name = 'users'
        allowed_domains = ['jsonplaceholder.typicode.com']
        redis_key = 'users:start_urls'
    
        def parse(self, response):
            users = response.json()
            for user in users:
                yield {
                    'id': user['id'],
                    'name': user['name'],
                    'username': user['username'],
                    'email': user['email'],
                    'address': user['address'],
                    'phone': user['phone'],
                    'website': user['website'],
                    'company': user['company'],
                }
    

    在 Redis 中添加初始 URL:

    redis-cli lpush users:start_urls https://jsonplaceholder.typicode.com/users
    

    运行爬虫:

    scrapy crawl users
    

    通过上述配置和修改,我们将爬虫转换为了分布式爬虫,可以在多个机器上同时运行爬虫,并将抓取结果存储在 Redis 中。

    3. 并发与分布式爬取的最佳实践

    3.1 优化并发性能

    • 合理设置并发请求数:根据目标网站的性能和带宽,合理设置并发请求数,避免过度请求导致目标网站崩溃。
    • 使用下载延迟:适当设置下载延迟,防止请求过于频繁导致被目标网站封禁。

    示例配置:

    # settings.py
    
    DOWNLOAD_DELAY = 0.5  # 每个请求之间的延迟时间(秒)
    RANDOMIZE_DOWNLOAD_DELAY = True  # 随机化下载延迟
    

    3.2 分布式爬取中的常见问题

    • 去重策略:在分布式爬取中,确保每个 URL 仅被抓取一次,可以通过 Redis 的去重机制实现。
    • 任务调度:合理调度分布式爬取任务,确保每个节点的任务负载均衡。

    3.3 监控和调试

    • 监控爬虫状态:使用 Scrapy 的日志和统计功能,监控爬虫的运行状态和抓取效率。
    • 调试爬虫:使用 Scrapy 的 Shell 和调试工具,实时调试爬虫的抓取逻辑和数据解析。

    4. 示例项目:分布式抓取 JSONPlaceholder 的所有数据

    在本节中,我们将构建一个分布式爬虫项目,从 JSONPlaceholder 抓取所有数据,包括用户、帖子、评论、相册、照片和待办事项。

    创建爬虫:

    scrapy genspider all_data jsonplaceholder.typicode.com
    

    修改爬虫文件 all_data.py

    import scrapy
    from scrapy_redis.spiders import RedisSpider
    
    class AllDataSpider(RedisSpider):
        name = 'all_data'
        allowed_domains = ['jsonplaceholder.typicode.com']
        redis_key = 'all_data:start_urls'
    
        def parse(self, response):
            # 解析用户数据
            if 'users' in response.url:
                users = response.json()
                for user in users:
                    yield {
                        'id': user['id'],
                        'name': user['name'],
                        'username': user['username'],
                        'email': user['email'],
                        'address': user['address'],
                        'phone': user['phone'],
                        'website': user['website'],
                        'company': user['company'],
                    }
            # 解析帖子数据
            elif 'posts' in response.url:
                posts = response.json()
                for post in posts:
                    yield {
                        'id': post['id'],
                        'userId': post['userId'],
                        'title': post['title'],
                        'body': post['body'],
                    }
            # 解析评论数据
            elif 'comments' in response.url:
                comments = response.json()
                for comment in comments:
                    yield {
                        'id': comment['id'],
                        'postId': comment['postId'],
                        'name': comment['name'],
                        'email': comment['email'],
                        'body': comment['body'],
                    }
            # 解析相册数据
            elif 'albums' in response.url:
                albums = response.json()
                for album in albums:
                    yield {
                        'id': album['id'],
                        'userId': album['userId'],
                        'title': album['title'],
                    }
            # 解析照片数据
            elif 'photos' in response.url:
                photos = response.json()
                for photo in photos:
                    yield {
                        'id': photo['id'],
                        'albumId': photo['albumId'],
                        'title': photo['title'],
                        'url': photo['url'],
                        'thumbnailUrl': photo['thumbnailUrl'],
                    }
            # 解析待办事项数据
            elif 'todos' in response.url:
                todos = response.json()
                for todo in todos:
                    yield {
                        'id': todo['id'],
                        'userId': todo['userId'],
                        'title': todo['title'],
                        'completed': todo['completed'],
                    }
    
        def start_requests(self):
            urls = [
                'https://jsonplaceholder.typicode.com/users',
                'https://jsonplaceholder.typicode.com/posts',
                'https://jsonplaceholder.typicode.com/comments',
                'https://jsonplaceholder.typicode.com/albums',
                'https://jsonplaceholder.typicode.com/photos',
                'https://jsonplaceholder.typicode.com/todos'
            ]
            for url in urls:
                yield scrapy.Request(url=url, callback=self.parse)
    
            # 解析相册数据
            elif 'albums' in response.url:
                albums = response.json()
                for album in albums:
                    yield {
                        'id': album['id'],
                        'userId': album['userId'],
                        'title': album['title'],
                    }
            # 解析照片数据
            elif 'photos' in response.url:
                photos = response.json()
                for photo in photos:
                    yield {
                        'id': photo['id'],
                        'albumId': photo['albumId'],
                        'title': photo['title'],
                        'url': photo['url'],
                        'thumbnailUrl': photo['thumbnailUrl'],
                    }
            # 解析待办事项数据
            elif 'todos' in response.url:
                todos = response.json()
                for todo in todos:
                    yield {
                        'id': todo['id'],
                        'userId': todo['userId'],
                        'title': todo['title'],
                        'completed': todo['completed'],
                    }
    
        def start_requests(self):
            urls = [
                'https://jsonplaceholder.typicode.com/users',
                'https://jsonplaceholder.typicode.com/posts',
                'https://jsonplaceholder.typicode.com/comments',
                'https://jsonplaceholder.typicode.com/albums',
                'https://jsonplaceholder.typicode.com/photos',
                'https://jsonplaceholder.typicode.com/todos'
            ]
            for url in urls:
                yield scrapy.Request(url=url, callback=self.parse)
    

    总结

    通过本文的学习,相信小伙伴们能够掌握 Scrapy 中并发爬取和分布式爬取的核心技术,并能够在实际项目中应用这些技术来提升数据抓取的效率。

  • 相关阅读:
    机器学习:朴素贝叶斯算法(Python)
    构建dagu+replicadb镜像
    成为会带团队的技术人 做规划:除了交付和稳定性,还要规划什么?
    【计算机网络】HTTP 重定向的应用场景
    Github 2024-05-30开源项目日报Top10
    前端食堂技术周刊第 62 期:11 月登陆浏览器的新特性、VueConf 2022、第 93 次 TC39 会议、TS 挑战
    前端1+x考证:上篇
    信息系统项目管理师(2022年)—— 重点内容:管理科学基础知识(23)
    JavaScript的基本知识点解析
    微信小程序能不能有一种公共的分包,能被普通的分包引用其资源?(内有解决方案)
  • 原文地址:https://blog.csdn.net/weixin_48321392/article/details/140961734