news 2026/9/2 8:59:16

基于Scrapy-Redis的分布式爬虫实战:高效采集百万级历史天气数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Scrapy-Redis的分布式爬虫实战:高效采集百万级历史天气数据

简介:这是一份面向计算机专业本科生及爬虫初学者的毕业设计级实战项目,聚焦Scrapy分布式架构在大规模历史天气数据采集中的落地应用。资源通过精简但完整的代码实现,解决了单机爬虫难以高效覆盖全国多城市、多年份天气数据的瓶颈问题,涵盖分布式调度、反爬应对、结构化清洗与本地持久化等关键环节。压缩包共9个文件(7个Python源码含Spider主逻辑、Pipeline处理、Redis队列配置等,1个README.md说明部署流程与运行步骤,1个.gitignore),总大小仅8KB,轻量易读,便于快速理解分布式爬虫核心模块分工与协作机制。目前已有228人学习下载,读者可直接复现从环境搭建、任务分发到数据落地的全流程,获得一套经过Windows 10/11实测可用、附带清晰注释与结构化目录的可运行分布式爬虫方案。

1. 项目缘起与核心价值

最近在做一个数据分析项目,需要用到全国多个城市过去十年的历史天气数据。一开始,我尝试去一些公开的气象数据网站手动下载,但很快就发现这几乎是个不可能完成的任务:城市太多、年份跨度大,而且很多网站的数据要么是图片格式,要么藏在层层嵌套的页面里,手动操作效率极低,还容易出错。这时候,用爬虫自动化采集就成了唯一可行的选择。

但问题又来了,全国几百个城市,每个城市要爬取多年的逐日数据,数据量预估在百万条级别。如果只用单机跑一个普通的Scrapy爬虫,且不说IP被封的风险,光是这个耗时就可能以“周”甚至“月”计,项目根本等不起。于是,“分布式”就成了这个项目的必然技术选型。Scrapy本身是一个强大的爬虫框架,但它原生并不支持分布式。我们需要借助额外的组件,通常是Redis,来搭建一个分布式的任务调度和数据去重中心,让多台机器(爬虫节点)可以协同工作,共同完成这个庞大的采集任务。

这个“scrapy分布式爬虫爬取全国历史天气”项目,其核心价值就在于解决大规模、高并发的结构化数据采集难题。它不仅仅是一个爬虫脚本,更是一套工程化的解决方案,涉及任务分发、协同去重、数据存储与容错。对于需要类似大规模数据采集的数据分析师、气象研究者,或是学习分布式系统在爬虫领域应用的开发者来说,这个项目的实现过程和踩过的坑,都具有很高的参考价值。接下来,我将详细拆解从零搭建这个系统的完整过程。

2. 目标网站分析与反爬策略制定

在动手写代码之前,花时间分析目标网站是至关重要的一步,这直接决定了爬虫的稳定性、效率和代码复杂度。我选择了一个提供历史天气查询的网站作为数据源(这里以假设的weather-history.com为例,实际项目中请替换为真实、合法且允许爬取的网站)。

2.1 页面结构与数据定位

首先,我手动访问了网站,观察其URL规律。通常,这类网站的URL会包含城市代码和日期,例如http://www.weather-history.com/city/101010100/20230101,其中101010100是北京的城市代码,20230101是日期。这是一个非常友好的信号,意味着我们可以通过构造URL列表来发起请求。

接着,使用浏览器的开发者工具(F12)查看页面源码。我发现目标数据(日期、最高温、最低温、天气状况、风力等)是以结构化的HTML表格形式呈现的,而不是通过JavaScript动态加载的。这大大降低了爬取难度。我只需要使用Scrapy的XPathCSS选择器,就可以精准地提取这些数据。

注意:务必检查网站是否有robots.txt文件(通常在网站根目录,如www.weather-history.com/robots.txt),并遵守其中的爬取规则。这是网络爬虫的道德和法律底线。

2.2 核心反爬机制识别与应对

