机器学习模型的好坏取决于其背后的数据,而大多数值得学习的数据都存在于公开网络上,而非别人递给你的整洁 CSV 文件中。产品列表、价格、评论、招聘信息、新闻和社交媒体讨论都在持续大量地产生,这正是模型所需要的那种新鲜、真实的信号。本指南将展示如何将网络爬取用于机器学习:为什么网络数据能驱动 ML,如何可靠地大规模收集数据,如何标注和结构化数据,以及如何将其送入训练管道,每个步骤都配有可运行的 Python 代码。

本演练仅限于公开数据,即任何人无需登录即可查看的页面。Python 通过 Crawlbase Crawling API 收集 HTML,将其转换为 pandas dataframe,清洗和标注数据行,并完成基本的特征预处理,使结果可以直接输入模型。目标是建立一个可以按计划重复运行的数据收集步骤,因为数据集过时就意味着模型会悄悄变差。

为什么网络数据能驱动机器学习

监督模型从样本中学习,而网络是现存最大的样本来源。它对 ML 尤其有价值,原因有三点。它多样:爬取多个网站能给模型提供泛化所需的多样性,而不是记住某个来源的特有规律。它新鲜:重新运行收集器能让你的训练集与当前世界的真实状态保持一致,这在价格、需求和情感等快速变化的领域尤为重要。它丰富:手动标注的数据集可能只有几千行,而爬虫可以汇集数十万条公开记录。

问题在于可靠性。一个今天在你笔记本上能跑的一次性脚本,不是一个可以在此基础上构建模型的数据源。网站在客户端渲染内容,轮换其标记,并封锁自动化流量,因此在下游任何事情有意义之前,收集层必须足够健壮。这正是 ML 项目在实践中失败的环节,也是本指南大部分篇幅所在。

爬取数据在 ML 管道中的位置

爬取的网络数据会出现在项目的多个节点,明确你要解决的是哪个节点很有帮助。

  • 训练数据。爬取的行直接成为模型学习的数据集,无论是监督学习、无监督学习还是半监督学习。
  • 特征工程。你提取的字段(文本长度、情感、价格变化、类别计数)成为输入特征,提升用其他数据训练的模型的预测能力。
  • 数据增强。当手动标注的集合太小时,爬取的记录可以扩大其规模和多样性,让模型看到更多的样本空间。
  • 评估。从训练集中保留的一份新鲜爬取切片是一个真实的测试集,可用于检验模型在当前真实数据上的表现。

本指南其余部分构建一个小型但完整的从收集到特征的管道,你可以将其适配到上述任何用途。关于数据准备好之后训练本身如何运作,AI 模型训练详解是一篇很好的参考文章。

为什么规模化时普通请求不够用

requests 收集一个页面很容易。可靠地从有反爬机制的网站收集十万个页面,才是大多数自制收集器会失败的地方。两个问题会很快出现。第一,许多页面在浏览器中用 JavaScript 渲染内容,所以你获取到的原始 HTML 是一个空壳。第二,商业网站会迅速标记自动化流量:数据中心 IP 和机械式的请求模式,远在你有足够数据可以训练之前就会被封锁。

你可以自己用无头浏览器加上轮换住宅代理池来解决这两个问题,但保持那个集群健康运行是大部分工程工作所在。Crawling API 将两者合并为一次调用:你发送一个 URL,它在可信 IP 后面渲染页面,在服务端轮换地址,并返回完整的 HTML。如果目标提供干净的静态标记,你只想要解析后的字段,Crawling API 可以直接返回结构化 JSON;如果你想要自己控制轮换的底层传输,Smart AI Proxy 是更低层的选项。本指南使用 Crawling API,因为数据集收集通常跨越有防御的混合网站。

普通令牌与 JavaScript 令牌

Crawlbase 提供两种令牌类型。普通令牌获取静态 HTML;JavaScript(JS)令牌会先在真实浏览器中渲染页面。如果你的目标是客户端渲染的,使用 JS 令牌,否则普通令牌更快更经济。按数据源选择,不要为整个任务统一使用一种。

搭建项目

