第一个爬虫
在这节课中,你将构建你的第一个网络爬虫。但在开始之前,让我们简要介绍一下涉及到该过程的Crawlee类。
Crawlee的工作原理
Crawlee中有3个主要的爬虫类可供使用。 CheerioCrawler
,PuppeteerCrawler
和 PlaywrightCrawler
。我们稍后会讨论它们之间的区别。现在,让我们谈谈它们共同拥有的特点。
每个网络爬虫的基本思想是访问一个网页,打开它,在页面上执行一些操作,保存一些结果,然后继续到下一个页面,并重复这个过程直到爬虫完成其工作。因此,网络爬虫总是需要解决两个问题:我应该去哪里?和我应该在那里做什么? 解决这两个问题,我们需要提供必要的设置。至于其他,爬虫都有适合的默认值。
爬什么 - Request
and RequestQueue
所有的网络爬虫都使用Request
类的实例来确定它们需要向哪里发出请求。每个请求可能包含大量信息,但至少必须包含一个URL —— 一个要打开的网页。但是只有一个URL对于爬行来说是意义不大。有时你可能拥有自己想访问的预先存在的URL列表,也许有一千个。甚至有时候,你需要在爬行过程中动态构建这个列表,并不断向列表添加更多URL。大多数情况下,你会同时出现这两种情况。
所有的请求存储在RequestQueue
中,这是一个包含了Request
实例的动态队列。你可以用起始URL来初始化,并且在爬虫运行时不断添加更多的请求。这就让爬虫先打开一个页面,然后不断将你感兴趣的URL,例如同一域名下的其他页面链接,添加到队列(称为 enqueuing ) ,并重复此过程来构建几乎无限数量的URL队列。
做什么 - requestHandler
在requestHandler
中,你告诉爬虫在访问每个页面时该做什么。你可以使用它从页面提取数据、处理数据、保存数据、调用API、进行计算等操作。
requestHandler
是一个用户定义的函数,由爬虫自动调用每个来自队列RequestQueue
的Request
。它总是返回一个参数 —— 一个CrawlingContext
。其属性根据使用的爬虫类而变化,但它始终包括 request
属性,该属性代表当前被抓取的URL和相关元数据。
构建一个网络爬虫
让我们把理论付诸实践,从一些简单的例子开始。访问一个页面并获取其HTML标题。在本教程中,你将爬取Crawlee网站https://crawlee.dev,但代码也同样适用于任何网站。
我们在示例中使用了一个名为Top-level await的JavaScript功能。要能够使用它,你可能需要一些额外的设置。换句话说,它需要使用ECMAScript模块,这意味着你需要将"type": "module"
添加到你的package.json
文件中,或者为你的文件使用*.mjs
扩展名。此外,如果你在一个TypeScript项目中,则需要将编译器选项设置为 ES2022
或更高版本。
将请求添加到爬取队列
之前学到爬虫要使用一个请求队列作为其要抓取的URL来源。让我们来创建一个队列,并添加第一个请求。
import { RequestQueue } from 'crawlee';
// 首先,你需要创建请求队列实例。
const requestQueue = await RequestQueue.open();
// 然后你可以添加一个或多个请求。
await requestQueue.addRequest({ url: 'https://crawlee.dev' });
函数 requestQueue.addRequest()
会自动将带有 url 字符串的对象转换为 Request
实例。 现在你有一个 requestQueue
队列,包含一个指向 https://crawlee.dev
的请求。
以上的代码是为了说明请求队列概念。很快你将会学习到crawler.addRequests()
方法,它允许你跳过这个初始化代码,并且支持添加批量的请求。
构建一个 CheerioCrawler
Crawlee有三个主要的爬虫类:CheerioCrawler
,PuppeteerCrawler
和 PlaywrightCrawler
。你可以在快速开始课程中阅读它们的简单介绍。
除非你有充分的理由选择其他方式开始,否则你应该首先尝试构建一个CheerioCrawler
。它是一个带有HTTP2支持、防屏蔽功能和集成HTML解析器 —— Cheerio 的HTTP爬虫。它快速、简单、运行成本低,并且不需要复杂的依赖关系。唯一的缺点是对于需要JavaScript渲染的网站无法直接使用。但实际上,你可能根本不需要JavaScript渲染,因为许多现代网站都采用服务器端渲染。
让我们继续之前的 RequestQueue
示例。
// 引入 CheerioCrawler
import { RequestQueue, CheerioCrawler } from 'crawlee';
const requestQueue = await RequestQueue.open();
await requestQueue.addRequest({ url: 'https://crawlee.dev' });
// 创建爬虫,使用我们的URL队列
// 用一个requestHandler来处理页面
const crawler = new CheerioCrawler({
requestQueue,
// `$` 参数是 Cheerio 对象
// 并包含了网站上要解析的HTML
async requestHandler({ $, request }) {
// 使用Cheerio提取<title>文本。
// 请查看 Cheerio 文档以获取 API 文档。
const title = $('title').text();
console.log(`The title of "${request.url}" is: ${title}.`);
}
})
// 启动爬虫并等待其完成。
await crawler.run();
当你运行示例时,你将在日志中看到 https://crawlee.dev 的标题打印出来。实际发生的是 CheerioCrawler 首先向 https://crawlee.dev
发送 HTTP 请求,然后使用 Cheerio 解析接收到的HTML,并将其作为 requestHandler
的 $
参数可用。
The title of "https://crawlee.dev" is: Crawlee · The scalable web crawling, scraping and automation library for JavaScript/Node.js | Crawlee.
更快地添加请求
早些时候我们提到过会使用crawler.addRequests()
方法来跳过请求队列的初始化。很简单。每个爬虫都有一个隐式的RequestQueue
实例,你可以使用crawler.addRequests()
方法向其添加请求。事实上,你甚至可以更加简化,只需使用crawler.run()
的第一个参数!
// 你不再需要导入RequestQueue
import { CheerioCrawler } from 'crawlee';
const crawler = new CheerioCrawler({
async requestHandler({ $, request }) {
const title = $('title').text();
console.log(`The title of "${request.url}" is: ${title}.`);
}
})
// 提供URL直接启动爬虫
await crawler.run(['https://crawlee.dev']);
当你运行此代码时,你将看到与之前的示例完全相同的输出。其实RequestQueue
仍然存在,只是由爬虫自动管理。
下一节
在下一课中你将学习如何爬取更多链接。这意味着在你爬取的页面上找到新的URL,并将它们不断添加到 RequestQueue
中,供爬虫访问。