ARTICLE DETAIL

资讯详情

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

数据采集基础梳理:从网页抓取到清洗入库的实战经验

数据采集基础梳理:从网页抓取到清洗入库的实战经验 做数据分析、搞业务报表的时候最尴尬的事情往往不是模型不会调也不是可视化不够炫而是数据根本拿不到。数据采集说白了就是把散落在网页、接口、文档里的信息按照结构化的方式收集、清洗再存进自己可控的数据库里为后续的分析、监控和决策提供原料。这几年我前后维护过好几个采集项目有跑一次就扔的小脚本也有每天定时执行、持续跑了一年多的常驻任务踩过的坑累积起来足够写一本小册子。这篇文章就把我理解的数据采集基础知识完整梳理一遍从思路、工具、实操到排障尽量说人话给到能直接用的方法。适合看这篇内容的人大概是这么几类刚入门的数据分析师或数据开发想自己动手抓点数据做研究的产品经理和运营同学以及想系统了解采集流程、避免被各种复杂方案绕晕的技术爱好者。内容不会涉及任何非常规手段所有案例都以公开、合规的信息获取为前提这一点后面会专门展开讲。1. 数据采集到底在解决什么问题很多人一听到数据采集第一反应是“写个脚本去抓网页”这个理解没错但太窄了。数据采集是一个从“信息源”到“可分析数据”的完整链路网页抓取只是其中最常见的一种形式。弄懂这条链路里每个环节在解决什么问题才能真正把采集这件事做好。1.1 数据采集不只是“把网页保存下来”我见过不少朋友第一次写采集脚本做出来的东西本质上是“批量下载网页”——拿到HTML就存成文件以为任务完成了。等真正要用数据的时候才发现HTML里夹杂着大量的样式、脚本、标签根本没法直接做统计、画图表。顶多能用CtrlF在浏览器里搜一搜关键词。所以数据采集的目标不是拿到网页而是拿到“字段”。所谓字段就是你在最终表格里看到的每一列比如一条新闻的标题、发布时间、来源链接、正文摘要或者一条商品的价格、销量、上架日期。采集的核心工作是从混杂的信息里精确提取这些字段再经过清洗、去重、标准化变成一行行干净的数据。这个思路决定了整个技术选型。如果只是偶尔抓一两个字段手动复制粘贴都能搞定如果需要每天更新上千条记录就必须用脚本配合数据库自动处理。想清楚“我要的是数据本身而不是网页文件”这件事很多方案取舍就变得很清晰了。1.2 数据来源的五种常见形态按获取方式来分数据源大概有五种形态实际工作中经常会混着用数据来源典型例子获取难度稳定性开放API天气接口、地图接口、交易所公告接口低按文档调用即可高有官方维护网页HTML新闻列表页、资讯详情页、公开讨论区中需要解析页面结构中页面改版会受影响结构化文件CSV、Excel、JSON压缩包低直接下载解析高一般有发布周期数据库直连公司内部数仓、授权共享库低有权限的话高日志流服务访问日志、设备上报数据较高需要对接消息通道高上面这几种里网页HTML采集是需求最旺盛、也最容易出问题的一类。原因很简单很多有价值的数据没有官方API或者API限制很严而公开网页上恰好有整理好的内容。比如公开财经社区里的讨论热度、行情资讯站点上的公告信息、政府公开的统计表格这些通常优先考虑看有没有文件下载或接口退而求其次才去做页面解析。1.3 先判断值不值得采成本收益评估很多人在采集开始前完全没有评估意识。拿到一个需求就闷头开写写了半天发现页面是动态加载的非得再用浏览器渲染工具结果速度和稳定性都很差。我自己的习惯是动手之前先问三个问题这个数据是一次性要还是长期要一次性需求可以直接手动导出或临时写脚本长期需求才值得搭完整链路。目标数据有没有更规范、更稳定的渠道先去网上搜搜有没有官方API、开放数据集、定期发布的统计文件比硬刚网页要省力得多。数据量级大概是多少几百条和几百万条的技术方案完全不是一回事前者用普通脚本加CSV就能搞定后者要考虑分布式、存储和调度。这三问看似简单但能筛掉至少三分之一的无用功。成本收益评估应该成为数据采集的第一步而不是等到脚本写了一半才回头想。2. 工具选型搞清楚方案比会写代码更重要我见过用一种工具吃遍所有场景的人也见过工具换了一堆、每个都只学了皮毛的人。数据采集领域的工具选择确实有点多但基础场景其实很集中。理顺了就能知道什么情况下该用什么。2.1 代码方案requests加BeautifulSoup加SQLite就够了先给一个我反复验证过的结论对于绝大多数中小规模的数据采集任务Python的requests库负责请求页面BeautifulSoup负责解析HTMLSQLite负责存储这三件套已经足够应对日常需求的八成。requests是目前最主流的HTTP请求库它做的事情就是帮我们模拟浏览器向服务器要内容。日常使用中设置好请求头特别是User-Agent、超时时间基本就能正常获取到公开页面。BeautifulSoup则专精于HTML解析它能把网页标签转成Python对象让我们用选择器去提取目标字段。SQLite则是轻量级数据库不需要额外起服务一个文件就是一个库配合Python内置的sqlite3模块非常好用。至于框架级别的工具比如Scrapy功能确实强大自带调度、去重、异步下载、管道输出适合大型采集项目。但它的学习曲线和学习成本也比较明显对刚入门的同学来说很容易一头扎进配置细节里反而忽略了数据采集本身的逻辑。我建议先把requests加BeautifulSoup用熟觉得脚本越来越复杂、效率不够了再考虑升级到Scrapy。另外提一个近年比较好用的解析库selectolax基于C语言实现解析速度是BeautifulSoup的好几倍。它的API设计偏向CSS选择器风格如果你对DOM操作比较熟悉上手会很快。追求性能的脚本可以用它来替代BeautifulSoup代价是资料相对少一点遇到问题需要多翻官方文档。2.2 轻量级可视化采集工具适合不想写代码的人不是所有人都有精力去学Python。产品经理想拉一点行业竞品数据运营同学想定期监控几个公开榜单这时候完全可以用可视化采集工具。主流的轻量级采集工具原理都差不多通过内置浏览器打开目标页面然后让你在页面文字上点选工具自动生成采集规则之后定时抓取并导出成Excel接口或数据库。这类工具的特点是上手快界面友好适合规则不太复杂的网页。缺点也很明显页面改版后需要重新配置规则而且能处理的反受限逻辑有限复杂场景还是得回到代码方案。选择建议很简单如果任务规模不大、字段不多、采集频率也不高可视化工具是效率最高的方案如果数据量大、逻辑复杂、要求自动化运维老老实实写代码。工具只是手段能稳定拿到数据才是目的。2.3 一个典型案例场景公开财经信息该怎么采我在实际工作中接触过不少“想抓财经资讯、讨论区观点、公告数据”的需求。这类数据有几个共同特点数据是公开可见的不需要登录信息更新频率高分钟级或小时级变化字段结构比较清晰一般是标题、链接、时间、正文、作者、浏览数之类。处理这类需求时我一般先去看目标站点有没有官方API如果没有就分析它的列表页结构。列表页通常是一个带有多个条目的HTML模块每条记录的结构基本一致非常适合用CSS选择器提取。提取完列表页之后如果想拿正文再点进详情页单独解析。整个过程分为“列表页解析”和“详情页解析”两步每一步都要防止网络异常、元素缺失、字段为空等意外导致的中断。提示公开可见的数据不代表可以无限制地采集。务必控制请求频率并在使用数据时注意版权和用户协议尤其是行情类数据很多原始版权归属交易所只适合个人学习研究不适合对外二次分发。3. 实操全流程从目标页面到结构化数据库这一部分我用一个虚构的公共资讯页面来演示完整流程。把这套流程跑通你就掌握了数据采集最核心的通用套路后续遇到不同站点只是换一下URL和解析规则而已。3.1 实操目标设定找对页面和字段假设需求是采集某个公开资讯站点的“财经资讯列表”每天获取最新的标题、链接、发布时间存起来做后续分析。目标很清晰字段也明确就是四个标题、链接、发布时间、抓取时间。动手写代码之前第一步永远是去浏览器里打开目标页面用肉眼确认两件事。第一你要的数据是不是直接在HTML里。怎么判断在页面上按Ctrl加U键查看源代码如果能搜到标题文字说明是服务端直接渲染的可以用requests正常请求如果源代码里搜不到说明数据是后加载的后面要换思路。第二记录字段在页面中的位置特征比如标题是在class为title的链接里时间在class为time的span标签里。这一步是后面写解析代码的依据。3.2 页面结构分析技巧浏览器开发者工具的正确用法定位字段位置我不建议直接看HTML源代码海选而是用浏览器开发者工具的“元素检查”功能。操作方法是在目标页面上右键点击某个标题选择“检查”浏览器会高亮显示这个元素对应的HTML片段。先看这个元素有没有类名、ID等可辨认特征再看它所在的父级容器结构。一般来说每条列表项都会包在一个共同的标签里比如ul下的li找到这个共同容器之后就能写出一条覆盖所有列表项的选择器。举个例子如果一条新闻的结构是这样的ul classnews-list li a classtitle href/news/123某财经新闻标题/a span classtime2025-06-01 10:30/span /li /ul那么目标就很明确先定位ul.news-list下面的所有li再在每一个li里分别提取a.title的文本和href属性以及span.time的文本。在开发者工具里可以直接验证选择器的正确性在控制台输入document.querySelectorAll(.news-list li).length如果输出结果和页面显示的条数一致说明选择器没问题。注意页面类名经常是混淆过的比如classel-card__body x9b这种。不要靠肉眼理解类名的语义只用它做定位就好。优先使用稳定的标签层级关系定位而不是依赖某个类名类名变更的频率往往比标签层级高。3.3 写最小可用脚本完整Python示例页面结构分析完之后就可以写脚本了。我通常会把需求拆成四个函数请求页面、解析数据、保存数据、主流程控制。这样的结构清晰后续出了问题也容易定位。下面是一段可以直接参考的示例代码目标地址请替换成你真正有权限访问的公开页面import time import csv from datetime import datetime import requests from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } TARGET_URL https://example.com/news?page{page} def fetch_page(page: int) - str: url TARGET_URL.format(pagepage) resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding return resp.text def parse_news(html: str) - list: soup BeautifulSoup(html, html.parser) items [] for li in soup.select(.news-list li): title_tag li.select_one(a.title) time_tag li.select_one(span.time) if not title_tag: continue items.append({ title: title_tag.get_text(stripTrue), link: title_tag.get(href), publish_time: time_tag.get_text(stripTrue) if time_tag else , fetched_at: datetime.now().isoformat(), }) return items def save_to_csv(items: list, filename: str news.csv) - None: with open(filename, a, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnames[title, link, publish_time, fetched_at]) if f.tell() 0: writer.writeheader() writer.writerows(items) if __name__ __main__: for page in range(1, 6): html fetch_page(page) parsed parse_news(html) save_to_csv(parsed) print(fpage {page} done, saved {len(parsed)} items) time.sleep(2)这段代码里有两个容易被忽视的细节。第一是resp.encoding resp.apparent_encoding这句话它让requests根据页面内容自动推断编码能解决大量中文乱码问题。第二是每次请求之间加了time.sleep(2)这是刻意控制频率避免对目标服务器造成压力。很多新手喜欢把sleep去掉觉得跑得快才爽实际上设置合理间隔是数据采集的基本素养也是保护自己任务稳定性的方式。3.4 清洗、去重与入库很多人采集完数据直接看一眼CSV文件就认为大功告成了。实际上采集拿到的数据很少是干净的至少要做三步处理第一步是格式标准化。时间字段可能有“2025/06/01”“2025年6月1日”“06-01 10:30”等多种形式最好统一转成ISO标准格式。标题里的空格、换行、特殊符号要去掉。链接如果是相对路径要拼接成完整URL。第二步是去重。同一篇文章可能在多个列表页里交叉出现或者定时采集时重复抓到同一条去重逻辑一般基于某个唯一标识。如果链接是稳定的用链接做唯一键最简单如果链接带统计参数会变化就用标题加发布时间的组合判断。第三步是存库。当数据量达到几千条以上CSV操作起来就比较吃力了改用SQLite会更顺手。SQLite支持增删改查和去重约束适合单机使用。建表时给唯一字段加上UNIQUE约束再用INSERT OR IGNORE写入就能天然实现“重复数据不再入库”。下面是一个简单的建表脚本示例import sqlite3 conn sqlite3.connect(news.db) conn.execute( CREATE TABLE IF NOT EXISTS news ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, link TEXT UNIQUE NOT NULL, publish_time TEXT, fetched_at TEXT ) ) conn.commit()把“解析结果”和“存储逻辑”分开写是很好的习惯。当你想把数据输出到Excel、MySQL或者推送到消息队列时只需要改保存函数解析逻辑完全不用动。4. 高频问题与排查经验汇总采集脚本跑不通原因基本集中在几个点上。这里把我踩过最多的坑和排查思路总结成一套“诊断清单”照着排查大多数问题都能在几分钟内定位。4.1 拿到的HTML和浏览器里看到的不一样这个问题出现的概率极高。你在浏览器里能看到完整内容脚本打印出来的HTML却只有空壳。原因通常是数据是通过JavaScript动态渲染的初始HTML里只有页面框架真正的数据由浏览器执行脚本后从接口加载。排查手法很固定先在浏览器里查看源代码搜索目标文字。如果源代码搜不到说明是动态加载。再打开开发者工具的网络面板刷新页面查看浏览器实际发出了哪些异步请求重点找返回内容是JSON或数据结构的接口。一旦找到这个接口直接请求接口往往比渲染页面更高效、更稳定。对于实在找不到接口、又必须渲染页面才能拿到数据的场景可以用Playwright或Selenium这类浏览器自动化工具。它们的原理是打开一个真实浏览器内核来执行页面脚本然后获取渲染完成后的HTML。这种方法几乎能解决所有动态加载页面代价是运行速度慢、资源占用高、维护比较复杂所以要作为最后手段而不是首选方案。4.2 中文乱码到底怎么根治乱码问题的根源只有一个网页实际使用的编码和解析时使用的编码不一致。常见的中文网页编码有两种UTF-8和GBK或GB2312还有少量历史页面用GB18030。我之前有个土办法是“一个编码不对就换另一个试”后来发现更好的做法是让程序自动识别。requests库默认使用响应头里声明的编码但很多网站响应头声明不准确这时候让apparent_encoding出场就能解决它会根据原始字节内容推测编码准确率相当高。需要啰嗦一句的是保存CSV时建议使用utf-8-sig编码而不是utf-8。二者的区别在于utf-8-sig会在文件开头写入BOM头这样用Excel直接打开CSV时中文不会乱码。这个细节很小但能让使用文件的人少踩一个坑。4.3 请求失败、被限流的应对脚本跑着跑着突然报超时或者返回状态码变成403通常是请求频率太高被服务端暂时限制。这种场景下我的第一反应不是换代理骗过去而是先审视采集方式是否合理。正确的做法永远包含这几条限制请求速率比如每请求一次就sleep几秒给请求头设置完整的浏览器标识避开目标站点的高峰时段在代码里加指数退避重试逻辑而不是失败后立刻疯狂重试。遇到403时先停半小时再继续基本就能恢复访问。如果业务确实需要大流量、高质量地获取数据正确路径是联系数据方申请官方API或者商务合作授权。官方接口通常有配额和规范但胜在稳定、合规、对双方都有保障。绕开限制去硬碰只会让双方的体验都变差。4.4 断点续采与增量更新采集任务跑一半程序崩了是特别让人头疼的事。重新跑一遍全部历史数据既浪费时间又制造大量重复请求。解决办法是引入断点机制把“已经成功采集到哪一页”的状态记录下来。最简单的实现是维护一个进度文件。每成功处理完一个页面就把页码或页面对应的唯一标识写入文件。下次脚本启动时先读取进度文件从上次失败的位置继续。更稳妥的方式是把“已采集链接”存到数据库表里每次处理数据之前先检查链接是否已存在存在就跳过。这套思路配合在线程里定时提交进度能做到“任你崩我自岿然不动”。增量更新的逻辑与之类似但要额外考虑数据的新增和变化。很多列表页的最新数据是按时间倒排的适合记录“上一次抓到的最大时间”然后只抓在该时间之后新增的内容。需要注意有些页面的排序不是严格按时间而是按热度或运营推荐这种情况下靠时间做增量就会漏数据最好结合内容ID或已采集集合做判断。4.5 问题速查表现象常见原因优先排查方向HTML拿到但解析不到数据选择器写错或页面结构改变用开发者工具验证选择器中文乱码编码识别错误设置resp.encoding只有框架没有内容JS动态渲染找异步数据接口请求返回403频率过高被限制降低频率、检查请求头脚本中途崩溃网络异常或字段缺失加异常捕获和断点续采数据重复严重缺少去重机制用链接或标题做唯一键页面偶尔解析失败目标字段为空增加字段缺失容错5. 数据采集的合规边界与长期维护最后这部分不直接教你写代码但比写代码重要得多。数据越采越多一些边界问题就需要越早想清楚。5.1 数据采集的合规边界数据采集要守住几条清晰的边界只采集公开可访问的信息不碰需要破解权限或绕过登录的内容不收采集手机号、身份证、地址等个人隐私字段这类数据即使公开出现也要格外审慎遵守目标网站的robots协议和用户协议控制请求频率不以影响对方服务为前提采集所得的数据只用于个人学习研究不做商业二次分发。尤其是行情类数据很多原始版权归属交易所或数据供应商平台只是数据展示方。这类数据即使页面完全公开也不代表可以随意打包导出给别人用。我个人处理这类需求时都会先确认数据用途如果是内部研究、个人分析问题不大如果需要对外提供老老实实走数据授权渠道。5.2 采集任务的维护成本与稳定性监控运行时间超过一个月的采集任务最大的敌人不是写不出来而是“悄悄失效”。网站的页面结构会改接口字段会调整字段内容格式会变化运行环境也会出各种意外。采集脚本成功跑一遍只是开始持续稳定运行才是考验。我的做法是至少给定时执行的采集任务加两层保障第一层在脚本内做异常捕获和日志记录每轮采集完成之后输出汇总信息比如成功条数、失败条数、耗时写入日志文件。第二层在外面套一个监控检查如果连续几轮采集到的数据量为零或者日志文件长时间没有更新推送提醒到本地消息渠道。数据量骤降通常是网站改版或反限制调整导致的有效信号。页面结构变化后的修复成本要远低于最初编写脚本的成本因为解析逻辑固定在脚本的少数几个函数里。只要当时写得结构清晰改起来只是调整选择器的事。这又回到了第三部分说的把请求、解析、保存分开设计是长期维护的基本功。5.3 数据质量校验采集不等于拿到干净数据采集到数据之后质量校验是很多人容易忽略的一环。一张表里塞满了解析结果不代表这些数据能直接支撑分析。时间字段可能是空字符串标题可能混入广告文案部分记录可能重复还有的页面在改动时输出结构不完整。我习惯在入库前做一次“字段级别体检”巡检函数检查必填字段是否为空、时间字段能否被正常解析成时间类型、标题长度是否在合理区间、链接是否属于目标域名。体检结果单独存一份异常记录表既不干扰正常数据入库又能提醒我回头处理异常样本。这套做法的核心思想是“把数据质量当成采集任务的正式环节而不是附带的检查”。采集链路的价值最终体现在下游分析能不能信任这批数据。宁可多花一点时间做校验也不要把脏数据送进报表里否则后续排查会花掉十倍的时间。写在最后我维护采集项目这么长时间最大的体会是真正难的不是写第一个能跑的脚本而是让一个脚本在无人值守的情况下持续稳定运行。数据采集本质上是个工程问题需要你在需求定义、工具选型、解析逻辑、异常处理、合规判断多个层面都考虑清楚。这篇文章的每条经验都是从实际项目中磨出来的照着这套框架上手可以少走不少弯路。最后再分享一个我自己的小习惯初次接触一个目标站点永远先抓一小批样本数据人工检查一遍确认没有异常再放开全量采集这个步骤十次有九次能帮你提前发现隐藏问题。
返回列表