即使页面结构简单,网站也可能设有反爬机制。经过测试,我发现了以下几种情况:

  1. 请求频率限制:短时间内连续请求同一域名,可能会遇到HTTP 429(Too Many Requests)错误,或者IP被暂时封禁。

    • 应对策略:在Scrapy中启用内置的AutoThrottle扩展,并设置合理的DOWNLOAD_DELAY(如下载延迟为2-5秒)。在分布式架构中,由于是多台机器,整体请求速率会更快,因此更需要严格控制单个节点的请求频率,避免“集体被封”。
  2. User-Agent检查:一些网站会检查请求头中的User-Agent,如果使用Scrapy默认的或空的User-Agent,可能会被拒绝。

    • 应对策略:在Scrapy的settings.py中设置一个USER_AGENT列表,并在Downloader Middleware中编写随机切换的逻辑。也可以直接使用scrapy-fake-useragent这类第三方库。
  3. Cookie/Session验证:部分网站可能需要先访问首页获取一个初始Cookie,后续请求才有效。

    • 应对策略:在Spider的start_requests方法中,先发起一个对首页的请求,不解析数据,只为获取Cookie。Scrapy的Request对象会自动管理CookieJar,后续请求会携带这些Cookie。
  4. 动态渲染内容(针对热词中提到的playwright场景):虽然本例目标网站是静态HTML,但热词中提到了scrapy playwright 动态 iframe。如果目标数据是通过JavaScript动态加载的,或者隐藏在<iframe>标签内,那么传统的Scrapy就无法直接抓取。

    • 应对策略:这就需要集成scrapy-playwrightseleniumscrapy-playwright是更现代、更高效的选择。它允许Scrapy的请求由Playwright控制的浏览器来执行,等页面完全渲染、JavaScript执行完毕后,再提取数据。这对于爬取单页面应用(SPA)或复杂交互网站至关重要。在本项目中,由于数据是静态的,我们暂不需要,但这是必须掌握的进阶技能。

经过以上分析,我确认目标网站的反爬措施相对温和,主要需防范请求频率过高。这为我们设计分布式爬虫提供了有利条件。

3. 分布式爬虫架构设计与核心组件

单机Scrapy爬虫的流程是线性的:引擎从Spider获取初始URL,放入调度器(Scheduler),下载器(Downloader)按顺序取出并下载,再将响应返回给Spider解析,数据交给Item Pipeline处理。调度器和去重队列(DupeFilter)默认都在内存中。

分布式爬虫的核心思想是将调度器和去重队列中心化,让所有爬虫节点从一个共享的“任务池”里领取任务,并将发现的新任务放回池中,同时共享去重指纹,避免重复爬取。

3.1 基于Scrapy-Redis的架构

scrapy-redis是实现这一思想的经典库。它重写了Scrapy原生的调度器(Scheduler)和去重过滤器(DupeFilter),使其基于Redis数据库工作。

架构图(文字描述):

  1. 一个主Redis服务器:作为整个分布式系统的“大脑”,存储着两种关键队列:
    • 爬虫名:requests(默认):一个优先级队列(Redis的zset),存放所有待爬取的Request。每个Request被序列化后存入,并带有优先级分数。
    • 爬虫名:dupefilter:一个Redis集合(set),存放所有已爬取URL的指纹(如SHA1哈希值),用于去重。
  2. 多个Scrapy爬虫节点(Worker):这些节点可以运行在不同的物理机、虚拟机或容器中。它们的配置几乎相同,都连接到同一个Redis服务器。
  3. 工作流程
    • 所有爬虫节点启动后,都会监听Redis中的爬虫名:requests队列。
    • 某个节点从队列中“弹出”(blpopzpopmin)一个Request,进行下载和解析。
    • 解析过程中产生的新Request(比如“下一页”或“其他城市”的链接),会被该节点重新“推入”(lpushzadd)到Redis的同一个爬虫名:requests队列中。
    • 同时,新Request的URL指纹会被检查是否已存在于爬虫名:dupefilter集合中,如果存在则丢弃,实现全局去重。
    • 解析出的数据(Item),可以由每个节点独立处理(如存入本地文件),更常见的做法是再推入一个Redis的爬虫名:items队列,由单独的数据处理进程来消费,实现数据存储与爬取解耦。

3.2 环境准备与依赖安装

首先,确保所有节点都安装了Python(建议3.8+)和必要的库。在每台机器上执行以下命令:

pip install scrapy scrapy-redis redis