你需要安装 Python 3 和 pip。确认两者都已安装,然后创建项目并安装管道所用的库。

bash
python --version
pip --version

mkdir ml-dataset && cd ml-dataset
python -m venv .venv && source .venv/bin/activate
pip install crawlbase beautifulsoup4 pandas scikit-learn

四个依赖各司其职:crawlbase 是 Crawling API 的客户端,beautifulsoup4 解析返回的 HTML,pandas 将数据集保存为 dataframe,scikit-learn 在最后处理特征预处理。你还需要一个 Crawlbase 账号和令牌,注册后从控制台获取。将其存放在环境变量中,而不是硬编码进代码。

第一步:通过 Crawling API 收集页面

从收集层开始,因为下游的一切都依赖于它返回干净的 HTML。Python 客户端将 API 封装为一次 get 调用。对于有防御机制的客户端渲染网站,两个选项很重要:ajax_wait 告诉 API 等待异步内容,page_wait 在加载后固定等待若干毫秒,使延迟渲染的内容得以出现。收集器会检查状态码,确保被封锁的页面不会默默变成数据集中的空白行。

python
import os
import time
from crawlbase import CrawlingAPI

# JS token renders the page in a real browser before returning HTML
api = CrawlingAPI({"token": os.environ["CRAWLBASE_JS_TOKEN"]})

options = {
    "ajax_wait": "true",
    "page_wait": 5000,
}

def fetch_html(url):
    response = api.get(url, options)
    if response["status_code"] != 200:
        raise RuntimeError(f"fetch failed: {response['status_code']}")
    return response["body"].decode("utf-8")

def collect(urls):
    pages = []
    for url in urls:
        try:
            pages.append({"url": url, "html": fetch_html(url)})
        except RuntimeError as err:
            print(f"skipping {url}: {err}")
        time.sleep(1)  # pace requests so you stay unblocked
    return pages

Crawling API 为你轮换 IP 并渲染页面,因此收集器保持简洁。请求之间的 time.sleep 是有意为之的:合理节奏能使长时间运行保持健康。对于任何具有真实规模的数据集,你需要数千个 URL、重试逻辑和并发,这是一个独立话题,在大规模网络爬取中有详细介绍。

Crawlbase Crawling API

构建 ML 数据集意味着要在有防御机制的网站上进行数千次页面获取。Crawling API 接收令牌,在真实浏览器中渲染页面,在服务端轮换住宅 IP,并返回完整的 HTML,让你无需自己运行无头浏览器集群和代理池。先在免费套餐上指向一个公开数据源来测试。

第二步:将页面解析为结构化记录

原始 HTML 不是数据集。下一步将每个页面转化为包含你想要学习的字段的平坦记录。这个示例将产品列表页作为数据源,提取名称、价格、评分和评论文本,但这个结构适用于任何场景:选取字段,将每个字段映射到选择器,返回一个字典。一个小工具函数将缺失元素处理为空字符串而不是报错崩溃。

python
from bs4 import BeautifulSoup

def text_or_empty(node, selector):
    el = node.select_one(selector)
    return el.get_text(strip=True) if el else ""

def parse_products(page):
    soup = BeautifulSoup(page["html"], "html.parser")
    rows = []
    for card in soup.select(".product-card"):
        rows.append({
            "name": text_or_empty(card, ".title"),
            "price": text_or_empty(card, ".price"),
            "rating": text_or_empty(card, ".rating"),
            "review": text_or_empty(card, ".review-snippet"),
            "source": page["url"],
        })
    return rows

将上面的选择器视为起始模板而非固定约定:类名和数据属性会在没有任何提示的情况下改变,所以当提取返回空字段时,在浏览器开发工具中重新检查实时页面并更新选择器。这是任何生产爬虫的正常维护工作。

第三步:构建 pandas dataframe

有了记录列表,pandas 只需一行就能给你一个 dataframe 和此后所有操作的工具包。收集、解析并加载所有行,然后在信任它之前先检查一下。去重和 dropna 步骤的重要性不亚于表面看起来的:充满重复或半空行的数据集会让模型学到错误的东西。

