数据抓取

如何用网页抓取构建数据集

一份构建干净、完整的网页抓取数据集的实战指南:流水线各阶段、新鲜度、去重,以及为什么封禁和地理缺口会悄悄让你的数据产生偏差。

Chris Collins

Chris Collins

2026年6月23日 · 2 分钟阅读

大多数讲”用网页抓取构建数据集”的指南,停在了”写个爬虫、把结果存下来”。那是容易的那 20%。难的那 80%——决定你的数据集到底能不能用的那部分——是围绕抓取(fetch)的一切:确保你采到的是对的行、它们是完整的、是当前的,而且你数据里的缺口是随机的,而不是系统性的。

最后这一点,正是悄悄毁掉数据集的那一点,也是几乎没人写的那一点。当一次抓取在 30% 的页面上失败时,你丢的不是随机的 30% 数据。你丢的是特定的 30%——更难够到、防御更强、地理限制更多的那一片——而剩下的,是一份看起来很完整的、有偏差的样本。一个在它上面训练的模型、或一个基于它做出的决策,会在没人察觉的情况下继承那份偏差。

这是一份能扛得住的网页抓取数据集构建实战指南:流水线各阶段、真正重要的质量维度,以及代理层在哪里成了”代表性数据集”和”有偏数据集”之间的分界线。

“一个好数据集”到底意味着什么

在写任何代码之前,先想清楚你在优化什么。一个由抓取构建的数据集,按五件事来评判:

完整性。 你采到了范围内的全部,还是只采到了那些没还手的部分?缺行不好;系统性缺行更糟。

代表性。 样本与真实的总体相符吗?如果你抓产品价格,但你的爬虫在高流量大零售商那里被封、在小商家那里畅通无阻,那你的”平均价”就在一个你看不见的方向上是错的。

新鲜度。 网页数据会过期。上个月的价格数据集,和今天的是两份不同的数据集。你需要知道每一行有多旧,并有一个刷新它的计划。

一致性。 每一行都应遵循同一个 schema,有同样的单位、格式、编码。抓取从乱糟糟的 HTML 里拉数据,所以归一化是一半的工作量。

来源(provenance)。 对每一行:它从哪来(源 URL)、你什么时候取的、从哪里取的(地理)。没有来源,你之后就无法调试、去重、刷新或为这个数据集辩护。

把这五点记在心里,因为下面流水线的每个决策,都在服务于其中之一。

流水线,逐阶段

一条抓取到数据集的流水线是六个阶段。把它们当作各自带验证的独立步骤,而不是一个大脚本。

1. 发现。 枚举范围内的 URL——来自 sitemap、一次搜索/列表 crawl、一个 API 索引,或一个已知的 ID 区间。这个阶段定义了你预期的总体。把它明确写下来;它是你之后衡量完整性的尺子。

2. 抓取(fetch)。 取回每个 URL。封禁、地理重定向、速率限制和超时都发生在这里,数据集的偏差也诞生在这里。下面会细讲,因为这是对质量最要紧的阶段。

3. 提取。 把响应解析成结构化字段。要写得防御性强:布局会变、字段会消失,一个脆弱的选择器会悄无声息地在数千行里变成 null。

4. 归一化。 把提取出的原始值转成一致的类型和单位——货币统一到一种计价、日期转 ISO、去掉空白、修好编码、把分类值映射到一个受控词表。

5. 去重。 同一个实体常常出现在多个 URL 上(规范页 + 变体、分页重复、重新上架的条目)。按一个稳定的键去重,而不是按 URL。

6. 存储与刷新。 带着完整来源持久化,然后定一个 re-crawl 的节奏,让数据集保持新鲜,而不是退化成一次性快照。

决定质量的那个阶段:抓取

这是整篇文章的核心论点。你数据集的质量上限被卡在抓取阶段,因为一个被封的请求不只是丢数据——它丢的是非随机的数据。

三种机制把抓取失败变成数据集偏差:

封禁偏差。 反爬系统(Cloudflare、Akamai、DataDome)对高价值、高流量目标的保护最凶。如果你的爬虫从数据中心 IP 跑、在那些目标上被封,你的数据集就在系统性地丢掉最重要的行、留下容易的行。结果向更小、防御更弱的来源倾斜,而它看起来很完整,因为你毕竟还是拿到了数千行。(机制见爬虫为什么会被封。)

