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

ops/2024/9/24 21:19:15/

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

  • 前言
  • 1. 并发爬取
    • 1.1 并发爬取的基本概念
    • 1.2 Scrapy 中的并发配置
    • 1.3 示例项目:抓取 JSONPlaceholder 的数据
  • 2. 分布式爬取
    • 2.1 分布式爬取的基本概念
    • 2.2 Scrapy-Redis 的安装与配置
    • 2.3 修改爬虫实现分布式爬取
  • 3. 并发与分布式爬取的最佳实践
    • 3.1 优化并发性能
    • 3.2 分布式爬取中的常见问题
    • 3.3 监控和调试
  • 4. 示例项目:分布式抓取 JSONPlaceholder 的所有数据
  • 总结

前言

  • 欢迎来到“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(表示不限制)。

示例配置:

python"># settings.pyCONCURRENT_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

python">import scrapyclass 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'],}

配置并发设置:

python"># settings.pyCONCURRENT_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 文件:

python"># 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

python">import scrapy
from scrapy_redis.spiders import RedisSpiderclass 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 优化并发性能

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

示例配置:

python"># settings.pyDOWNLOAD_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

python">import scrapy
from scrapy_redis.spiders import RedisSpiderclass 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 中并发爬取和分布式爬取的核心技术,并能够在实际项目中应用这些技术来提升数据抓取的效率。


http://www.ppmy.cn/ops/92513.html

相关文章

微信小程序--20(API)

一、API 1.作用 获取用户信息、本地存储、支付功能等。 2.分类 事件监听API同步API异步API 二、事件监听API 1.特点 on开头,用来监听某些事件的触发 2.举例 wx.WindowResize(function callback)监听窗口尺寸变化的事件 三、同步API 1.特点 以Sync结尾的A…

快速搭建Vue框架并实现登录功能

要搭建一个使用Vue框架并实现登录功能的前端应用,我们可以使用Vue CLI来快速搭建项目结构,并使用Vuex来管理状态(如登录状态),以及Axios来发送HTTP请求到后端API。以下是一个基本的步骤指南: 1. 环境准备 …

Kubernetes节点上线和下线、Kubernetes高可用集群搭建上、Kubernetes高可用集群搭建中和Kubernetes高可用集群搭建下

一、Kubernetes节点上线和下线 1.新节点上线 1)准备工作 关闭防火墙firewalld、selinux 设置主机名 设置/etc/hosts 关闭swap swapoff -a 永久关闭,vi /etc/fstab 注释掉swap那行 将桥接的ipv4流量传递到iptables链 modprobe br_netfilter ##生成brid…

Matplotlib库

目录 1. 基本概念与安装 2. 绘图接口 3. 常见图表类型 4. 图表属性设置 5. 高级绘图技巧 6. 文本支持 7. 使用示例 结论 Matplotlib中如何实现动画绘制? 在Matplotlib中设置图表的详细属性有哪些? Matplotlib支持哪些高级绘图技巧&#xff0c…

K8S Helm

简述 Helm 是 Kubernetes 的开源包管理器。它提供了提供、共享和使用为 Kubernetes 构建的软件的能力。它允许开发者定义、‌打包、‌发布和管理Kubernetes应用资源,‌类似于Linux下的apt或yum包管理器。‌Helm3的架构主要包括Helm客户端、‌Chart仓库以及Kubernet…

【C++】面向对象三大特性之—— 继承 | 详解

目录 继承的概念 继承语法格式 继承方式 隐藏 继承下来的成员和父类是不是同一份 隐藏 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 构造 拷贝构造 赋值重载 析构 继承与友元 继承与静态成员 菱形继承及菱形虚拟继承 多继承 菱形继承 菱形…

OpenCV中数据类型cv::Vec3f

cv::Vec3f 是 OpenCV 提供的一个模板类,用于表示具有三个元素的向量,每个元素都是 float 类型。在图像处理和计算机视觉中,这种类型的向量通常用于表示颜色值(如 BGR 颜色空间中的一个像素点)、坐标点(例如…

java学习笔记 day8.6

修改数据 1.修改数据时判断name参数不为空且非空字符串,判断salary是不为空则添加记录 <update id"editStaffItem">update staff<set><if testname!null and name!"">name#{name},</if><if test"salary!null">sa…