如果目标网站需要处理JavaScript,则还需要安装scrapy-playwright

pip install scrapy-playwright playwright install chromium # 安装浏览器驱动

Redis服务器部署:你需要一个独立的Redis服务器。可以使用云服务商的Redis,也可以在本地一台机器上安装。安装命令(以Ubuntu为例):

sudo apt-get update sudo apt-get install redis-server sudo systemctl start redis-server sudo systemctl enable redis-server

安装后,默认运行在127.0.0.1:6379重要:在生产环境中,务必为Redis设置密码,并配置防火墙规则,只允许爬虫节点IP访问。

4. Scrapy项目创建与分布式改造

4.1 创建Scrapy项目与Spider

在任意一个节点上,创建标准的Scrapy项目:

scrapy startproject weather_distributed cd weather_distributed scrapy genspider history_weather weather-history.com

这会生成一个名为history_weather的Spider文件。我们首先按照常规方式编写它,从单个城市开始。

4.2 编写核心Spider逻辑

weather_distributed/spiders/history_weather.py

import scrapy from scrapy_redis.spiders import RedisSpider # 关键:继承RedisSpider from datetime import datetime, timedelta import logging class HistoryWeatherSpider(RedisSpider): name = 'history_weather' allowed_domains = ['weather-history.com'] # 关键:不再定义start_urls,而是使用redis_key redis_key = 'history_weather:start_urls' # Redis中起始URL队列的键名 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 可以在这里初始化一些变量,比如城市列表 self.city_code = '101010100' # 示例:北京。实际应从外部传入或读取列表。 def parse(self, response): """ 解析每日天气详情页 """ # 提取数据,这里根据实际网页结构编写XPath或CSS选择器 # 示例结构,需要你根据实际网站调整 date = response.url.split('/')[-1] # 从URL提取日期 high_temp = response.xpath('//span[@class="high-temp"]/text()').get() low_temp = response.xpath('//span[@class="low-temp"]/text()').get() condition = response.xpath('//span[@class="weather-condition"]/text()').get() if high_temp and low_temp: yield { 'city_code': self.city_code, 'date': date, 'high_temperature': high_temp.replace('°C', '').strip(), 'low_temperature': low_temp.replace('°C', '').strip(), 'weather_condition': condition.strip(), 'source_url': response.url } logging.info(f"成功爬取 {date} 数据") else: logging.warning(f"数据提取失败于 {response.url}") # 可以考虑将失败的URL重新加入队列,或者记录到日志供后续排查 # 示例:生成前一天和后一天的URL,实现连续爬取(需根据网站实际情况调整) # current_date = datetime.strptime(date, '%Y%m%d') # prev_date = current_date - timedelta(days=1) # next_date = current_date + timedelta(days=1) # prev_url = f"http://www.weather-history.com/city/{self.city_code}/{prev_date.strftime('%Y%m%d')}" # next_url = f"http://www.weather-history.com/city/{self.city_code}/{next_date.strftime('%Y%m%d')}" # 将新发现的Request放入Redis队列(由scrapy-redis自动完成) # yield scrapy.Request(prev_url, callback=self.parse) # yield scrapy.Request(next_url, callback=self.parse)

关键改造点:

  1. 继承RedisSpider:这是scrapy-redis提供的类,它继承了Scrapy的Spider,但重写了start_requests等方法,使其从Redis读取起始任务。
  2. redis_key:定义了Spider从Redis的哪个键(key)中获取起始URL。我们需要手动向这个键里添加第一批任务(种子URL)。
  3. 移除start_urls:起始URL不再在代码中硬编码,而是通过Redis动态注入。

4.3 配置Scrapy-Redis连接

修改weather_distributed/settings.py文件,启用scrapy-redis组件:

# 启用scrapy-redis调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 启用scrapy-redis去重过滤器 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 指定Redis服务器连接信息 REDIS_HOST = '192.168.1.100' # 你的Redis服务器IP REDIS_PORT = 6379 REDIS_PASSWORD = 'your_strong_password_here' # 如果设置了密码 # REDIS_URL = 'redis://:password@host:port/db' # 也可以使用URL格式 # 保持Redis队列在爬虫关闭后不清空,允许暂停和恢复 SCHEDULER_PERSIST = True # 使用优先级队列进行调度(默认) SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue' # 配置Item Pipeline,将数据存入Redis(可选) ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300, # 你可以添加自己的Pipeline,比如写入MySQL或MongoDB 'weather_distributed.pipelines.WeatherDataPipeline': 400, } # 其他重要设置 # 设置下载延迟,避免请求过快 DOWNLOAD_DELAY = 3 # 启用自动限速扩展 AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 5 AUTOTHROTTLE_MAX_DELAY = 60 # 并发请求数,根据机器和网络情况调整 CONCURRENT_REQUESTS = 16 # 配置随机User-Agent USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...' # 或者使用中间件随机切换 DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, 'weather_distributed.middlewares.RandomUserAgentMiddleware': 400, }

4.4 编写数据存储Pipeline

虽然scrapy-redis提供了RedisPipeline将Item暂存到Redis,但我们通常需要持久化到数据库或文件。创建一个自定义的Pipeline,weather_distributed/pipelines.py

import pymongo # 以MongoDB为例,也可以用pymysql, sqlite3等 from itemadapter import ItemAdapter import logging class WeatherDataPipeline: def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db self.client = None self.db = None @classmethod def from_crawler(cls, crawler): # 从settings.py读取配置 return cls( mongo_uri=crawler.settings.get('MONGO_URI', 'mongodb://localhost:27017'), mongo_db=crawler.settings.get('MONGO_DATABASE', 'weather_data') ) def open_spider(self, spider): # 当Spider启动时连接数据库 self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] logging.info(f"Connected to MongoDB at {self.mongo_uri}, database: {self.mongo_db}") def close_spider(self, spider): # 当Spider关闭时断开连接 if self.client: self.client.close() logging.info("Disconnected from MongoDB.") def process_item(self, item, spider): # 处理每个Item,插入数据库 adapter = ItemAdapter(item) collection = self.db[spider.name] # 使用Spider名作为集合名 try: # 使用日期和城市代码作为复合唯一键,避免重复插入 collection.update_one( {'city_code': adapter['city_code'], 'date': adapter['date']}, {'$set': adapter.asdict()}, upsert=True ) logging.debug(f"Item saved/updated: {adapter['city_code']} - {adapter['date']}") except Exception as e: logging.error(f"Error saving item {item} to MongoDB: {e}") return item

然后在settings.py中添加MongoDB配置:

MONGO_URI = 'mongodb://username:password@your_mongo_host:27017/' MONGO_DATABASE = 'weather_history'

5. 任务启动、监控与实战避坑指南

5.1 初始化任务队列与启动爬虫

Spider写好后,它自己不会开始工作,因为Redis中的起始队列是空的。我们需要一个脚本来生成所有要爬取的初始URL(种子URL),并推送到Redis。

创建一个脚本seed_urls.py

import redis import datetime def generate_date_range(start_date, end_date): """生成日期范围内的所有日期,格式为YYYYMMDD""" start = datetime.datetime.strptime(start_date, '%Y%m%d') end = datetime.datetime.strptime(end_date, '%Y%m%d') date_generated = [start + datetime.timedelta(days=x) for x in range(0, (end-start).days + 1)] return [date.strftime('%Y%m%d') for date in date_generated] def push_start_urls(): # 连接Redis r = redis.Redis(host='192.168.1.100', port=6379, password='your_password', decode_responses=True) # 定义城市代码列表(示例) city_codes = ['101010100', '101020100', '101030100'] # 北京,上海,广州 base_url = 'http://www.weather-history.com/city/{city_code}/{date}' start_date = '20140101' end_date = '20231231' all_dates = generate_date_range(start_date, end_date) start_urls = [] for city in city_codes: for date in all_dates: url = base_url.format(city_code=city, date=date) start_urls.append(url) # 推送到Redis队列,键名与Spider中的`redis_key`一致 redis_key = 'history_weather:start_urls' # 使用lpush(左推入)或rpush(右推入)均可,scrapy-redis默认使用lpop(左弹出) if start_urls: r.lpush(redis_key, *start_urls) # 批量推送 print(f"成功推送 {len(start_urls)} 个起始URL到Redis键 '{redis_key}'") else: print("没有生成起始URL。") # 也可以查看队列长度 queue_length = r.llen(redis_key) print(f"当前队列 '{redis_key}' 中的任务数量: {queue_length}") if __name__ == '__main__': push_start_urls()