地理偏差。 很多站点按访客位置提供不同的内容、价格或可用性,并根据 IP 悄悄重定向或本地化。如果你所有请求都从一个地区发出,你数据集里每一个随地理变化的字段,反映的都是那一个观察点,而不是你以为捕获到的全球现实。从一个国家抓”全球产品可用性”,你实际抓到的是一个国家的视图,却被错误地标成了全球。

速率限制偏差。 当一个目标对你限速,幼稚的反应是放慢、或放弃那些响应慢的页面——而那些往往是又重又富含数据的页面。你最终过度采样了又快又轻的页面。

这三者的解法是同一个:通过一池看起来像真实用户、在正确位置的 IP 来抓取,让覆盖完整而均匀,而不是向”哪些容易够到”倾斜。

为什么代理层是一个数据质量决策,而不只是管道

这正是为什么一个住宅代理网络专门对构建数据集重要——超出”不被封”之外:

完整覆盖。 住宅 IP 带着真实消费者连接的信任画像,所以它能在数据中心 IP 够不到的、有防御的目标上过去。这就堵上了封禁偏差的缺口——你采到了难的行,而不只是容易的行。

有意为之的地理覆盖。 用国家、州、城市定位,你可以刻意对每个市场采样,并给每一行打上它来自哪个观察点的标签。你得到的不是一个偶然的视角,而是一个受控的多地理数据集,其中地理是一列,而不是一个隐藏的混杂因子。这就是”我抓了价格”和”我抓的是从 12 个特定市场看到的价格、并按行记录下来”之间的差别。

规模上的均匀采样。 在一个大池子里轮换能把请求摊开,让没有任何单个 IP 触发速率限制,从而避免你过度采样快页面、欠采样又慢又富含数据的页面。

直白地说:代理层就是你决定”你的数据集是代表性样本,还是便利样本”的地方。对数据集工作来说,这不是管道细节,而是方法论选择。(更宽的基础设施视角见机器学习的代理基础设施。)

新鲜度:数据集是个动词,不是名词

一次性的抓取是一张快照,而快照会腐烂。一开始就决定你是在构建一个静态数据集(对一次性的研究没问题),还是一个活的(价格、库存、列表、任何会变的东西都需要)。

对活的数据集:

  • 定一个 re-crawl 节奏,匹配数据变化的速度——波动的价格按小时、目录元数据按周、慢变的参考数据按月。
  • 做增量刷新,而不是完整重抓。 检测什么变了(ETag、last-modified、内容哈希、列表 diff),只重新取那部分。更便宜、更快、对目标也更轻。
  • 给每一行盖上抓取时间戳,这样下游消费者能按新近度过滤,你也能衡量陈旧程度。

新鲜度也是个覆盖问题:如果你的刷新 crawl 每次都在同样那些有防御的页面上被封,那些行就会变旧、而容易的行保持当前,随时间重新引入偏差。同一个解法。

去重与归一化:数据集在这里见输赢

原始抓取数据是脏的。两个阶段把它清干净:

归一化到一个 schema。 先决定目标 schema,然后把每个来源映射进去。货币统一到一种计价、日期转 ISO 8601、从”1,299 件”这样的字符串里把数字解析出来、文本去空白并做 unicode 归一化、分类值对齐到受控词表。不一致的归一化,是一个抓取数据集”技术上完整、分析上没用”的最常见原因。

按稳定的键去重,而不是 URL。 同一个产品、人或记录,惯常住在好几个 URL 上。从稳定身份(SKU、ISBN、归一化后的名称+位置、规范 URL)构造一个去重键,把重复折叠掉,保留最新或最完整的那个版本。只按原始 URL 去重,会留给你被夸大的计数、和双倍加权的行,它们会悄悄扭曲任何聚合。

带着来源存储

对每一行,至少存:

  • 它来自的源 URL
  • 抓取时间戳(UTC)
  • 请求所用的地理观察点(国家/城市),如果地理对数据重要
  • 一个内容哈希或版本,以便在 re-crawl 时检测变化
  • 原始 payload(或对它的引用),和解析出的字段分开存,这样当你的提取器变好时,你可以重新解析而不必重新抓取

来源感觉像是额外开销,直到第一次有人问”这个数字哪来的”,或你的提取器有个 bug、你需要在不碰网络的情况下重新解析 50 万行。从第一天起就存它。

