ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从遗留系统到通用数据平台:构建可配置化多源数据收集系统的实战

2026/8/28 15:51:44 拓冰建站 浏览量
从遗留系统到通用数据平台:构建可配置化多源数据收集系统的实战 1. 项目缘起一个被“遗忘”的数据收集站几年前我接手了一个听起来有点“历史感”的项目一个名为“2021亚太数据收集网站”的遗留系统。接手时项目文档几乎为零代码仓库里只有一堆未经整理的脚本和零散的数据库表。这个标题本身就很耐人寻味——“2021亚太数据收集”它暗示了特定的时间2021年、地域亚太和目的数据收集但具体收集什么、怎么收集、收集来做什么一概不清。这恰恰是很多企业内部数据项目的典型写照一个因临时需求而启动完成后便被束之高阁直到某天需要复用或审计时才被重新翻出来的“考古现场”。我的任务就是把这个“考古现场”变成一个清晰、可维护、甚至具备一定复用价值的数据管道。这不仅仅是一次代码重构更是一次对数据工程核心逻辑的梳理实战。通过这个项目我深刻体会到一个健壮的数据收集系统其价值远不止于“把数据存下来”更在于如何定义数据、如何保证其质量、如何设计其生命周期以及如何为未来的不确定性预留接口。接下来我将以这个项目为蓝本拆解构建一个区域性数据收集网站的核心架构、技术选型、踩坑实录与演进思考。2. 核心需求考古与业务逻辑重建面对一个空白的项目正文第一步不是写代码而是当“侦探”进行需求考古。关键词和摘要描述的缺失迫使我们必须从项目标题、遗留代码和可能的数据残留中反向推导。2.1 从标题“2021亚太数据收集”拆解隐含维度“2021亚太数据收集”这个标题本身就包含了多个关键维度我们需要逐一明确时间维度2021这是指数据产生的时间还是项目启动的时间经过与最初已离职的同事沟通和日志分析确认“2021”主要指代数据的时间范围。系统需要收集2021年度或至少以2021年为关键时间节点的亚太区数据。这引出了对数据“时间新鲜度”和“历史回溯”能力的要求。地域维度亚太亚太地区包含众多国家和地区差异巨大。需要明确地理粒度是按国家如中国、日本、澳大利亚还是按城市/区域收集数据代表性是否要求覆盖主要经济体对于网络数据是否需要处理不同国家的语言中文、日文、英文、韩文等和网站本地化问题合规性考量不同国家和地区的数据隐私法规如中国的《个人信息保护法》、欧盟的GDPR虽不直接适用但具有参考价值对数据收集有何限制这是初期最容易被忽略后期却可能引发严重问题的关键点。数据收集这是最核心的部分。收集什么数据从哪里收集数据源类型是公开的网站数据Web Scraping、第三方API接口、合作伙伴的数据文件推送还是用户通过前端表单提交的数据数据内容可能是市场报告摘要、新闻资讯、社交媒体趋势、公开的经济指标或是特定的产品信息。我们需要从遗留的数据库表结构和爬虫脚本残留的URL模式中推断。收集频率是每日、每周、每月一次的批量收集还是近实时的流式收集通过分析遗留的数据库我发现了几张表如articles_2021、economic_indicators和source_config。结合脚本里的一些域名多为亚太地区的新闻门户和政府统计网站我初步重建了业务逻辑这是一个用于收集2021年度亚太地区主要国家的宏观经济新闻、行业报告摘要以及部分公开经济指标并进行集中存储、去重和简单分类的系统。初始目标可能是为内部的市场分析周报提供数据素材。2.2 重建后的系统核心目标与用户故事基于考古结果我为新系统明确了核心目标多源、异构数据归一化能从不同类型的源HTML页面、API JSON、CSV文件中抽取结构化信息并统一存储。自动化与可靠性配置化调度任务实现无人值守的自动收集并具备完整的错误处理与重试机制。数据质量基础保障实现去重基于标题、URL、内容指纹、基础字段校验非空、格式、和数据来源标记。可观测与可维护所有数据收集任务的状态、日志、性能指标必须清晰可见便于排查故障和优化性能。架构前瞻性虽然聚焦2021年数据但架构上应能方便地扩展至其他年份和地区。对应的用户故事很简单作为市场分析师我希望每天上午能自动获取一份包含昨日亚太区重点新闻和经济数据的清单以便我快速编写市场动态简报。作为数据工程师我希望当某个数据源失败时能立即收到通知并查看详细错误日志以便快速修复。作为系统管理员我希望能通过一个简单的界面启停、配置收集任务而无需修改代码。3. 技术栈选型轻量、可控与高效考虑到这是一个偏内部、数据量中等预计每日数万条记录、且需要快速迭代验证的业务系统我选择了以下技术栈核心原则是“用成熟的工具解决特定问题避免过度设计”。3.1 后端与数据收集引擎语言Python几乎是数据抓取和脚本编写的首选。生态丰富Requests, BeautifulSoup, Scrapy, Pandas开发效率高适合快速原型和迭代。爬虫框架Scrapy 自研调度中间件对于结构复杂的网站Scrapy框架提供了优秀的扩展性和并发处理能力。但我没有直接使用Scrapy的默认调度器而是将其作为“抓取执行单元”集成到更大的任务调度系统中。对于简单的API调用或静态页面直接使用RequestsBeautifulSoup/lxml组合更加轻量灵活。任务调度Celery Redis这是系统的“大脑”。Celery是一个强大的分布式任务队列完美契合“定时触发不同数据收集任务”的需求。我使用Redis作为Broker消息代理和Result Backend结果存储。每个数据源对应一个Celery任务任务的调度周期每天、每周和参数如URL、解析规则通过数据库配置。为什么不用CronCron适合简单的定时脚本但缺乏任务状态跟踪、重试机制、分布式执行和复杂的依赖管理。Celery提供了所有这些功能并且与Python生态无缝集成。数据存储PostgreSQL 对象存储MinIOPostgreSQL存储所有结构化和半结构化的元数据。例如文章的标题、来源、发布时间、摘要、分类标签、原始URL以及任务执行日志。利用其JSONB字段存储从不同源提取的、结构可能变化的原始数据片段非常灵活。MinIO兼容S3协议的对象存储用于存储非结构化或大型数据。例如爬取到的原始HTML页面用于审计和调试、下载的PDF报告原文、图片等。将数据Data与元数据Metadata分离是重要设计这避免了数据库膨胀并利于低成本存储大量原始资料。数据去重与指纹Bloom Filter 文本哈希在内存Redis中使用布隆过滤器进行快速、低内存占用的URL去重预判虽然可能有极小的误判率但能拦截绝大多数重复抓取。对于内容去重防止不同URL发布相同文章使用simhash算法为文章正文生成指纹并存储在PostgreSQL中。当新文章指纹与库中已有指纹的汉明距离小于某个阈值如3时则判定为重复。3.2 前端与管理界面框架Vue.js Element UI为了给内部运营人员提供一个简单的管理界面我选择Vue.js构建一个轻量级单页应用。Element UI组件库能快速搭建出美观且功能齐全的后台界面。功能模块任务看板以卡片或列表形式展示所有数据收集任务包括名称、状态运行中/成功/失败、上次执行时间、下次执行时间、成功率等关键指标。任务配置表单化配置任务参数如源URL、CSS选择器/XPath解析规则、API密钥、请求头、调度Cron表达式等。这些配置存入数据库任务代码从数据库读取配置实现“配置驱动”无需发版即可修改抓取规则。数据预览表格化展示最新收集到的数据支持按时间、来源、关键词筛选方便运营同学快速验证数据质量。日志查询集中查看每个任务每次执行的详细日志包括INFO、WARNING、ERROR等级别是排查问题的第一现场。3.3 基础设施与部署容器化Docker Docker Compose将Python爬虫服务、Celery Worker、Web前端、PostgreSQL、Redis、MinIO等所有组件容器化。docker-compose.yml文件一键启停整个开发环境保证了环境一致性也简化了后续向Kubernetes迁移的路径。监控告警Prometheus Grafana 钉钉/企业微信机器人在Celery Worker和Django应用中暴露Prometheus指标如任务执行次数、成功率、耗时、队列长度等。Grafana配置仪表盘可视化监控系统健康度。通过配置Celery的任务失败重试机制和告警钩子当任务连续失败时自动通过机器人向运维群发送告警消息包含任务ID和错误摘要。这个技术栈看似组件不少但每个都职责明确且都是该领域久经考验的成熟方案组合起来形成了一个松耦合、易扩展的架构。4. 核心实现细节与避坑指南有了架构设计接下来就是具体的实现。这里分享几个关键环节的实现细节和踩过的坑。4.1 配置化爬虫解析器应对网站改版静态写在代码里的解析规则XPath/CSS选择器是爬虫的“阿喀琉斯之踵”网站一旦改版整个抓取就失效。我们必须实现配置化。解决方案在数据库中为每个数据源data_source表创建一条记录其中包含一个parsing_rules字段JSONB类型。规则设计为一种简单的DSL领域特定语言。{ item_selector: div.article-list article, fields: { title: { selector: h2 a, type: text, required: true }, url: { selector: h2 a, type: attr, attr: href, required: true, transform: make_absolute // 一个处理相对URL的转换函数 }, publish_time: { selector: time.pub-date, type: attr, attr: datetime, required: false, transform: parse_iso_datetime }, summary: { selector: p.excerpt, type: text, required: false } } }爬虫任务执行时会从数据库加载对应源的配置然后使用一个通用的解析引擎基于parsel或lxml来执行这些规则。这样当某个网站改版时运营人员只需在管理界面更新这条JSON配置而无需工程师修改和部署代码。踩坑记录最初我尝试用YAML文件存储配置但发现当规则需要动态更新时文件管理很麻烦。存入数据库后又遇到了JSON结构设计过于复杂的问题导致前端配置界面难以生成。最终我采用了上述的扁平化结构并为transform字段预定义了一批常用的清洗函数如去空格、日期转换、编码处理平衡了灵活性和易用性。4.2 反爬虫策略的温和应对收集公开数据必须遵守Robots协议并采取温和、有节制的策略避免对目标网站造成压力。尊重Robots.txt使用robotparser模块在抓取前检查目标路径是否被允许。设置合理的请求头模拟真实浏览器的User-Agent并携带Referer。请求速率限制在Celery任务中使用time.sleep(random.uniform(1, 3))在请求间加入随机延迟。对于同一域名使用一个全局的基于Redis的令牌桶限速器确保不会超出对方承受范围。使用IP代理池谨慎评估对于某些反爬特别严格的网站可能需要使用代理IP。我们自建了一个小型代理IP池从几个付费供应商获取IP并持续检测其可用性和匿名度。这里必须极度谨慎确保代理IP的用途合法合规并明确知晓其地理位置特别是收集亚太数据时最好使用亚太本地的出口IP数据更准确。错误处理与重试使用Tenacity或Retrying库为请求函数添加装饰器对网络超时、连接拒绝等临时性错误进行指数退避重试。对于HTTP 403/429频率限制等错误则延长重试等待时间或直接标记任务失败并告警。4.3 数据质量保障的“三道防线”数据收集上来质量是生命线。我们建立了三个层级的质检。采集时校验第一道防线字段完整性在解析规则中标记required: true的字段如果抓取不到则本条记录视为无效记录警告日志。格式初筛通过transform函数进行基础清洗如日期格式转换失败则置为NULL。入库前清洗第二道防线在数据进入PostgreSQL前有一个专门的清洗管道Pipeline。这里进行更复杂的操作文本去噪去除HTML标签、多余空白字符、不可见字符。语言检测使用langdetect库判断文章正文语言与预期的“亚太语言”中、英、日、韩等进行比对过滤掉明显不符的如俄语、阿拉伯语垃圾或干扰数据。关键信息抽取使用简单的正则或关键词匹配尝试从正文中抽取提及的国家、地区、公司名称作为补充标签。入库后监控第三道防线编写定时监控任务每天检查各数据源的成功率、数据量环比/同比波动。如果某个源今日数据量骤降为0或仅为平时的10%则触发告警提示“可能源网站结构已变更或抓取失败”。对核心字段如发布时间进行统计检查是否存在大量未来日期或过于陈旧的日期这可能是解析规则错误。4.4 调度系统的健壮性设计Celery是核心但默认配置很脆弱。以下是几个关键配置点任务幂等性每个收集任务都被设计为幂等的。即在相同输入如相同的配置ID和执行日期下多次执行产生的结果和副作用相同。这通过任务开始时在Redis中设置一个分布式锁SETNX key task_id来实现防止同一任务被意外并发执行。结果处理与链式任务一个数据收集任务如crawl_news成功后会自动触发一个后续处理任务如process_and_store_news。我们使用Celery的chain或link功能来实现。这样主任务只负责抓取和初步解析将原始数据放入消息队列后续的清洗、去重、存储由另一个专门的Worker处理实现了职责分离和负载均衡。Worker的优雅退出与并发控制为Celery Worker配置max-tasks-per-child参数让每个Worker进程在执行一定数量任务后重启防止内存泄漏。同时根据机器资源合理设置每个Worker的并发数-c避免过多并发导致网络连接耗尽或目标网站压力过大。定时任务Celery Beat的持久化使用django-celery-beat或直接将定时任务配置存储在数据库中而不是写在代码里。这样可以通过管理界面动态添加、修改或禁用定时任务非常灵活。5. 从“2021”到“任意时间”系统的演进思考项目初期紧扣“2021”但好的架构应该能优雅地适应变化。随着业务方提出“能不能也收集一下2022年的数据”、“欧洲的数据能不能用同样的系统”我们预先做的设计就派上了用场。时间维度的抽象我们不再硬编码“2021”而是在任务配置中增加了一个time_range字段可以是{year: 2021}也可以是{start: 2021-01-01, end: 2021-12-31}甚至是动态的{offset: -7d}抓取最近7天。任务执行时会根据这个配置去生成具体的抓取URL或API查询参数。地域维度的插件化我们将与地域相关的逻辑如语言处理、特定网站解析规则、本地时区转换抽象成“地域插件”。每个插件对应一个地区包如parsers.asia.japan里面包含该地区特有的处理函数。系统根据数据源配置的region_code来加载对应的插件。这样扩展新地区时主要是编写新的插件核心框架变动很小。数据模型的版本化随着收集的数据类型增多我们为存储的数据引入了简单的版本概念。在元数据表中增加一个schema_version字段。当数据结构发生不兼容变更时例如新增一个必填字段不是直接修改原表而是创建新版本的数据处理管道并将新数据存入带有版本后缀的新表或通过JSONB字段的扩展属性来存储。这保证了历史数据的可访问性也便于进行A/B测试。这个项目让我明白面对一个模糊的遗留系统最重要的不是急于重写代码而是先花大力气理解其背后的业务意图和数据逻辑。技术选型上平衡“够用”与“前瞻”用配置化对抗变化用监控保障稳定用清晰的架构隔离关注点。最终这个为“2021亚太”而生的系统成功演进为了团队内一个通用的“多区域时序数据收集平台”这或许是对当初那段“考古”与“重建”工作最好的回报。