ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Scrapy与Redis构建分布式爬虫系统,高效采集全国历史天气数据

基于Scrapy与Redis构建分布式爬虫系统,高效采集全国历史天气数据 简介这是一份面向计算机专业本科生及爬虫初学者的毕业设计级实战项目聚焦Scrapy分布式架构在大规模历史天气数据采集中的落地应用。资源通过精简可运行的代码体系解决单机爬虫难以高效覆盖全国多城市、多年份天气数据的痛点兼顾技术深度与工程可部署性。压缩包共9个文件7个Python核心模块含Spider、Pipeline、Middleware等1个README.md说明文档1个.gitignore总大小仅8KB结构紧凑、无冗余依赖适合作为课程设计参考或二次开发基础模板。已有228人学习下载读者可直接获取完整分布式爬虫方案包含基于Scrapy Cluster的多节点任务分发逻辑、天气站点HTML解析与字段清洗规则、标准化Item定义与MySQL/CSV双存储适配、Windows 10/11环境一键部署教程以及典型反爬应对策略注释。1. 项目概述与核心价值最近在整理一个数据分析项目需要用到全国多个城市过去十年的历史天气数据。找了一圈公开的数据集要么是收费的要么数据颗粒度不够细要么就是城市覆盖不全。这种时候老办法最管用自己动手丰衣足食。用爬虫来抓取是最直接的解决方案但考虑到数据量庞大全国几百个城市每个城市十年每天的数据条目单机爬虫的效率会成为瓶颈运行时间可能长达数天甚至更久而且一旦中途出错或网络波动重头再来的成本太高。所以这个项目的核心思路很明确构建一个基于 Scrapy 的分布式爬虫系统来高效、稳定地爬取全国历史天气数据并最终打包成结构化的压缩文件。分布式爬虫的核心优势在于它能把一个庞大的抓取任务分解成无数个小任务分发给网络中的多个爬虫节点也就是多台机器或者多个进程同时执行。这不仅仅是“人多力量大”的简单叠加更重要的是它带来了容错性和可扩展性。一个节点挂了任务会被重新分配给其他节点觉得速度还不够快加机器就行了。这对于需要长时间运行、数据量巨大的爬取任务来说是质的提升。这个项目非常适合有一定 Python 和 Scrapy 基础想深入理解分布式系统原理并解决实际大规模数据采集问题的朋友。你将不仅学会如何写一个爬虫更能掌握如何让爬虫“团队作战”。最终你会得到一个完整的、可复用的分布式爬虫框架以及一份宝贵的全国历史天气数据集。2. 技术选型与架构设计为什么是 Scrapy Redis 这个组合这是经过实践检验的、用于构建分布式爬虫的经典方案。我们来拆解一下每个组件的角色和选型理由。2.1 Scrapy爬虫框架的基石Scrapy 是一个为爬取网站数据、提取结构性数据而编写的应用框架。它封装了网络请求、调度、下载、解析、数据管道等爬虫开发中的通用环节让我们可以专注于核心的抓取规则和数据处理逻辑。它的异步处理能力和中间件机制为高性能爬虫提供了坚实基础。在这个项目中Scrapy 负责每个爬虫节点的“具体执行工作”发起请求、解析网页、提取数据。2.2 Redis分布式任务调度与状态中枢Redis 是一个高性能的内存键值数据库。在分布式爬虫架构中它扮演着“大脑”和“通信中心”的角色。具体来说我们主要利用 Redis 的几种数据结构集合Set或列表List作为“待爬取队列”Request Queue。所有需要爬取的 URL 起始点例如各城市各年月的天气页面链接都被推送到这里。各个 Scrapy 爬虫节点从这个公共队列中领取任务避免了重复爬取和任务冲突。集合Set作为“已爬取指纹库”Dupe Filter。Scrapy 会将每个请求生成一个唯一指纹通常是 URL 的哈希值在爬取前先到 Redis 的这个集合里查重确保全网爬虫都不会重复抓取同一个页面这是实现分布式去重的关键。键值对Key-Value可以存储一些共享的配置信息、统计状态如已爬城市计数或临时数据。选择 Redis 是因为它速度极快内存操作支持丰富的数据结构并且具备持久化能力即使重启服务任务队列和去重状态也不会丢失保证了系统的可靠性。2.3 整体架构设计整个系统的运行流程可以概括为以下几个步骤任务播种由一个独立的脚本种子生成器运行根据我们设定的城市列表和日期范围生成所有需要爬取的初始 URL并将这些 URL 推送到 Redis 的“待爬取队列”中。节点启动在多台机器或同一台机器的多个进程中启动配置好的 Scrapy 爬虫节点。这些节点共享同一个 Redis 数据库地址。任务领取与执行每个爬虫节点启动后会从 Redis 的公共队列中获取一个 URL 任务。然后Scrapy 框架执行标准的爬取流程下载页面、解析数据。数据提取与后续请求在解析页面时我们不仅提取当天的天气数据如温度、天气状况、风力等还可能从当前页面中发现新的需要爬取的链接例如“下一页”的链接或者该城市其他月份的链接。这些新发现的 URL 也会被加工成新的请求并推回 Redis 的公共队列中等待其他空闲节点处理。这样就形成了任务的动态分发。数据存储提取到的天气数据项Item通过 Scrapy 的 Item Pipeline 进行清洗、验证并最终存储。这里我们可以选择多种方式例如直接写入一个大的 JSON 文件、存入 SQLite/MySQL 数据库或者按城市/日期分割成多个 CSV 文件。项目最终要求打包成 ZIP因此一种合理的做法是先按“城市/年月.csv”的结构保存文件最后再用脚本打包。状态同步与去重在整个过程中所有节点通过 Redis 同步“已爬取指纹”确保任何 URL 只会被整个集群爬取一次。这个架构的美妙之处在于它的“松耦合”和“自协调”。你不需要一个中心节点来指挥谁该做什么每个爬虫节点都是平等的工人从公共任务池里领活干干完了再领新的。通过增加工人节点的数量就能线性地提升爬取速度。3. 核心实现与代码拆解接下来我们深入到代码层面看看如何一步步实现这个分布式爬虫。我会假设我们从一个常见的天气历史数据查询网站进行爬取。3.1 环境准备与依赖安装首先确保你的 Python 环境建议 3.7已经就绪。我们需要安装核心库pip install scrapy scrapy-redis redisscrapy: 爬虫框架本体。scrapy-redis: 这是一个第三方库它完美地桥接了 Scrapy 和 Redis为我们实现了基于 Redis 的分布式调度器Scheduler、去重过滤器DupeFilter以及一套项目模板。它是本项目的“粘合剂”极大简化了开发工作。redis: Python 的 Redis 客户端库用于连接和操作 Redis。同时你需要安装并运行一个 Redis 服务器。可以从官网下载安装或者使用 Docker 快速启动一个docker run -d -p 6379:6379 --name weather-redis redis3.2 创建 Scrapy 项目与爬虫使用 Scrapy 命令行工具创建项目和爬虫scrapy startproject national_weather cd national_weather scrapy genspider weather_spider example-weather-site.com3.3 关键配置启用 Scrapy-Redis这是将普通 Scrapy 爬虫升级为分布式爬虫的核心步骤。我们需要修改national_weather/settings.py配置文件# 启用 scrapy-redis 的调度器替代 Scrapy 原生的调度器 SCHEDULER scrapy_redis.scheduler.Scheduler # 启用 scrapy-redis 的去重过滤器确保所有爬虫共享去重状态 DUPEFILTER_CLASS scrapy_redis.dupefilter.RFPDupeFilter # 指定 Redis 服务器的连接信息 REDIS_URL redis://localhost:6379/0 # 如果 Redis 有密码格式为 redis://:passwordlocalhost:6379/0 # 是否在爬虫关闭时保留 Redis 中的请求队列和去重指纹。 # 设为 True 的话爬虫重启后会从上次中断的地方继续爬取非常适合长时间任务。 SCHEDULER_PERSIST True # 配置 Item Pipeline将数据存储到文件或数据库 ITEM_PIPELINES { national_weather.pipelines.WeatherDataPipeline: 300, scrapy_redis.pipelines.RedisPipeline: 400, # 可选用于将 item 暂存到 Redis实现分布式处理这里我们主要用文件存储所以可以注释掉或设置更高优先级 } # 为了礼貌爬取降低对方服务器压力务必设置下载延迟 DOWNLOAD_DELAY 13.4 定义数据模型Item在items.py中定义我们要抓取的天气数据结构import scrapy class WeatherItem(scrapy.Item): city scrapy.Field() # 城市名 date scrapy.Field() # 日期格式如 2023-01-01 high_temp scrapy.Field() # 最高气温 low_temp scrapy.Field() # 最低气温 weather scrapy.Field() # 天气现象如“晴”、“多云” wind_dir scrapy.Field() # 风向 wind_force scrapy.Field() # 风力 # 可以根据目标网站增加更多字段如空气质量指数、降水量等3.5 编写爬虫核心解析逻辑修改weather_spider.py。这里的关键是分布式爬虫的爬虫类需要继承scrapy_redis.spiders.RedisSpider而不是普通的scrapy.Spider。它的起始 URL 不再来自start_urls而是从 Redis 队列中读取。import scrapy from scrapy_redis.spiders import RedisSpider from national_weather.items import WeatherItem class WeatherSpider(RedisSpider): name weather_spider redis_key weather:start_urls # 从 Redis 中这个键对应的列表里读取起始 URL def parse(self, response): 解析城市某年某月的天气列表页 假设页面结构是一个表格每一行是一天的数据 # 首先可以从响应中提取城市和年月信息可能来自 URL 或页面标题 # 例如URL 格式为http://xxx.com/beijing/202301 city response.url.split(/)[-2] year_month response.url.split(/)[-1] # 解析表格中的每一行 rows response.xpath(//table[classweather-table]/tbody/tr) for row in rows: item WeatherItem() item[city] city # 假设日期在第一个 td 温度在第二、三个 td... item[date] f{year_month[:4]}-{year_month[4:]}-{row.xpath(./td[1]/text()).get().zfill(2)} item[high_temp] row.xpath(./td[2]/text()).get().replace(℃, ).strip() item[low_temp] row.xpath(./td[3]/text()).get().replace(℃, ).strip() item[weather] row.xpath(./td[4]/text()).get().strip() item[wind_dir] row.xpath(./td[5]/text()).get().strip() item[wind_force] row.xpath(./td[6]/text()).get().strip() yield item # 处理分页查找“下一页”的链接 next_page response.xpath(//a[contains(text(), 下一页)]/href).get() if next_page: next_page_url response.urljoin(next_page) # 将新的请求 URL 放入 Redis 队列让其他节点也能处理 yield scrapy.Request(next_page_url, callbackself.parse) # 处理月份导航可能还需要爬取该城市其他月份的数据 # 这里可以根据页面上的月份链接生成新的请求并 yield注意实际的 XPath 或 CSS 选择器需要根据目标网站的具体 HTML 结构进行调整。这里只是一个示例模板。在正式爬取前务必仔细分析目标网站的页面结构。3.6 实现数据存储管道Pipeline我们需要一个 Pipeline 来将爬取到的WeatherItem存储起来。为了便于最终打包我们选择按城市和年份存储为 CSV 文件。import csv import os from itemadapter import ItemAdapter class WeatherDataPipeline: def open_spider(self, spider): # 创建一个目录来存放所有数据文件 self.data_dir weather_data os.makedirs(self.data_dir, exist_okTrue) # 用一个字典来缓存已打开的文件句柄和写入器避免频繁开关文件 self.file_handles {} def process_item(self, item, spider): adapter ItemAdapter(item) city adapter[city] date adapter[date] year date.split(-)[0] # 生成文件名例如weather_data/北京_2023.csv file_key f{city}_{year} filename os.path.join(self.data_dir, f{file_key}.csv) # 如果这个文件还没打开过则创建它并写入表头 if file_key not in self.file_handles: file open(filename, a, newline, encodingutf-8-sig) writer csv.writer(file) writer.writerow([city, date, high_temp, low_temp, weather, wind_dir, wind_force]) self.file_handles[file_key] (file, writer) # 写入数据行 file, writer self.file_handles[file_key] writer.writerow([ adapter[city], adapter[date], adapter[high_temp], adapter[low_temp], adapter[weather], adapter[wind_dir], adapter[wind_force] ]) return item def close_spider(self, spider): # 爬虫关闭时关闭所有打开的文件 for file, _ in self.file_handles.values(): file.close() self.file_handles.clear()3.7 任务播种脚本爬虫节点启动后会监听redis_key即weather:start_urls对应的队列。我们需要一个独立的脚本来向这个队列投放初始任务。创建一个seed_urls.py脚本import redis import itertools # 连接 Redis r redis.Redis(hostlocalhost, port6379, db0) # 定义需要爬取的城市列表示例 cities [北京, 上海, 广州, 深圳, 杭州, 成都, ...] # 可以是一个很长的列表 # 定义需要爬取的年份和月份范围 years range(2014, 2024) # 2014年到2023年 months range(1, 13) # 1月到12月 # 生成所有起始 URL假设目标网站的 URL 模式为 /{city}/{year}{month} 如 /beijing/202301 base_url http://www.example-weather-site.com/ for city, year, month in itertools.product(cities, years, months): # 注意这里需要将城市名转换为目标网站 URL 中使用的格式如拼音、编码等 city_formatted convert_city_to_url_format(city) # 你需要实现这个转换函数 url f{base_url}{city_formatted}/{year}{month:02d} # 将 URL 推送到 Redis 列表 r.lpush(weather:start_urls, url) print(f已成功播种 {len(cities)*len(years)*len(months)} 个初始 URL 任务到 Redis 队列。) def convert_city_to_url_format(city_name): 一个简单的示例函数将中文城市名转为拼音需要安装 pypinyin 库 # 示例简单返回小写拼音实际应用需处理多音字和特殊字符 # from pypinyin import lazy_pinyin # return .join(lazy_pinyin(city_name)) return city_name.lower().replace( , ) # 临时替代运行这个脚本任务种子就播撒到 Redis 中了。3.8 启动分布式爬虫集群现在你可以在任意多台机器上启动爬虫了。只要它们能访问到同一个 Redis 服务器并且代码和配置一致。在每台机器或每个终端中进入项目目录运行scrapy crawl weather_spider你会看到多个爬虫节点同时开始工作从 Redis 队列中领取 URL解析存储数据并可能将新的 URL 推回队列。它们协同工作共同完成庞大的爬取任务。3.9 数据打包当所有爬虫任务都完成队列为空且爬虫空闲后数据已经按照weather_data/城市_年份.csv的格式存储好了。最后一步是打包。创建一个简单的打包脚本pack_data.pyimport zipfile import os def zip_weather_data(): data_dir weather_data output_zip 全国历史天气数据.zip with zipfile.ZipFile(output_zip, w, zipfile.ZIP_DEFLATED) as zipf: for root, dirs, files in os.walk(data_dir): for file in files: file_path os.path.join(root, file) # 在 zip 文件中保持相对路径结构 arcname os.path.relpath(file_path, startos.path.dirname(data_dir)) zipf.write(file_path, arcname) print(f数据已打包至 {output_zip}) if __name__ __main__: zip_weather_data()运行这个脚本你就会得到最终的项目成果全国历史天气数据.zip。4. 实战避坑与进阶技巧在实际操作中你肯定会遇到各种各样的问题。下面分享一些我踩过的坑和总结的经验。4.1 反爬虫策略与应对大规模爬取公开数据绕不开反爬虫机制。常见的策略和应对方法如下User-Agent 检测这是最基本的。务必在settings.py中设置一个合理的USER_AGENT并且可以考虑使用scrapy-fake-useragent库来随机切换 User-Agent。DOWNLOADER_MIDDLEWARES { scrapy.downloadermiddlewares.useragent.UserAgentMiddleware: None, scrapy_fake_useragent.middleware.RandomUserAgentMiddleware: 400, }请求频率限制DOWNLOAD_DELAY已经设置了一个基础延迟。对于更严格的网站可能需要使用AutoThrottle扩展它能根据服务器的响应速度自动调整请求间隔。AUTOTHROTTLE_ENABLED True AUTOTHROTTLE_START_DELAY 1 AUTOTHROTTLE_MAX_DELAY 10 AUTOTHROTTLE_TARGET_CONCURRENCY 1.0IP 封锁这是最棘手的问题。分布式爬虫本身会从多个机器发起请求一定程度上分散了 IP 风险。但如果目标网站封禁了整个 IP 段就需要考虑使用代理 IP 池。可以为 Scrapy 配置代理中间件并从 Redis 或数据库中动态获取可用代理。请注意务必使用合法合规的代理服务。动态内容JavaScript 渲染很多现代网站使用 JavaScript 动态加载数据。如果目标天气网站是这种类型单纯的 Scrapy 就无能为力了因为它只下载静态 HTML。这时就需要引入浏览器渲染引擎。这就是为什么网络热词中会出现scrapy playwright的原因。你可以集成scrapy-playwright来爬取动态页面。# 安装 pip install scrapy-playwright # 在 settings.py 中启用 DOWNLOAD_HANDLERS { http: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, https: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, } # 在爬虫请求中指定使用 playwright yield scrapy.Request(url, callbackself.parse, meta{playwright: True})Cookie 和 Session有些网站需要登录或验证。你需要分析登录流程可能需要在爬虫中模拟登录并将获取到的 Cookie 通过scrapy.Request的cookies或headers参数传递下去。scrapy-redis默认不共享 Cookie如果需要可以自定义一个中间件将 Cookie 也存储到 Redis 中。4.2 数据清洗与质量保证爬下来的原始数据往往很“脏”需要清洗缺失值处理某些日期的数据可能缺失解析时得到None。需要在 Pipeline 中判断是记录为NULL还是尝试用前后日期的数据插值或者直接标记为缺失。格式统一温度单位℃/°C、风力描述“3-4级”、“微风”、天气现象“晴”、“晴天”等需要统一成你定义的格式。异常值检测比如最高气温低于最低气温、温度值明显超出合理范围如60°C这类数据需要被过滤或标记出来人工复核。编码问题确保文件存储时使用utf-8-sig编码这样用 Excel 打开 CSV 文件时不会出现乱码。4.3 监控与运维当爬虫在后台长时间运行时你需要知道它的状态Redis 监控使用redis-cli命令查看队列长度 (LLEN weather:start_urls)、去重集合大小 (SCARD相关键)可以大致了解任务进度。Scrapy 日志将 Scrapy 的日志级别设为INFO或DEBUG并输出到文件便于后期排查问题。编写监控脚本可以写一个简单的脚本定期检查 Redis 队列是否已空、各爬虫节点是否存活、数据文件是否在持续增长并通过邮件或即时通讯工具发送状态报告。4.4 性能优化调整并发数在settings.py中CONCURRENT_REQUESTS控制单个爬虫节点的并发请求数。根据目标网站的承受能力和自身网络条件调整通常设置在 16-32 之间。分布式爬虫的优势在于可以横向增加节点而不是盲目提高单个节点的并发。优化解析逻辑XPath 或 CSS 选择器的编写要尽量精确高效避免复杂的嵌套查询。在parse方法中减少不必要的字符串处理和临时变量创建。管道异步处理如果数据清洗或存储操作比较耗时比如写入远程数据库可以考虑使用Twisted的异步 API 或在 Pipeline 中引入线程池避免阻塞爬虫的下载器。4.5 关于增量爬取历史天气数据是静态的一次爬取即可。但如果你要做一个监控实时天气或每日更新的爬虫就需要增量爬取。scrapy-redis的SCHEDULER_PERSIST True设置可以保证任务不丢失但实现增量更新更常见的做法是在数据表中为每条记录增加update_time字段。每天播种新的日期任务到 Redis 队列。爬虫解析时检查该日期数据是否已存在若存在且未变化则跳过若存在但有更新或不存在则插入/更新。这需要你的 Pipeline 与数据库交互并实现“upsert”更新或插入逻辑。5. 常见问题与排查实录即使按照步骤操作也难免会遇到问题。这里记录一些典型问题和解决方法。5.1 爬虫启动后不工作日志显示空闲可能原因1Redis 队列中没有任务。检查seed_urls.py是否成功运行并用redis-cli执行LRANGE weather:start_urls 0 -1查看队列内容。可能原因2Redis 连接失败。检查settings.py中的REDIS_URL是否正确Redis 服务是否正常运行防火墙是否开放了 6379 端口。可能原因3爬虫类继承错误。确保你的 Spider 继承自RedisSpider并且redis_key属性设置正确。5.2 数据重复爬取可能原因1去重过滤器未生效。确认DUPEFILTER_CLASS配置正确并且 Redis 中用于去重的键默认以dupefilter:开头正在增长。可能原因2请求的指纹生成方式有问题。scrapy-redis默认使用请求的 URL 和方法来生成指纹。如果同一个数据可以通过不同 URL 访问比如带不同参数的 URL就会被当作不同请求。你需要根据情况在爬虫中重写make_request_from_item方法或规范化 URL 来确保指纹唯一。5.3 爬取速度很慢可能原因1DOWNLOAD_DELAY设置过大。在遵守目标网站robots.txt且不影响其服务的前提下可以适当调小。可能原因2目标网站响应慢。可以开启AutoThrottle让它自动调节。可能原因3网络或代理延迟。检查网络连接如果使用了代理测试代理的速度和稳定性。可能原因4解析函数效率低下。使用 profiling 工具如cProfile分析parse方法的性能瓶颈。5.4 遇到 403/429 等 HTTP 错误403 Forbidden通常是被服务器识别为爬虫。立即检查并加强你的伪装策略更换 User-Agent增加延迟使用代理。429 Too Many Requests请求过于频繁。必须立刻大幅降低请求频率并确保DOWNLOAD_DELAY和AUTOTHROTTLE已正确配置。5.5 数据存储混乱或文件未生成可能原因Pipeline 中的文件路径或写入逻辑有误。检查open_spider和process_item方法中的路径拼接、文件打开模式a为追加。确保在写入前item中的字段都已正确提取且不为None。可以在 Pipeline 中加入日志打印来调试。最后一个非常重要的建议在开始大规模爬取之前务必先用少量任务进行测试。验证整个流程从播种、爬取、解析到存储每一个环节都按预期工作。同时仔细阅读目标网站的robots.txt文件和服务条款尊重网站的规则合理控制爬取速度避免对对方服务器造成不必要的负担。你的爬虫应该是互联网上一个礼貌的访客而不是一场风暴。本文还有配套的精品资源点击获取
返回列表