网页抓取一开始可能会让人觉得有些吓人,但和任何技能一样,最好的学习方式就是亲自动手实践。在前三章中,我们将探索如何从静态 HTML 页面中提取数据的基础方法。
第 1 章:HTML 抓取菜鸟 Chad
我们的旅程将从最简单的场景开始——从一个基础 HTML 页面中提取文本。本章会向你介绍以下核心概念:
- 发起 HTTP 请求以获取网页
- 将 HTML 内容加载到解析器中
- 使用 CSS 选择器进行基础 DOM 选择
虽然这个例子看起来可能很简单(毕竟只是一个 “hello world”!),但它为后续所有内容打下了基础。
<!-- Example HTML structure -->
<div class="content">
<p>Some text we want to extract</p>
</div>
下面先看一眼我们要处理的内容:
// Basic concepts (not the solution!)
import * as cheerio from 'cheerio';
// Loading HTML content
const $ = cheerio.load(htmlContent);
// Using CSS selectors
const text = $('p').text(); // Selects all <p> tags
const specific = $('.content p').text(); // More specific selection
第 2 章:结构化数据
当我们深入到一个模拟电商商品页面时,事情就变得更有意思了。在这里,我们会遇到多个结构相似的元素:
<!-- Example product structure (simplified) -->
<div class="product">
<h2>Product Name</h2>
<span class="price">$99.99</span>
<div class="specs">
<ul>
<li>Size: M</li>
<li>Color: Blue</li>
</ul>
</div>
</div>
在处理像这样结构化的数据时,你需要思考:
// Conceptual approach (not the solution!)
$('.product').each((index, element) => {
// For each product, we might want to:
// 1. Extract the basic info
const name = $(element).find('h2').text();
// 2. Parse nested data
const specs = $(element).find('.specs li');
// 3. Structure the output
const data = {
name,
specs: specs.map(/* ... */),
};
});
小提示:在写任何代码之前,先花点时间分析 HTML 结构。观察数据的组织方式是否有规律——是否存在一致的 class 名?父元素和子元素之间是怎样关联的?
第 3 章:AI 辅助抓取
现在真正有意思的部分来了!前面的挑战教给我们的是传统抓取技术,而第 3 章将引入一种现代方法:AI 辅助网页抓取。我们面对的是一个堪称噩梦的场景——不一致的 HTML 结构、混淆后的 class 名,以及多种框架模式杂糅在一起。
先来看看到底是什么让这个挑战如此特别:
<!-- Traditional product structure -->
<div data-testid="product-container-1" class="_3xj_item">
<h2 data-qa="name">Red Sneakers</h2>
<span data-price-current="5999">$59.99</span>
</div>
<!-- React-style component -->
<div class="ProductCard-root-1a2b3c">
<div class="ProductCard-title-4d5e6f">Pink Walking Shoes</div>
<div class="ProductCard-pricing-7g8h9i">$84.99</div>
</div>
<!-- Vue-style template -->
<div data-v-abcdef class="product">
<h2 data-v-abcdef>Navy Boat Shoes</h2>
<span data-v-abcdef>$79.99</span>
</div>
传统抓取方法在这里会举步维艰,因为:
- class 名是随机化的,或者带有框架特定特征
- 不同商品之间的数据结构各不相同
- 不同框架使用不同的模式
- 标记中的语义信息已经丢失
这正是 AI 大显身手的时候。与其编写脆弱的选择器,不如用自然语言描述我们想要的内容,让 AI 来完成模式匹配。本章的关键概念包括:
- 面向网页抓取的 Prompt 工程
- 使用 AI 理解语义含义
- 处理不一致的数据结构
- 应对框架特定的标记结构
- 在不同模式之间保持数据一致性
AI 并不是魔法,但它非常擅长需要模式识别和自适应能力的任务。这让它在抓取现代 Web 应用时尤其有价值,因为这类应用往往无法保证标记模式始终一致。
关于使用 AI 的说明
请记住,AI 辅助并不意味着可以获得完全自动化的解决方案。最好的结果来自以下要素的结合:
- 清晰的问题定义
- 结构良好的提示词
- 数据校验
- 人工监督
你的挑战在于设计出合适的提示词,让 AI 同时理解你想提取内容的结构以及背后的意图。
准备好把传统网页抓取知识与现代 AI 能力结合起来了吗?让我们看看 AI 如何帮助我们拿下哪怕最混乱的 HTML!
提示
- 试试不同的 CSS 选择器:
// Different ways to select elements
$('.class') // By class
$('#id') // By ID
$('div > p') // Direct children
$('div p') // All descendants
$('[data-type="x"]') // By attribute
- 尝试修改输出格式
- 思考错误处理和边界情况
- 考虑你的方案在更大数据集上的扩展性
开始所需的全部代码都在项目仓库中。把它克隆下来,配置好环境,然后开始抓取吧!
git clone https://github.com/jonaylor89/housefly.git
cd housefly
npm install
在本地启动各章节服务器——每一章都运行在各自独立的端口上(3001–3011):
turbo dev
然后在 exercises/chapter-NN/starter/src/index.ts 中编辑你的抓取器,并进行验证:
npm run validate -- <chapter>
想看提示?运行 npm run hint -- <chapter> 获取逐步引导。每个挑战的源 HTML 都位于 apps/chapter{n}/ 目录中,参考解答则在 exercises/chapter-NN/solution/src/ 中。研究结构,规划方法,并记住——每一位专家都曾经是初学者。
记得以负责任的方式处理你的请求:
// Basic error handling example
async function fetchPage(url: string) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.text();
} catch (error) {
console.error('Failed to fetch page:', error);
throw error;
}
}
祝你抓取愉快!