资讯动态

全国天气数据采集:Scrapy爬虫到SQLite结构化数据全流程

发布时间:2026/10/17 4:21:30 来源:尧图企业网站定制
简介一套覆盖全国2290个地区、时间跨度2011至2024年的天气数据采集与处理资料包面向爬虫开发、数据分析及气象应用的学习者与从业者。数据源取自天气网历史天气频道包含历史气温、风向、风力、生活指数、健康指数、旅游指数等维度可支撑农业、交通、旅游等场景的基础数据需求。资源配有Python爬虫源码、XML配置文件和Jupyter Notebook分析示例能清晰还原从目标选择、参数配置、请求解析到CSV存储的多城市批量采集流程。包内共300个文件以261个CSV天气数据为主体29个PY脚本实现抓取逻辑另有6个XML配置文件与2个IPYNB演示文档压缩包整体约1.23MB结构简洁便于定位。目前已有163人学习下载借助该包可直接获得2011至2024年多个城市的标准化历史天气数据集并参考源码学习如何处理分页、数据清洗与字段整理非常适合需要夯实爬虫和数据处理技能的初学者。1. 全国天气数据采集把零散网页变成结构化天气数据集写了三年采集脚本最怕的不是被限流而是数据拿到手发现字段对不上昨天还是晴今天变成晴转多云温度和风力各说各话。这套全国天气数据采集源码把从请求调度、页面解析、数据清洗到落库的完整链路拆成了一个个可替换的模块拿到手改一改配置就能跑出按城市和日期组织的结构化天气数据集。它适合三类人做数据分析但苦于没有稳定数据源的研究者刚学 Scrapy 想找个完整工程参考的开发者以及需要批量历史天气数据做模型训练的算法同学。项目的核心是一套轻量级采集框架调度器维护城市队列下载器处理重试与限速解析层把天气状况、温度、风力等字段统一格式清洗后按日期分表写入 SQLite。整条链路不需要额外部署数据库服务一台普通开发机就能跑完全国城市的数据采集。2. 架构设计轻量级采集框架如何拆解数据链路在打开代码之前先理解整个工程的骨架。这里讲清三件事技术栈为什么这么选、目录结构怎么组织、数据模型怎么设计。弄懂之后后面读代码就是按图索骥。2.1 技术选型Scrapy 主采、Requests 补采、SQLite 落库先说结论这套项目没有用 Airflow 或分布式调度的全家桶而是用 Scrapy 作为批量采集引擎、Requests 作为单点补充工具、SQLite 作为最终存储。选型的理由要从数据量级说起。全国天气历史数据即便全量累积跑一年也就是百来万行的量级单机完全能扛住。用分布式框架属于杀鸡用牛刀调度、监控、容错全都要额外配置运维成本反而拖慢开发进度。Scrapy 承担的是每天定时批量跑的城市队列。每个城市的天气页面是一个独立 Request交给下载器并发请求项目默认把并发数设为 8并且开启了下载延迟。这个组合让采集节奏对目标站点相对友好不至于单个 IP 的请求频率把数据源打挂。Requests 则用在两类场景一是 Scrapy 解析失败后的单条重试二是调试某个城市页面时手动拉取 HTML 看结构。两个 HTTP 客户端分工明确前者跑批量后者做兜底。有人可能会问既然 Requests 也能批量跑为什么还要引入 Scrapy答案是 Scrapy 自带了两样 Requests 需要手写的东西并发调度和下载中间件。Requests 的并发需要自己用线程池或协程管理而 Scrapy 的引擎天然处理并发请求的调度、去重和异常恢复。另一个实际体验是 Scrapy 的日志自带采集统计item_scraped_count、request_count 这些指标在排查问题时比自定义日志直观得多。SQLite 作为存储层是刻意做的取舍。很多爬虫项目一上来就上 MySQL但天气数据是典型的按城市、按日期查询模型SQLite 的 B 树索引在这种场景下查询性能完全够用而且部署时不用额外维护数据库服务。数据库文件就是一个独立的 .db 文件备份直接拷文件迁移也简单。项目里存储层做了接口抽象如果后续数据量真的大到单机 SQLite 扛不住可以把写入逻辑替换成 MySQL 版本清洗层和解析层完全不用动。这三个选型合起来看核心思路是能单机解决的问题不引入分布式。对学习者和中小型数据分析任务来说这套组合的性价比是最高的。2.2 目录结构每个目录在采集链路中的位置项目的目录结构是典型的 Scrapy 工程风格但在默认模板基础上增加了清洗和存储两个自定义层weather_spider/ ├── scrapy.cfg ├── requirements.txt ├── config/ │ ├── settings.py # 并发数、限速、User-Agent 轮换 │ └── cities.json # 城市编码与名称映射表 ├── spiders/ │ ├── __init__.py │ └── weather.py # 核心爬虫定义请求与解析逻辑 ├── pipelines/ │ ├── __init__.py │ ├── cleaner.py # 数据清洗与去重 │ └── storage.py # SQLite 写入与分表 ├── utils/ │ ├── logger.py # 日志封装 │ └── retry.py # 请求重试与退避 ├── db/ │ └── weather.db # 生成后的数据库文件 └── scripts/ ├── init_db.py # 建表与索引 └── export_csv.py # 导出为 CSV 数据集每个目录的职责边界都很清晰。config 目录集中管理城市清单和运行参数意味着换数据源或者增删城市时不需要改代码只改 JSON。比如想把采集范围从省会城市扩展到地级市只要往 cities.json 里追加城市对象就行想把采集频率从每天一次改成每小时一次改 settings.py 里的调度配置即可。spiders 目录只负责请求加解析拿到结构化字段后直接 yield 给 pipelines。pipelines 目录里 cleaner 和 storage 是两个独立类前者处理脏数据后者处理写入逻辑。这种三段式分离最大的好处是变更隔离目标站页面改版时通常只动 spiders 里的解析函数存储结构调整时只动 storage 类采集规则变化时只动 cleaner。项目大量设计都是围绕让改动落在一个目录里来做。utils 目录放跨层复用的工具。logger 做了日志分级和滚动写入把正常采集日志和丢弃记录分开存排查问题时有迹可循。retry 封装了带退避的重试逻辑。scripts 目录则是给运维用的辅助脚本init_db 负责建表export_csv 负责把数据导出成数据集便于外部工具直接使用。2.3 数据模型按城市与日期组织的气象字段落库之前采集到的每条原始记录需要映射成固定的表结构。项目的核心表名为 daily_weather在设计上直接对应分析场景的查询习惯daily_weather( id INTEGER PRIMARY KEY AUTOINCREMENT, city_code TEXT NOT NULL, -- 城市编码如 010101 city_name TEXT NOT NULL, -- 城市名称 date TEXT NOT NULL, -- 日期格式 YYYY-MM-DD weather TEXT, -- 天气状况如 晴、多云、小雨 max_temp INTEGER, -- 最高气温整数摄氏度 min_temp INTEGER, -- 最低气温 wind_direction TEXT, -- 风向如 东南风 wind_level TEXT, -- 风力等级 humidity INTEGER, -- 相对湿度百分比 updated_at TEXT -- 采集时间戳 )主键用自增 id 是为了保证写入顺序和唯一性但实际查询最常用的条件是 city_code 加 date 的组合所以项目在建表时给这两个字段建了联合索引。weather 和 wind_direction 这类文本字段没有强制枚举约束因为不同地区的天气描述存在差异晴和少云这类原始文本需要保留枚举约束反而会把合法数据卡掉。updated_at 记录采集时间用来排查某条数据是不是隔了很久的过期数据。这里还要提一下 city_code 的设计。项目使用数据源自带的城市编码而不是城市名称作为关联键好处是编码稳定且不会重名坏处是拿到数据集的人需要额外对照城市映射。好在导出的 CSV 里同时带 code 和 name 两列数据分析时直接按 name 看就行了不需要再查表。这套数据模型在查询够快和保留原始语义之间取了平衡。max_temp 和 min_temp 用整数而不是浮点因为天气页面上的温度通常已经是整数用浮点只会引入无意义的精度问题。湿度字段用 INTEGER 存百分数数值不做归一化由下游分析工具自行换算。3. 核心模块拆解从请求进入内存到数据落库的完整链路从城市队列生成第一个请求到数据库里出现一条完整记录整个链路要经过调度、下载、解析、清洗、存储五层。下面沿着代码往下走每一层都有对应的 Python 文件代码和说明对照着看。3.1 请求调度城市队列、限速与重试所有采集动作的起点是调度器。项目在 spiders/weather.py 里用 start_requests 方法把 config/cities.json 中的每个城市编码转成一个独立的 Request并附带当天的日期参数# spiders/weather.py 核心调度逻辑 import json import scrapy from utils.retry import make_retry_request class WeatherSpider(scrapy.Spider): name weather def start_requests(self): with open(config/cities.json, r, encodingutf-8) as f: cities json.load(f) for city in cities: request make_retry_request( urlself.build_url(city[code]), callbackself.parse_weather, meta{city: city}, errbackself.handle_error, ) yield request def build_url(self, city_code): # 拼接目标平台的城市天气页地址 return fhttps://weather.example.com/{city_code}.html这段代码有两个细节值得注意。make_retry_request 是 utils/retry.py 里的封装它在 Request 对象上增加了重试次数和退避策略默认失败后等 5 秒再重试最多三次。Scrapy 自带的 retry middleware 虽然能做重试但不带退避间隔短时间密集重试反而更容易触发数据源的限流。另一个细节是 meta 参数的传递把整个 city 字典挂在 meta 上回调函数 parse_weather 从 response.meta 里取出来这样即使并发请求乱序返回每条响应也能正确对应到自己的城市信息不会串数据。控制整体节奏的开关在 config/settings.py 里。项目把 CONCURRENT_REQUESTS 设为 8DOWNLOAD_DELAY 设为 0.8 秒这两个参数配合的效果是最多 8 个请求同时在飞每个请求之间至少间隔 0.8 秒。如果目标站点响应明显变慢应该优先把并发降到 4 或 2而不是去改代码。这里有个经验请求调度层的调参顺序永远是先降并发、再升延迟、最后才考虑加代理。为了把调度逻辑从具体业务里解耦项目单独维护了一个城市队列。cities.json 中的城市顺序决定了初始请求的入队顺序但 Scrapy 的调度器会按优先级和去重队列重新组织实际的发送顺序。城市对象通常几百条直接用 JSON 加载比引入外部队列服务省事得多。后续如果要做全国几千个区县级采集再考虑把城市队列拆成独立文件按区块分批加载避免启动时一次性加载太多内容进内存。关于重试这里还有一个细节make_retry_request 里除了重试次数还设置了 download_timeout 参数默认 10 秒。这个值不是随便定的它要求单次请求在 10 秒内必须有响应超时就触发重试。天气页面通常很轻10 秒已经足够宽裕如果目标站响应超过这个值说明数据源本身有问题重试也没用。调这个参数时要注意设得太短会误杀正常请求设得太长会让整批采集卡在慢请求上。3.2 页面解析从 HTML 结构到结构化字段的转换解析层是这套项目里最容易受数据源改版影响的环节。parse_weather 收到响应后先用 XPath 提取天气信息块再把原始字符串清洗成统一格式# spiders/weather.py 解析逻辑 def parse_weather(self, response): city response.meta[city] # 页面中的天气信息被包裹在 class 为 weather-info 的 div 里 weather_block response.xpath(//div[contains(class, weather-info)]) if not weather_block: self.logger.warning(fpage structure changed: {city[code]}) return None item { city_code: city[code], city_name: city[name], date: response.meta.get(date, self.today()), weather: weather_block.xpath( ./div[contains(class, weather-text)]/text() ).get(default), max_temp: self._parse_temp( weather_block.xpath( ./div[contains(class, temp-range)]/span[1]/text() ).get(default) ), min_temp: self._parse_temp( weather_block.xpath( ./div[contains(class, temp-range)]/span[2]/text() ).get(default) ), wind_direction: weather_block.xpath( ./div[contains(class, wind)]/text() ).get(default).strip(), wind_level: weather_block.xpath( ./div[contains(class, wind-level)]/text() ).get(default).strip(), humidity: self._parse_humidity( weather_block.xpath( ./div[contains(class, humidity)]/text() ).get(default) ), } yield item这段代码最核心的设计是 _parse_temp 和 _parse_humidity 两个私有解析方法。_parse_temp 接收页面上12℃~23℃这类原始文本内部用正则提取数字遇到暂无或空字符串时返回 None 而不是 0因为 0 是有效气温None 才是缺失值。_parse_humidity 先把湿度78%里的百分号去掉再转成整数存储。解析层和清洗层的边界也在这段代码里体现解析层只做格式转换不做业务判断。比如某个城市页面返回的气温明显异常最高温 60 度解析层照样提取出来交给 cleaner 去判断剔除。这样划分的原因是解析层要保持纯粹任何业务规则的变化都不应该触发解析代码的改动。反之如果数据源调整了页面结构也只是改 XPath 表达式清洗规则不需要动。3.3 数据处理与清洗空值、异常值与重复记录的处理数据采集只是第一步数据处理才是决定数据集质量的关键。清洗层在 pipelines/cleaner.py 中实现它接收爬虫 yield 出来的 item逐字段做校验。项目里清洗规则分三类空值处理、合理范围校验、重复记录去重。# pipelines/cleaner.py 清洗逻辑 from scrapy.exceptions import DropItem class CleanerPipeline: def process_item(self, item, spider): # 1. 关键字段缺失直接丢弃 if not item.get(city_code) or not item.get(date): raise DropItem(fmissing key field: {item}) # 2. 温度合理范围校验常见事故页面露出单位符号或乱码 for field in (max_temp, min_temp): value item.get(field) if value is not None and not (-50 value 50): raise DropItem(ftemperature out of range: {field}{value}) # 3. 湿度范围校验 humidity item.get(humidity) if humidity is not None and not (0 humidity 100): raise DropItem(fhumidity out of range: {humidity}) # 4. 重复数据去重同城市同日期只留一条 seen_keys spider.state.setdefault(seen_keys, set()) dedup_key f{item[city_code]}_{item[date]} if dedup_key in seen_keys: raise DropItem(fduplicate record: {dedup_key}) seen_keys.add(dedup_key) return item这段代码展示了清洗层的三个原则。原则一校验失败用 DropItem 异常直接丢弃而不是修改数据后放行避免脏数据进入存储层。原则二温度范围设置得比较宽-50 到 50因为全国范围包含高海拔地区和夏季高温范围太窄会误杀真实数据。原则三去重放在爬虫进程的 state 里做同一轮采集内维护一个 seen_keys 集合重复数据会在内存里被拦截。这套去重策略是够用但不过度的设计。它解决的是同一轮采集因为重试或页面跳转导致的重复写入适合单机运行的场景。如果项目后续改成多机分布式采集就要把这套内存去重换成 Redis 去重set 的 key 结构可以原样迁移逻辑几乎不用变。从数据结构上看city_code 加 date 作为聚合键天然就是天气数据的业务主键。清洗层还有一个容易被忽略的职责日志记录。每条被丢弃的数据都要能追溯原因否则数据缺失时无法判断是没采集到还是被规则过滤了。项目在 cleaner 里通过 logger 记录 drop 原因这份日志在后续数据校验阶段非常重要。实际跑批时DropItem 数量占采集总量的比例应当很低如果超过 5% 就需要回头检查解析逻辑是否出了问题。3.4 数据落库按日期分表与增量写入清洗完成的数据进入 storage pipeline写入 SQLite。项目的存储策略是按日期分表表名格式为 daily_weather_20250615也就是一个日期一张表。分表的直接好处是历史数据归档方便删除过期数据时直接 DROP 表不用执行 DELETE 语句逐行删。# pipelines/storage.py 写入逻辑 import sqlite3 from datetime import datetime class StoragePipeline: def open_spider(self, spider): self.conn sqlite3.connect(db/weather.db) self.conn.execute(PRAGMA journal_modeWAL) def process_item(self, item, spider): table_name self._table_for(item[date]) self.conn.execute( f INSERT OR REPLACE INTO {table_name} (city_code, city_name, date, weather, max_temp, min_temp, wind_direction, wind_level, humidity, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) , ( item[city_code], item[city_name], item[date], item[weather], item[max_temp], item[min_temp], item[wind_direction], item[wind_level], item[humidity], datetime.now().isoformat(), ), ) self.conn.commit() return item写入时使用 INSERT OR REPLACE 而不是普通 INSERT这等于在数据库层面做了第二道去重同一城市同一天的数据如果重复采集旧记录被新记录覆盖。前面 cleaner 做的是内存级去重这里做的是落地级保护两道防线防止数据膨胀和主键冲突。PRAGMA journal_modeWAL 是一个容易被忽略但很关键的配置。它启用了 SQLite 的预写日志模式读写操作之间互不阻塞适合采集任务一边写入一边被查询的场景。注意 WAL 模式会额外生成 -wal 和 -shm 两个文件备份数据库时要把这三个文件一起拷贝只拷 .db 文件会丢失未合并的日志数据。4. 运行实战从环境搭建到批量采集的完整流程代码读通了只是第一步真正跑起来才会遇到各种环境问题和数据源的变化。下面按照从零搭建到批量采集的操作顺序来写结尾附上实际运行中踩过的高频问题。4.1 环境准备与依赖安装项目运行在 Python 3.8 以上版本依赖集中在 requirements.txt 里。先用虚拟环境隔离依赖是最稳妥的方式避免和系统 Python 的其他包冲突python -m venv venv source venv/bin/activate pip install -r requirements.txtrequirements.txt 中的核心依赖是 Scrapy、Requests、APScheduler 和 lxml。Scrapy 自带 lxml 解析器所以大多数情况下不需要单独安装 lxml。APScheduler 用于定时采集任务的调度如果只跑一次性数据采集这个依赖可以从清单里去掉。安装完成后用 pip list 确认一下关键包版本Scrapy 主版本差异有时会影响 middleware 的写法。安装完依赖需要检查 config/cities.json 里的城市列表是否有实际数据。文件是一个 JSON 数组每个元素包含 code 和 name 两个字段。code 是目标数据源的城市编码通常是一串数字name 是对应的中文名称。如果只想采集部分城市做测试直接删掉不需要的城市对象即可。这里有个建议第一次跑通前不要贪多选两三个城市先验证链路全部跑通了再放开全国城市列表这样排查问题时影响面更小。运行前还需要确认三个关键参数它们都在 config/settings.py 里参数默认值作用CONCURRENT_REQUESTS8最大并发请求数DOWNLOAD_DELAY0.8请求间隔秒数RETRY_TIMES3单条请求最大重试次数这三个参数是采集节奏的油门和刹车。并发太大或延迟太短容易触发数据源限流并发太小全国城市跑完全程要很久。初次运行时建议保持默认值观察日志里 request 和 item 的比例如果 request 数量涨得飞快但 item 产出很少优先检查解析层而不是调参。4.2 初始化数据库并启动采集库表结构需要先初始化。项目提供 scripts/init_db.py它会读取建表 SQL创建 daily_weather 分表和联合索引python scripts/init_db.py python -m scrapy crawl weather第二条命令是启动采集的主命令。Scrapy 会先执行 start_requests读取城市列表生成请求队列然后按 settings.py 里的并发配置开始抓取。日志里如果看到 item_scraped_count 持续增长说明采集链路正常如果这个数字一直为 0多半是解析层没有产出可用的 item要回到第三章的解析逻辑去排查。采集完成后可以用 export 脚本快速验证数据集python scripts/export_csv.py --date 2025-06-15这个命令会把指定日期的所有城市天气记录导出成一个 CSV 文件列与数据库表字段一一对应方便导入 Excel 或 Pandas。第一次跑全量数据时建议先只导一天的数据检查字段内容确认没有解析错位后再放开来跑全量。这里我有过一次教训拿到新数据源后直接全量跑了一晚上第二天发现温度和风向两列对调了重新采集浪费了不少时间。从那以后每次改完解析逻辑我都先跑一个最小城市集验证。关于增量更新项目的 scripts 目录里还有一个 update_daily.py 脚本作用是把调度器接入 APScheduler每天固定时间触发一次 crawl。实际使用时需要先确认数据源的更新时段天气数据一般在凌晨更新当天预报早上 7 点前采集是比较稳妥的窗口期。定时任务的参数要按目标站的更新规律来调建议先观察一周更新时间再设置固定的调度时间避免空跑。4.3 避坑手记5 个真实踩过的运行问题先说第一个高频问题启动后一个请求都没发出去日志停在 start_requests 就没动静了。原因多半是 cities.json 的编码格式问题文件被编辑器保存成了 UTF-8 with BOMjson.load 解析出的第一个城市 code 前面多了不可见字符目标站返回 404 导致整个队列卡住。解决方法是把 cities.json 重新保存为无 BOM 的 UTF-8或者在代码里用 utf-8-sig 编码打开文件一劳永逸。第二个问题是页面能打开但解析字段全为空。常见原因是目标站返回的不是完整 HTML而是一段被压缩过的内容XPath 匹配不到节点。现象是日志里出现 page structure changed 的警告。解决方法是先确认响应头里的 Content-EncodingScrapy 的 downloader middleware 通常会自动解压 gzip但遇到 br 压缩格式时需要额外处理。最直接的排查动作是写个临时脚本用 Requests 拉一次响应头看编码再对照调整 settings 里的 headers 配置。第三个问题与反爬限流有关采集到一半开始大量超时日志里频繁出现 ResponseNeverReceived。原因通常是目标站对单 IP 的请求频率设了阈值触发后直接断开连接。解决方法是把 CONCURRENT_REQUESTS 从 8 降到 4DOWNLOAD_DELAY 从 0.8 提到 2 秒以上。这类问题本质上不是代码 bug是节奏问题调参优先级始终高于改代码。有些超时现象看起来像玄学换个时间段再跑就恢复了实际还是频率问题。如果调参后仍然被限流再考虑轮换 User-Agent不建议一上来就上代理池后者引入的变量更多。第四个问题比较隐蔽SQLite 数据库文件在采集过程中越来越大但逻辑数据量没涨多少。原因是 WAL 模式下频繁的更新操作会产生大量碎片页文件实际占用远大于有效数据量。解决方法是采集任务结束后执行一次 VACUUM 命令压缩文件。项目里可以在 export_csv.py 里加一个 --vacuum 参数每次导出前自动执行效果很明显。第五个问题涉及数据校验某城市在数据库里缺记录第一反应是采集没爬到实际很可能是清洗层按规则把记录丢弃了。排查时先看日志里的 DropItem 记录再看 cleaner 的校验规则是否合理。项目里 logger.py 专门把 dropped items 写进单独的日志文件就是为了让这类排查有迹可循。结合排查经验我的习惯是每次跑完采集后先看一眼 dropped 日志如果丢弃率异常升高优先怀疑解析层而非清洗层。5. 二次开发把天气数据集改造成自己的数据服务5.1 新增采集字段以空气质量为例加一个 AQI 字段需要走完整链路解析层提取、清洗层校验、存储层建列。先分析目标页面上 AQI 数值所在的节点然后在 parse_weather 里增加提取逻辑接着在 cleaner 里增加范围校验最后在建表语句中增加一列。# 在 parse_weather 中追加 AQI 提取 aqi: self._parse_int( weather_block.xpath( ./div[contains(class, aqi)]/text() ).get(default) ),改造时最容易漏的是 init_db.py很多人加了字段却忘记更新建表语句导致新字段没有写入位置。我的习惯是每加一个字段就同步改三处解析、清洗、建表。如果数据量后续上涨明显storage pipeline 的 SQL 可以改成批量插入把逐条 commit 改为攒批后一次性 commit写入速度能提升一个量级。5.2 把 SQLite 数据导出为 REST API天气数据集更大的价值在于服务下游应用。可以基于 Flask 写一个最小的只读 API让前端或定时任务通过 HTTP 获取数据# app.py 简易天气数据接口 from flask import Flask, jsonify, request import sqlite3 app Flask(__name__) DB_PATH db/weather.db app.route(/api/weather) def get_weather(): city request.args.get(city) date request.args.get(date) conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row rows conn.execute( SELECT * FROM daily_weather WHERE city_code ? AND date ? , (city, date), ).fetchall() return jsonify([dict(row) for row in rows]) if __name__ __main__: app.run(port8000)这个接口把数据库查询能力直接暴露给外部city 参数传城市编码date 参数传日期返回该城市当天的天气记录。实际部署时可以改成支持日期区间查询加一个分页参数就能支撑前端图表展示的需求。写接口时用参数化查询避免 SQL 注入另外要加一层访问控制比如白名单 IP 或请求头校验防止数据库文件被外部大量拉取。运行这套采集脚本半年后我最大的体会是数据采集项目的核心不是爬虫本身而是变更管理页面改版时怎么快速定位解析层数据量增长时怎么平滑迁移存储层新数据源接入时怎么复用清洗规则。从那以后我每次接手类似的采集工程都会强制自己先画一遍请求到落库的数据流图确认每一层的数据格式契约再动手改代码这套项目最值得借鉴的正是这种拆分思路。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