python
import pandas as pd

urls = [
    "https://www.example.com/category/page/1",
    "https://www.example.com/category/page/2",
]

records = []
for page in collect(urls):
    records.extend(parse_products(page))

df = pd.DataFrame(records)
df = df.drop_duplicates(subset=["name", "source"])
df = df.dropna(subset=["name"])

print(df.shape)
print(df.head())
df.to_csv("dataset_raw.csv", index=False)

在这个阶段写入 dataset_raw.csv 可以作为一个检查点:收集速度慢且受速率限制,所以你绝对不想因为后续清洗步骤有 bug 就重新爬取。将 CSV 加载用于管道的其余部分,将收集器作为一个独立的、偶尔运行的任务保留。

第四步:清洗和标注数据行

爬取的字段以杂乱的字符串形式到达:价格是 "$118",评分是 "4.5 out of 5",评论是自由文本。模型需要数字和目标列,因此这一步规范化原始字段并推导标签。这里的标签是从评分派生的简单情感代理,将一个未标注的爬取结果转化为监督分类数据集。

python
import re
import pandas as pd

df = pd.read_csv("dataset_raw.csv")

def to_float(value):
    match = re.search(r"(\d+(?:\.\d+)?)", str(value))
    return float(match.group(1)) if match else None

df["price"] = df["price"].apply(to_float)
df["rating"] = df["rating"].apply(to_float)
df["review"] = df["review"].fillna("").str.strip()

# derive a supervised label from the rating
df = df.dropna(subset=["rating"])
df["label"] = (df["rating"] >= 4.0).astype(int)

print(df["label"].value_counts())
df.to_csv("dataset_clean.csv", index=False)

检查标签的 value_counts 不是可选的。爬取的数据很少是平衡的,而目标类别占比 95% 的数据集会产生一个看起来准确但什么都没学到的模型。如果分布倾斜,在训练之前通过重采样或加权类别来重新平衡。关于为 ML 规范化爬取字段的深入处理,请参阅为 AI 和 ML 结构化和清洗网络爬取数据

第五步:为模型准备特征

最后一步将干净的 dataframe 转化为模型训练所需的数值矩阵。文本需要向量化,数值列从归一化中受益,因此 scikit-learn 的 ColumnTransformer 在一次遍历中对每一列应用正确的转换。输出是特征矩阵 X 和标签向量 y,已分割为训练集和测试集,可以直接交给任何估计器。

python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import StandardScaler

df = pd.read_csv("dataset_clean.csv").fillna({"review": ""})

features = df[["review", "price"]]
y = df["label"]

pre = ColumnTransformer([
    ("text", TfidfVectorizer(max_features=5000), "review"),
    ("num", StandardScaler(), ["price"]),
])

X = pre.fit_transform(features)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

print(f"train: {X_train.shape}, test: {X_test.shape}")

从这里开始,X_trainy_train 可以直接传入任何 scikit-learn 估计器的 fit 方法,保留的测试集则给你一个诚实的准确率读数。stratify=y 参数在分割中保持类别平衡的一致性,在标签倾斜时尤为重要。从收集到特征的这条链是可复用的部分:换掉选择器和标注规则,同样的五个步骤就能为不同的问题构建数据集。

保持收集层健康

一个可以按需重建的数据集远比一次性导出有价值,因此收集器需要在目标改变时持续运行。以下几个习惯能让长期运行保持健康。

  • 控制节奏并轮换。分散请求并通过轮换住宅 IP 路由,使任何单一地址都不会触发速率限制。Crawling API 为你处理轮换;如果你构建自己的技术栈,这是最需要做对的部分。
  • 关注状态码。当运行开始返回挑战时,说明当前速率或 IP 层级不再够用。将其视为信号并退缩,而不是持续重试直至被封锁。
  • 检查点保存原始 HTML。在解析之前保存你获取的内容,这样解析器的 bug 就不会让你付出重新爬取的代价。

完整的策略手册请参阅如何在不被封锁的情况下爬取网站。一旦数据集存在,AI 数据提取及其工作原理涵盖了如何更自动化地将杂乱页面转化为结构化字段。

