Java并发编程框架之综合案例—— 分布式爬虫(四)

发布于:2024-12-23 ⋅ 阅读:(13) ⋅ 点赞:(0)

最近看到一句很喜欢的话,"轻舟已过万重山"。
小时候觉得忘带作业是天大的事,开始进修学业的时候觉得学习是天大的事,高中的时候觉得没考上理想大学是天大的事。
但现在回头看看,那些曾觉得难以跨过的山其实都跨过去了,以为不能接受的也都接受了。生活充满了选项,遗憾也不过是平常,好在许多风景路过我身旁,我也一个人看过很多次夕阳。
其实人就是无论做什么选择都会后悔的。大家总是习惯去美化那条当初没有选择的路,可是大家都心知肚明,就算时间能重来一次,以当时的心智和阅历还是会做出同样的选择,我想人生大概就是一个享受过程的过程吧。


目录

选择合适的框架

构建分布式架构

配置主节点(Master)

设置从节点(Slave)

实现关键功能

任务分配

结果收集

去重过滤

提升性能

第一步:安装依赖

第二步:创建Scrapy项目

第三步:配置Redis连接

第四步:编写爬虫逻辑

第五步:启动Redis服务

第六步:运行爬虫

案例分析:抓取电商网站的商品列表页及其详情页信息

背景介绍

项目结构与配置

编写爬虫逻辑

启动Redis服务

运行爬虫

数据存储


开发一个基于Scrapy或其他类似框架的分布式爬虫程序是一项复杂的任务,它不仅涉及到了解和掌握Scrapy本身的使用方法,还需要深入理解分布式系统的设计原则。下面我将详细介绍如何构建这样一个系统,并解决在开发过程中可能遇到的关键问题。

选择合适的框架

首先,选择一个适合做分布式爬虫的框架非常重要。Scrapy 是一个非常流行的Python爬虫框架,它提供了强大的功能来简化网页抓取的过程。为了实现分布式的特性,我们可以结合Scrapy与Redis一起工作,即使用scrapy-redis库。这个库允许我们通过Redis共享请求队列以及去重过滤器,从而让多个Scrapy实例可以协同工作。

构建分布式架构

配置主节点(Master)
  1. 安装依赖:确保所有机器上都安装了Python环境,并且能够访问互联网以安装必要的包。
  2. 设置Redis服务器:作为URL存储和通信中介,Redis将在整个分布式网络中扮演核心角色。你需要在一个稳定的环境中部署Redis服务,并保证其他节点能够连接到这台服务器。
  3. 创建Scrapy项目:根据目标网站的特点,编写具体的爬虫逻辑。此时应该调整项目的配置文件settings.py,指定Redis的相关参数如主机地址、端口等信息,以便于后续步骤中的集成。
设置从节点(Slave)

对于每个参与爬取工作的从节点来说,它们的任务是尽可能多地获取数据并将结果反馈给主节点。因此,在这些机器上也需要完成类似的准备工作:

  • 安装相同版本的Python解释器及其依赖库。
  • 确保可以从本地或远程位置克隆Git仓库中的代码。
  • 修改settings.py以指向正确的Redis实例。

实现关键功能

任务分配

为了让各个节点都能有效地参与到工作中来,必须有一个有效的机制来进行任务分发。这里可以通过向Redis中插入待处理URL的方式来实现这一点。当新的链接被发现时,它们会被添加进队列;而每个slave则会定期检查是否有新的任务需要执行。

结果收集

一旦某个slave完成了对特定页面的解析,它会把得到的数据存入另一个专门用于保存items的列表里。master端负责监听这个列表的变化,并适时地将内容导出到持久化存储介质中。

去重过滤

为了避免重复抓取相同的页面,可以在每次生成新请求之前先查询名为dupefilter的集合,确认该URL是否已经被访问过。如果尚未记录,则将其加入其中,并允许此次请求继续进行。

提升性能