运行这个脚本,种子URL就被注入到Redis了。

在任意一个爬虫节点上,进入项目目录,启动爬虫:

scrapy crawl history_weather

你会看到爬虫开始运行,并从Redis中获取任务。在其他机器上,以同样的方式启动同一个爬虫项目(确保settings.py中的Redis配置指向同一服务器),它们就会自动加入工作,协同爬取。

5.2 分布式爬虫的监控与管理

  1. Redis监控:使用redis-cli工具可以直观地查看任务状态。

    • LLEN history_weather:start_urls:查看待爬请求队列长度。
    • SCARD history_weather:dupefilter:查看去重集合的大小(已爬取的唯一URL数量)。
    • KEYS history_weather:*:查看所有与该爬虫相关的键。
    • INFO stats:查看Redis服务器整体状态。
  2. Scrapy日志:每个爬虫节点都会输出日志。建议将日志级别设置为INFODEBUG,并输出到文件,便于排查问题。在settings.py中配置:

    LOG_LEVEL = 'INFO' LOG_FILE = './scrapy.log'
  3. 进程管理:在生产环境,建议使用supervisorsystemd来管理每个节点的Scrapy进程,确保进程异常退出后能自动重启。

5.3 实战中踩过的坑与解决方案

坑1:Redis连接不稳定或超时

  • 现象:爬虫运行一段时间后报错:ConnectionError: Error while reading from socket
  • 根因:网络波动、Redis服务器负载过高或配置不当(如timeout设置过小)。
  • 解决
    • 确保Redis服务器性能充足,并检查网络连接。
    • 在Scrapy的settings.py中调整REDIS_PARAMS,增加socket_timeoutsocket_connect_timeout
    • 使用连接池:scrapy-redis默认使用了连接池,但可以调整池的大小。在settings.py中设置REDIS_PARAMS = {'connection_pool_class': redis.ConnectionPool, 'max_connections': 50}

坑2:去重集合过大导致Redis内存暴涨

  • 现象:爬取数百万URL后,Redis内存占用极高,甚至导致OOM(内存溢出)。
  • 根因scrapy-redis的默认去重是将每个URL的指纹(一个长字符串)存入一个Redis Set。海量URL下,这个Set会非常庞大。
  • 解决
    • 使用布隆过滤器(Bloom Filter)scrapy-redis支持布隆过滤器去重,它用很小的内存和一定的误判率(可以接受,因为爬虫可以容忍少量重复)来替代巨大的Set。在settings.py中设置:DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"替换为DUPEFILTER_CLASS = "scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter",并安装scrapy-redis-bloomfilter库。同时需要配置BLOOMFILTER_HASH_NUMBERBLOOMFILTER_BIT等参数。
    • 定期清理或分片:对于一次性爬虫任务,爬完后可以直接删除去重键。对于长期运行的增量爬虫,可以考虑按日期或主题对去重键进行分片。

坑3:任务队列“饥饿”或“堆积”

  • 现象:有的节点很快没活干了(队列空),有的节点还在忙;或者队列里永远有大量任务,但爬取速度很慢。
  • 根因:任务粒度不均(有的页面解析快,有的慢)、网络延迟差异、或节点性能不同。
  • 解决
    • 优化任务粒度:尽量让每个Request的任务量均衡。例如,不要把一个城市所有日期的请求做成一个任务,而是每个日期一个独立Request。
    • 调整并发和延迟:在settings.py中合理设置CONCURRENT_REQUESTSDOWNLOAD_DELAY。分布式环境下,总并发数是各节点之和,要综合考虑目标网站的承受能力。
    • 使用优先级队列scrapy-redis默认使用优先级队列。可以为重要的、种子URL设置更高的优先级(在Request对象中设置priority属性),确保它们被优先处理。