诚实的部分:伦理与合法性

构建 ML 数据集与任何爬取行为负有同样的责任,是否被允许取决于每个网站的服务条款、你所在的司法管辖区以及你对数据的用途。仅收集公开数据,遵守每个网站的 robots.txt 和声明的速率期望,并将请求量保持在不会给任何人的服务器造成压力的范围内。

有两点对 ML 尤为重要。永远不要收集个人数据或任何与可识别个人相关联的内容,并注意推导的标签或特征不会重新构建这些信息。还要记住,模型会继承训练数据的偏见:从一个地区、语言或平台爬取的数据集会产生一个在其他地方泛化能力差的模型。对于商业再利用,请获得许可或官方数据协议,而不是将沉默视为默许。

回顾

核心要点

  • 网络数据多样、新鲜且丰富。这三个属性正是模型泛化所需要的,这也是为什么爬取能驱动如此多 ML 数据集的原因。
  • 可靠性是难点,而不是解析。渲染客户端页面,轮换 IP,并控制请求节奏,否则收集器在你有足够行数之前就会失败。
  • Crawling API 将渲染和轮换合并为一次调用。客户端页面使用 JS 令牌,静态页面使用普通令牌,按数据源选择。
  • 在训练之前清洗和标注。将杂乱字符串规范化为数字,推导目标列,并始终检查类别平衡。
  • 特征预处理使数据集可供模型使用。用单个 scikit-learn 转换器向量化文本、归一化数值,并分割为训练集和测试集。
  • 坚守公开数据。遵守服务条款和 robots.txt,避免个人数据,并注意训练集中固化的偏见。

常见问题

网络爬取在机器学习中有应用吗?

是的,应用广泛。能够从多个来源收集大量公开数据,让你可以构建比单独手动标注数据更大、更多样的训练集,这正是帮助模型泛化的关键。爬取还能保持数据集的新鲜度,让价格或情感等快速变化领域的模型与当前状况保持一致,而不是从过时的快照中学习。

如何大规模收集网络数据来构建机器学习数据集?

瓶颈在于可靠性,而不是解析。许多页面是客户端渲染的,大多数商业网站会封锁自动化流量,因此你需要渲染能力和可信的轮换 IP 池,才能在不被切断的情况下获取数千个页面。Crawling API 通过一次调用处理两者:发送一个 URL,获取完整的 HTML,然后将其解析为记录。控制请求节奏,检查点保存原始 HTML,并将收集器作为独立任务与管道其余部分分开。

我需要普通令牌还是 JavaScript 令牌?

取决于数据源。普通令牌获取静态 HTML,更快也更经济,因此在页面已经包含你想要的数据时使用它。JavaScript 令牌先在真实浏览器中渲染页面,这是客户端渲染网站所必需的,对于这类网站,普通获取只会返回空壳。按数据源选择,而不是为整个任务统一使用一种。

如何将爬取的页面转化为已标注的数据集?

将每个页面解析为平坦记录,加载到 pandas dataframe 中,然后清洗和标注。将杂乱字段规范化为数字(去除货币符号,提取评分),删除重复行和空行,并从你信任的字段推导目标列,例如将高评分映射为正标签。在训练之前始终检查类别平衡,因为爬取的数据很少是平衡的。

如何将爬取的数据准备为模型的特征?

将每一列转换为模型可以读取的数值形式。用 TF-IDF 等方法向量化文本字段,归一化数值列使任何单一特征不占主导地位,并用 scikit-learn ColumnTransformer 在一次遍历中应用两者。将结果分割为训练集和测试集,在标签上进行分层以保持类别平衡,特征矩阵就可以拟合任何估计器了。

为机器学习爬取数据合法吗?

取决于每个网站的服务条款、你所在的司法管辖区以及你的用途。严格坚守公开数据,遵守 robots.txt 和速率期望,永远不要收集个人数据或任何与可识别个人相关联的内容。注意模型会继承训练集的偏见。对于商业再利用,请获得许可或官方数据协议,而不是依赖爬虫。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量