在信任数据集之前先验证它

在任何人基于这个数据集之上构建之前,跑一遍覆盖和质量检查——这就是你抓住抓取阶段可能引入的偏差的方式:

  • 覆盖审计。 把采到的行和发现阶段的预期总体对比。92% 的完成率没问题;问题是缺的那 8% 是不是随机的。抽查那些失败——如果它们聚集在一个来源、一个地理或一种站点类型上,你就有系统性偏差要修,而不只是缺数据。
  • 每字段空值率检查。 一个忽然 40% 为 null 的字段,通常意味着选择器坏了,而不是数据缺失。
  • 分布合理性检查。 价格分布、类别构成、地理分散,和你预期的相符吗?一处偏斜,往往揭示上游的一个采样问题。
  • 新鲜度检查。 行的年龄分布是什么样?如果有一块总是陈旧,那你的刷新 crawl 在那里被封了。

这些检查很便宜,而它们就是”交付一个数据集”和”交付一个自信地错了的数据集”之间的差别。

关于负责任地做这件事的一点说明

从网上构建数据集带着真实的义务。只采公开数据、在 robots.txt 起承载作用的地方尊重它、尊重速率限制、别拖垮你拉取的站点、除非有合法依据否则远离个人数据、并遵守每个目标的条款。代理改变的是请求从哪个 IP 发出,而不是你是否应该发它。我们的可接受使用政策是 Shifter 上”什么被允许”的最终依据,在你扩大规模之前,合乎道德的数据采集值得一读。

常见问题

用网页抓取构建数据集,最难的部分是什么? 不是抓取,是覆盖。拿到一个完整、无偏的样本,远比取回页面更难,因为失败的请求会移除非随机的数据切片,而得到的数据集仍然看起来完整。大多数数据集质量问题都追溯到抓取阶段。

封禁是怎么让一个抓取数据集产生偏差的? 反爬系统对高价值目标的保护最凶,所以一个被封的爬虫丢掉的是重要的、防御良好的行,留下的是容易的行。数据集向防御较弱的来源倾斜,这会腐蚀任何建立在它之上的聚合或模型。

构建数据集需要住宅代理吗? 只有当你的目标封禁数据中心 IP、或按地理改变内容时才需要——而大多数有价值的目标都会这么做。对未受保护、地理中立的来源,数据中心 IP 就够。对有防御或本地化站点的完整、有代表性的覆盖,住宅代理堵上偏差缺口。

怎么让一个抓取数据集保持新鲜? 定一个匹配数据变化速度的 re-crawl 节奏、做增量刷新(用 ETag/哈希/diff 检测变化,而不是完整重抓)、并给每一行盖上抓取时间戳,以便衡量陈旧程度并据此过滤。

我该怎么给抓取数据去重? 按一个稳定的身份键(SKU、ISBN、规范 URL、归一化的名称+位置),绝不要按原始 URL,因为同一个实体出现在很多 URL 上。把重复折叠到最新或最完整的版本。

除了提取出的字段,我还该存什么? 来源:源 URL、抓取时间戳、地理观察点、用于变化检测的内容哈希,以及理想情况下原始 payload——这样当你的提取器变好时,可以重新解析而不必重新抓取。

结论

用网页抓取构建数据集,是一个穿着抓取外衣的数据质量问题。谁都能取回页面;真正的功夫,是确保你取回的是对的页面、完整、当前,而且在本该有难目标的地方没有一个系统性的洞。流水线——发现、抓取、提取、归一化、去重、存储、刷新——很直白。那个悄悄给你质量封顶的唯一阶段,是抓取,因为正是在那里,封禁和地理把”缺失的数据”变成了”有偏的数据”。

把抓取层搞对,剩下的就是工程。如果你的来源有防御或随地理变化,一个住宅代理网络就是把便利样本变成代表性样本的东西——对难目标的完整覆盖、刻意的多地理采样、以及规模上的均匀轮换。等你准备接线时,Python 指南展示了抓取阶段的代码,定价页有按 GB 的计划。先把覆盖建好,数据集自会照顾好自己。

标签: web scraping datasets data collection residential proxies data engineering

准备好开始了吗?

试用 Shifter 住宅代理,205M+ 个 IP,195+ 个国家,低至 $1.00/GB。

立即开始