坑4:数据存储成为瓶颈

  • 现象:爬虫解析速度很快,但数据写入数据库(如MySQL)很慢,导致整体速度上不去,甚至内存堆积。
  • 根因:数据库写入是I/O密集型操作,单条插入效率低。
  • 解决
    • 批量插入(Bulk Insert):在Pipeline中积累一定数量的Item(如100或1000个),然后使用数据库的批量插入语句一次性写入,这比逐条插入快一个数量级。
    • 异步写入:使用Twisted的异步数据库驱动(如twisted.enterprise.adbapi)或像aiomysql这样的异步库,避免阻塞爬虫的主事件循环。Scrapy本身基于Twisted,集成异步驱动是更优雅的方式。
    • 读写分离:将数据先快速写入一个消息队列(如Redis List或Kafka),然后由下游的多个消费者进程异步地消费队列数据并写入数据库,实现解耦和负载分流。

坑5:应对网站结构变更

  • 现象:爬虫运行一段时间后,大量解析失败,日志中满是Selector提取不到数据的警告。
  • 根因:目标网站改版了,HTML结构发生了变化。
  • 解决
    • 增加健壮性检查:在解析函数中,对关键数据字段进行判断,如果提取不到,则记录详细的错误信息(包括当前URL和响应片段),并可能将URL放入一个重试队列或错误队列。
    • 设计可配置的解析规则:不要将XPath或CSS选择器硬编码在代码里。可以将它们提取到配置文件(如JSON或YAML)或数据库中。当网站改版时,只需更新配置文件并重启爬虫,甚至可以实现热更新。
    • 实施监控告警:对爬虫的解析成功率、数据产出量进行监控。当成功率低于某个阈值或产出量异常时,通过邮件、钉钉、微信等渠道发送告警,提醒开发者及时检查。

6. 数据清洗、存储与后续处理

爬虫爬取到的原始数据往往夹杂着空白字符、非法格式、甚至缺失值,需要进行清洗。

6.1 在Pipeline中进行数据清洗

我们可以在上面的WeatherDataPipeline.process_item方法中,加入清洗逻辑:

def process_item(self, item, spider): adapter = ItemAdapter(item) # 清洗温度数据,移除单位和非数字字符,并转换为整数或浮点数 def clean_temp(temp_str): if not temp_str: return None # 移除°C、℃、空格等字符,只保留数字和负号、小数点 import re cleaned = re.sub(r'[^\d.-]', '', temp_str) try: return float(cleaned) if '.' in cleaned else int(cleaned) except ValueError: return None adapter['high_temperature'] = clean_temp(adapter.get('high_temperature')) adapter['low_temperature'] = clean_temp(adapter.get('low_temperature')) # 清洗天气状况,去除首尾空格 adapter['weather_condition'] = adapter.get('weather_condition', '').strip() # 处理缺失值,可以设置为None或默认值 if not adapter['weather_condition']: adapter['weather_condition'] = '未知' # 日期格式标准化 date_str = adapter.get('date') if date_str and len(date_str) == 8: # 假设是YYYYMMDD格式 try: # 可以转换为datetime对象或保持字符串 # adapter['date'] = datetime.strptime(date_str, '%Y%m%d') pass except ValueError: logging.error(f"Invalid date format: {date_str} for item {adapter}") adapter['date'] = None # ... 后续的数据库插入逻辑 return item

6.2 多格式存储与备份

除了主数据库(如MongoDB),建议同时将原始数据备份一份到文件系统,例如按日期分片的JSON文件或Parquet文件,方便后续进行大数据分析。

可以在Pipeline中同时写入文件和数据库,或者使用Scrapy的Feed Exports功能,将Item直接导出到指定格式的文件。

settings.py中配置Feed Exports:

FEED_FORMAT = 'jsonlines' # 或 'csv', 'xml' FEED_URI = 'file:///path/to/output/%(name)s/%(time)s.json' # 按爬虫名和时间分目录存储 FEED_EXPORT_ENCODING = 'utf-8'

6.3 数据验证与质量报告

爬虫结束后,应该生成一份简单的数据质量报告:

  • 总爬取记录数
  • 各城市数据量分布
  • 温度、天气状况等字段的缺失值比例
  • 数据的时间范围是否连续

可以写一个脚本,连接数据库进行聚合查询,输出报告。这有助于评估本次爬取任务的完整性和可用性。

7. 项目总结与扩展思考