除了上述提到的功能外,还可以考虑采取一些额外措施来进一步优化系统的效率:

  • 异步I/O:利用Twisted提供的非阻塞API或者更现代的选择如asyncio,可以让单个进程中同时发起多个HTTP请求,减少等待时间。
  • 代理池:为避免因频繁请求导致IP被封禁的情况发生,建议设置一个动态更新的代理服务器列表供各节点轮流使用。
  • 负载均衡:采用Nginx或者其他反向代理工具可以帮助分散流量压力,使得系统更加稳定可靠。

总之,通过精心规划和合理配置,你就可以成功搭建起一个高效可靠的分布式爬虫平台。在这个过程中,不断试验不同的策略和技术方案,最终找到最适合你自己应用场景的最佳实践。

当然,构建分布式爬虫系统是一个涉及多个方面的复杂任务。接下来我会详细讲解如何使用Scrapy和scrapy-redis来创建一个简单的分布式爬虫示例,并附上代码及注释。

第一步:安装依赖

首先,你需要在所有参与爬取工作的机器(包括主节点和从节点)上安装必要的Python包:

pip install scrapy scrapy-redis redis

第二步:创建Scrapy项目

你可以通过命令行创建一个新的Scrapy项目:

scrapy startproject distributed_crawler

然后进入项目目录:

cd distributed_crawler

第三步:配置Redis连接

编辑settings.py文件,添加或修改以下设置以配置Redis作为调度器和去重过滤器:

# 使用scrapy-redis的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 确保所有的spider共享相同的去重指纹
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 设置Redis数据库地址
REDIS_HOST = 'localhost'  # 如果是远程服务器,请替换为实际IP
REDIS_PORT = 6379         # Redis端口,默认是6379

# 是否允许暂停/继续爬虫
SCHEDULER_PERSIST = True

第四步:编写爬虫逻辑

创建一个新的爬虫文件,例如my_spider.py,并编写如下代码:

import scrapy
from scrapy_redis.spiders import RedisSpider

class MySpider(RedisSpider):
    name = 'my_spider'
    allowed_domains = ['example.com']  # 替换为目标网站域名
    # 不需要定义start_urls,因为我们将使用Redis中的URL队列

    def parse(self, response):
        # 这里可以实现你的解析逻辑
        self.logger.info(f"Parsed page {response.url}")
        
        # 示例:提取页面上的链接并加入到Redis队列中
        for link in response.css('a::attr(href)').getall():
            yield scrapy.Request(url=response.urljoin(link), callback=self.parse)

第五步:启动Redis服务

确保你的Redis服务正在运行。如果你是在本地测试,那么只需保证本机有Redis实例即可。如果是分布式环境,则需要确保所有节点都能够访问到同一个Redis实例。

第六步:运行爬虫

在主节点上,你可以使用以下命令将起始URL推送到Redis队列:

redis-cli lpush my_spider:start_urls http://example.com

然后,在每个工作节点上启动爬虫:

scrapy crawl my_spider

这将使得所有的工作节点开始根据Redis队列中的URL进行爬取工作。

案例分析:抓取电商网站的商品列表页及其详情页信息

背景介绍

在电子商务领域,商家经常需要监控竞争对手的价格变化、新品发布情况以及其他市场动态。为了实现这一目标,开发者们常常会构建爬虫来自动化地收集这些数据。本案例中,我们将使用Scrapy框架结合scrapy-redis库创建一个分布式爬虫,用于抓取电商网站上的商品列表页以及对应的详情页信息。通过这种方式,我们不仅能够获取到商品的基本信息(如名称、价格),还可以深入挖掘更多细节。

项目结构与配置

首先,我们需要确保所有参与爬取工作的机器都安装了Python环境,并且能够访问互联网以安装必要的包。接下来,在主节点上执行以下命令创建一个新的Scrapy项目:

scrapy startproject ecommerce_crawler

进入项目目录后,编辑settings.py文件,添加或修改如下设置以配置Redis作为调度器和去重过滤器:

# 使用scrapy-redis的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 确保所有的spider共享相同的去重指纹
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 设置Redis数据库地址
REDIS_HOST = 'localhost'  # 如果是远程服务器,请替换为实际IP
REDIS_PORT = 6379         # Redis端口,默认是6379

# 是否允许暂停/继续爬虫
SCHEDULER_PERSIST = True

# 下载延迟,避免对目标网站造成过大压力
DOWNLOAD_DELAY = 2

此外,还需定义Item类来保存抓取的数据。可以在items.py中添加如下代码:

import scrapy

class ProductItem(scrapy.Item):
    title = scrapy.Field()      # 商品标题
    price = scrapy.Field()      # 商品价格
    description = scrapy.Field()# 商品描述
    image_urls = scrapy.Field() # 商品图片链接
    detail_url = scrapy.Field() # 商品详情页URL
编写爬虫逻辑

接下来,创建一个新的爬虫文件,例如ecommerce_spider.py,并编写如下代码:

import scrapy
from scrapy_redis.spiders import RedisSpider
from ecommerce_crawler.items import ProductItem

class EcommerceSpider(RedisSpider):
    name = 'ecommerce'
    allowed_domains = ['example.com']  # 替换为目标网站域名
    redis_key = 'ecommerce:start_urls'

    def parse(self, response):
        """解析商品列表页"""
        for product in response.css('div.product-item'):  # 根据实际页面结构调整选择器
            item = ProductItem()
            item['title'] = product.css('h2::text').get()
            item['price'] = product.css('span.price::text').get()
            item['description'] = product.css('p.description::text').get()
            item['image_urls'] = [response.urljoin(url) for url in product.css('img::attr(src)').getall()]
            item['detail_url'] = response.urljoin(product.css('a::attr(href)').get())

            yield scrapy.Request(item['detail_url'], callback=self.parse_detail, meta={'item': item})

        # 处理分页,找到下一页链接并继续爬取
        next_page = response.css('a.next-page::attr(href)').get()
        if next_page is not None:
            yield scrapy.Request(response.urljoin(next_page), callback=self.parse)

    def parse_detail(self, response):
        """解析商品详情页"""
        item = response.meta['item']
        # 假设详情页上有额外的信息,比如品牌、型号等
        item['brand'] = response.css('span.brand::text').get()
        item['model'] = response.css('span.model::text').get()

        yield item

这段代码实现了两个主要功能:

  1. 解析商品列表页:从每个商品卡片中提取基本信息,并生成指向详情页的新请求。
  2. 解析商品详情页:进一步抓取更详细的资料,并将完整的Item对象返回给引擎处理。
启动Redis服务

确保你的Redis服务正在运行。如果你是在本地测试,那么只需保证本机有Redis实例即可。如果是分布式环境,则需要确保所有节点都能够访问到同一个Redis实例。

运行爬虫

在主节点上,你可以使用以下命令将起始URL推送到Redis队列:

redis-cli lpush ecommerce:start_urls http://example.com/products

然后,在每个工作节点上启动爬虫:

scrapy crawl ecommerce

这将使得所有的工作节点开始根据Redis队列中的URL进行爬取工作。每当某个节点完成对一个商品详情页的解析时,它会将结果发送回主节点,最终由管道组件负责将数据存储至指定的目标位置。

数据存储

最后一步是定义如何保存抓取的数据。可以通过修改pipelines.py文件来实现这一点。下面是一个简单的例子,展示了如何将数据保存到MongoDB中:

import pymongo

class MongoDBPipeline(object):

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        collection_name = 'products'
        self.db[collection_name].insert_one(dict(item))
        return item

同时别忘了更新settings.py文件中的相关设置:

ITEM_PIPELINES = {
   'ecommerce_crawler.pipelines.MongoDBPipeline': 300,
}

MONGO_URI = 'mongodb://localhost:27017/'
MONGO_DATABASE = 'ecommerce'

至此,我们就完成了一个完整的分布式爬虫系统的搭建。这个系统不仅可以高效地抓取电商网站上的商品信息,而且还能很好地应对大规模数据采集的需求。