apify是一个基于NodeJS的爬虫框架,它集成了puppeteer,cheerio等常应用于爬虫服务的NodeJS库,致力于填补复杂场景下的web页面爬虫服务上的功能空白,如通用的爬虫任务入口、爬虫任务的错误捕获和重试、爬虫任务队列/列表、爬虫内部状态监控以及代理池等。
提供范式化的爬虫api。
开发者可以专注于目标爬取逻辑的编写,同时通过设置handleFailedRequestFunction来编写爬虫任务失败的处理逻辑。
const Apify = require('apify');
Apify.main(async () => {
// 初始化爬虫列表
const requestList = new Apify.RequestList({
sources: [
{ url: 'http://www.google.com/' },
{ url: 'http://www.example.com/' },
{ url: 'http://www.bing.com/' },
{ url: 'http://www.wikipedia.com/' },
],
});
await requestList.initialize();
// 初始化爬虫
const crawler = new Apify.BasicCrawler({
// 并发爬取限制
minConcurrency: 10,
maxConcurrency: 50,
// 单个任务重试次数
maxRequestRetries: 1,
// Increase the timeout for processing of each page.
handlePageTimeoutSecs: 60,
// 爬取任务列表
requestList,
// 页面处理函数
handleRequestFunction: async ({ request, response }) => {
// 目标页面爬取逻辑
},
handleFailedRequestFunction: async ({ request }) => {
// 重试n次之后的爬取错误处理逻辑
},
});
// 启动爬虫
await crawler.run();
});
基于puppeteer和cheerio的扩展爬虫
CheerioCrawler和PuppeteerCrawler都于集成于BasicCrawler,分别提供了两种库特有页面html处理功能。
const crawler = new Apify.CheerioCrawler({
// 爬虫列表
requestList,
// - html: 页面html字符串
// - $: 包含目标html的cheerio对象
handlePageFunction: async ({ request, html, $ }) => {
console.log(`Processing ${request.url}...`);
const title = $('title').text();
const h1texts = [];
$('h1').each((index, el) => {
h1texts.push({
text: $(el).text(),
});
});
},
});
在PuppeteerCrawler爬虫中,apify封装了puppeteer创建销毁browser和page的过程,开发者只需要专注于目标页面抓取逻辑的代码编写。
const crawler = new Apify.PuppeteerCrawler({
// 爬虫任务对列
requestQueue,
// puppeteer浏览器启动参数配置
launchPuppeteerOptions: {
},
handlePageFunction: async ({ request, page }) => {
// page来自于puppeteer context创建,可以使用所有puppeteer相关的api
const pageFunction = $posts => {
const data = []
$posts.forEach($post => {
data.push({
title: $post.querySelector('.title a').innerText,
rank: $post.querySelector('.rank').innerText,
href: $post.querySelector('.title a').href,
});
});
return data;
};
const data = await page.$$eval('.athing', pageFunction);
},
});
爬虫内部状态监控
apify内部实现了SystemStatus类,以定时轮询的方式定时查询爬虫服务的内部状态信息,如内存、cpu、eventloop等。当爬虫设置了并发数配置(minConcurrency和maxConcurrency),apify将通过当前SystemStatus按一定比例对爬取并发数进行动态缩放。当前系统状态不足于满足当前爬虫任务的执行时,apify会暂定来自于requestList或requestQueue的爬虫任务,直到SystemStatus恢复正常。通过设置autoscaledPoolOptions参数可以动态管理爬虫任务的生命周期。
{
runTaskFunction: async () => {
},
isTaskReadyFunction: async () => {
},
isFinishedFunction: async () => {
},
}
通过AutoscaledPool实例动态控制任务队列/列表的执行。
handlePageFunction: async ({ autoscaledPool }) => {
autoscaledPool
.run() ⇒ Promise // 启动
.abort() ⇒ Promise // 停止
.pause([timeoutSecs]) ⇒ Promise // 暂停
.resume() // 重启
}
内置代理池
在爬虫服务中,为了规避目标网站的反爬机制,添加代理以隐藏浏览器来源,往往需要通过编写复杂的逻辑给请求设置不同的代理IP。apify内置了代理池功能,通过proxyUrls参数传入ip地址的数组。apify内部将自动维护一个代理池,随机分配代理给每一个爬虫任务。
本地数据存储
apify基于fs模块实现了DataSet和KeyValueStore两种数据结构用于数据的本地存储。
DataSet
// 写入一个行数据到默认的本地存储
await Apify.pushData({ col1: 123, col2: 'val2' });
// 打开一个DateSet
const dataset = await Apify.openDataset('some-name');
// 写入多行数据
await dataset.pushData([{ foo: 'bar2', col2: 'val2' }, { col3: 123 }]);
KeyValueStore
// 写入一个键值对到默认的keyValueStore.
await Apify.setValue('OUTPUT', { myResult: 123 });
// 获取
const store = await Apify.openKeyValueStore('some-name');
// Write a record. JavaScript object is automatically converted to JSON,
// strings and binary buffers are stored as they are
await store.setValue('some-key', { foo: 'bar' });
// Read a record. Note that JSON is automatically parsed to a JavaScript object,
// text data returned as a string and other data is returned as binary buffer
const value = await store.getValue('some-key');
// Drop (delete) the store
await store.drop();
丰富的工具函数
Apify.utils.log (日志分级)
// 支持日志分级
log.setLevel(log.LEVELS.ERROR);
log.debug('Debug message');
log.info('Info message');
log.error('Error message');
Apify.utils.puppeteer
puppeteer常用的工具函数:
Apify.utils.puppeteer
.addInterceptRequestHandler ⇒ Promise // 页面请求拦截处理
.gotoExtended ⇒ Promise<Response> // page跳转前置处理
.infiniteScroll ⇒ Promise // 页面滚动
.saveSnapshot ⇒ Promise // 页面快照
.injectFile(page, filePath, [options]) ⇒ Promise // 页面植入脚本
.injectJQuery(page) ⇒ Promise // 页面植入jquery
.injectUnderscore(page) ⇒ Promise // 页面植入undeerscore
.blockRequests(page, [options]) ⇒ Promise // 顾虑页面请求
.cacheResponses(page, cache, responseUrlRules) ⇒ Promise // 缓存页面响应
.compileScript(scriptString, context) ⇒ function // 在page上下文执行额外脚本
apify解决了NodeJS爬虫服务中的很多痛点,使用apify编写爬虫程序,能很大程度上的减少代码量,开发者能够专注于编写目标页面的爬取逻辑。除了以上介绍的优点外,apify仍然存在一些不足,如不支持动态requestQueue,手动切换代理。不过目前所支持的功能,已经可以满足大部分业务常景了。