通过这个项目,我们成功构建了一个可以水平扩展的Scrapy分布式爬虫系统,用于爬取全国历史天气数据。从单机脚本到分布式系统的演进,关键在于引入了Redis作为中心化的调度和去重枢纽。这个过程涉及了爬虫策略制定、反爬应对、Scrapy项目改造、分布式组件配置、数据持久化以及一系列实战中的性能调优和故障排查。

几个关键的扩展方向:

  1. 动态渲染集成:如果未来目标网站改版为JavaScript重度依赖,可以无缝集成scrapy-playwright。只需在Spider中为Request指定meta={'playwright': True},并配置相应的Downloader Middleware即可,架构无需大改。
  2. 容器化与编排:将每个爬虫节点打包成Docker镜像,使用Kubernetes或Docker Compose进行编排和管理,可以轻松实现节点的弹性伸缩和故障自愈。
  3. 更复杂的调度策略scrapy-redis默认是先进先出(FIFO)的优先级队列。对于需要更复杂调度逻辑(如根据域名权重、爬取深度调度)的场景,可以继承并重写其调度器类。
  4. 增量爬虫与更新:对于历史天气,可能是一次性任务。但对于新闻、价格等持续更新的数据,需要设计增量爬取策略。可以记录每个数据源的最后爬取时间,定期爬取该时间点之后的新内容。去重集合需要持久化,并且要考虑如何清理过期的指纹以防止集合无限膨胀。

最后,也是最核心的一点,始终遵守法律法规和网站的服务条款。控制爬取速度,避免对目标网站造成过大压力。在数据使用上,尊重版权和隐私。技术是中立的,但使用技术的人需要肩负起责任。这个分布式爬虫框架是一个强大的工具,希望你能用它高效、负责地获取所需数据,为你的分析和研究提供坚实的基础。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 8:59:08

MAX197与51单片机并口数据采集完整方案

简介&#xff1a;面向单片机与数据采集初学者的Max197 ADC应用示例&#xff0c;以“程序原理图”组合展示芯片与单片机串口通信的实现方法&#xff0c;可用于学习AD转换、UART配置及电路设计。压缩包共19个文件&#xff0c;包含C语言源程序、头文件、Keil工程文件、Protel原理图…

作者头像 李华
网站建设 2026/9/2 8:58:59

基于SpringBoot与ECharts构建新闻可视化分析平台实战指南

简介&#xff1a;本资源是一套基于SpringBoot后端框架与ECharts前端可视化库构建的新闻数据分析平台完整源码及配套数据库&#xff0c;面向Java Web开发初学者、数据可视化实践者及高校课程设计学习者&#xff0c;旨在解决新闻数据采集、存储、分析与交互式图表展示的一体化教学…

作者头像 李华
网站建设 2026/9/2 8:57:56

STM32F103驱动HC-SR04超声波测距:GPIO、定时器输入捕获与中断实战

简介&#xff1a;本资源是基于STM32F103RCT6微控制器与HC-SR04超声波传感器实现高精度实时测距的完整嵌入式项目&#xff0c;面向嵌入式初学者、课程设计学生及智能硬件开发者&#xff0c;解决非接触式距离检测在机器人避障、智能家居与自动化场景中的基础应用需求。压缩包共77…

作者头像 李华
网站建设 2026/9/2 8:57:39

本地部署GPT Voice语音助手:从环境配置到接口调用的完整实践指南

这次我们来看一个 GPT Voice 项目。它不是一个官方产品&#xff0c;而是一个由社区开发者实现的、能让 GPT 模型“听懂”语音指令并“开口”回答的本地化工具。核心思路是结合语音识别、大语言模型和语音合成技术&#xff0c;实现一个类似语音助手的交互体验。对于想探索语音交…

作者头像 李华
网站建设 2026/9/2 8:57:30

Linux pv命令:管道数据流监控与速率控制的瑞士军刀

你有没有遇到过这样的场景&#xff1a;在终端里执行一个耗时很长的命令&#xff0c;比如复制一个大文件、打包一个目录、或者下载一个资源&#xff0c;屏幕上一片寂静&#xff0c;光标孤独地闪烁&#xff0c;你完全不知道它进行到哪一步了&#xff0c;是卡住了还是在正常运行&a…

作者头像 李华