Перейти к основному содержимому

Обновление до версии 1

Краткий обзор

После 3.5 лет активной разработки, множества критических изменений и устареваний, мы представляем Apify SDK v1. У этого релиза было две основные цели: стабильность и поддержка дополнительных браузеров - Firefox и Webkit (Safari).

SDK стал довольно популярным за эти годы, обеспечивая работу тысяч проектов по веб-скрапингу и автоматизации. Мы считаем, что наши разработчики заслуживают стабильной среды для работы, и с выпуском SDK v1 мы обязуемся вносить критические изменения только раз в год с новым мажорным релизом.

Мы добавили поддержку дополнительных браузеров, заменив PuppeteerPool на browser-pool - новую библиотеку, созданную специально для этой цели. Она основана на идеях PuppeteerPool и расширяет их для поддержки Playwright. Playwright - это библиотека автоматизации браузера, похожая на Puppeteer. Она работает со всеми популярными браузерами и использует практически тот же интерфейс, что и Puppeteer, добавляя полезные функции и упрощая общие задачи. Не беспокойтесь, вы по-прежнему можете использовать Puppeteer с новым BrowserPool.

Важное изменение заключается в том, что ни puppeteer, ни playwright больше не поставляются в комплекте с SDK v1. Чтобы упростить выбор библиотеки и ускорить установку, пользователям нужно будет самостоятельно установить выбранные модули и версии. Это позволит нам добавлять поддержку еще большего количества библиотек в будущем.

Thanks to the addition of Playwright we now have a PlaywrightCrawler. It is very similar to PuppeteerCrawler and you can pick the one you prefer. It also means we needed to make some interface changes. The launchPuppeteerFunction option of PuppeteerCrawler is gone and launchPuppeteerOptions were replaced by launchContext. We also moved things around in the handlePageFunction arguments. See the migration guide for more detailed explanation and migration examples.

What's in store for SDK v2? We want to split the SDK into smaller libraries, so that everyone can install only the things they need. We plan a TypeScript migration to make crawler development faster and safer. Finally, we will take a good look at the interface of the whole SDK and update it to improve the developer experience. Bug fixes and scraping features will of course keep landing in versions 1.X as well.

Migration Guide

There are a lot of breaking changes in the v1.0.0 release, but we're confident that updating your code will be a matter of minutes. Below, you'll find examples how to do it and also short tutorials how to use many of the new features.

Many of the new features are made with power users in mind, so don't worry if something looks complicated. You don't need to use it.

Установка

В предыдущих версиях SDK пакет puppeteer поставлялся в комплекте, поэтому его не нужно было устанавливать отдельно. SDK v1 также поддерживает playwright, и мы не хотим заставлять пользователей устанавливать оба пакета. Для установки SDK v1 с Puppeteer (как в предыдущих версиях) выполните:

npm install apify puppeteer

Для установки SDK v1 с Playwright выполните:

npm install apify playwright

Хотя мы постарались добавить самый важный функционал в начальный релиз, вы можете обнаружить, что некоторые утилиты или опции пока поддерживаются только Puppeteer, но не Playwright.

Запуск на платформе Apify

Если вы хотите использовать Playwright на платформе Apify, вам необходимо использовать Docker-образ с поддержкой Playwright. Мы создали такие образы специально для вас - ознакомьтесь с новым руководством по Docker-образам и выберите тот, который лучше всего соответствует вашим потребностям.

Обратите внимание, что ваш package.json ДОЛЖЕН включать puppeteer и/или playwright как зависимости. Если вы не укажете их в списке, эти библиотеки будут удалены из папки node_modules при сборке ваших акторов.

Аргументы обработчиков теперь являются контекстом сканирования

Ранее аргументы пользовательских функций-обработчиков предоставлялись в отдельных объектах. Это затрудняло отслеживание значений между вызовами функций.

const handlePageFunction = async (args1) => {
args1.hasOwnProperty('proxyInfo') // true
}

const handleFailedRequestFunction = async (args2) => {
args2.hasOwnProperty('proxyInfo') // false
}

args1 === args2 // false

Это происходило потому, что для каждой функции создавался новый объект аргументов. В SDK v1 теперь есть единый объект, называемый контекстом сканирования (Crawling Context).

const handlePageFunction = async (crawlingContext1) => {
crawlingContext1.hasOwnProperty('proxyInfo') // true
}

const handleFailedRequestFunction = async (crawlingContext2) => {
crawlingContext2.hasOwnProperty('proxyInfo') // true
}

// Все контексты - один и тот же объект
crawlingContext1 === crawlingContext2 // true

Map контекстов сканирования и их идентификаторов

Теперь, когда все объекты одинаковы, мы можем отслеживать все активные контексты сканирования. Мы можем делать это, работая с новым свойством id объекта crawlingContext. Это полезно, когда требуется доступ между контекстами.

let masterContextId;
const handlePageFunction = async ({ id, page, request, crawler }) => {
if (request.userData.masterPage) {
masterContextId = id;
// Подготовка главной страницы
} else {
const masterContext = crawler.crawlingContexts.get(masterContextId);
const masterPage = masterContext.page;
const masterRequest = masterContext.request;
// Теперь мы можем управлять данными главной страницы из другого handlePageFunction
}
}

Компонент autoscaledPool перемещен в crawlingContext.crawler

Чтобы избежать избыточности и упростить доступ к ключевым объектам, мы добавили свойство crawler в аргументы обработчика страницы.

const handlePageFunction = async ({ request, page, crawler }) => {
await crawler.requestQueue.addRequest({ url: 'https://example.com' });
await crawler.autoscaledPool.pause();
}

Это также означает, что некоторые сокращения, такие как puppeteerPool или autoscaledPool, больше не нужны.

const handlePageFunction = async (crawlingContext) => {
crawlingContext.autoscaledPool // больше НЕ существует
crawlingContext.crawler.autoscaledPool // <= правильное использование
}

Замена PuppeteerPool на BrowserPool

BrowserPool был создан для расширения возможностей PuppeteerPool и поддержки других библиотек автоматизации браузера. API похож, но не идентичен.

Доступ к работающему BrowserPool

Только PuppeteerCrawler и PlaywrightCrawler используют BrowserPool. Вы можете получить к нему доступ через объект crawler.

const crawler = new Apify.PlaywrightCrawler({
handlePageFunction: async ({ page, crawler }) => {
crawler.browserPool // <-----
}
});

crawler.browserPool // <-----

Страницы теперь имеют идентификаторы

Они совпадают с crawlingContext.id, что дает вам доступ к полному crawlingContext в хуках. См. раздел Хуки жизненного цикла ниже.

const pageId = browserPool.getPageId

Настройка и хуки жизненного цикла

Самым важным дополнением в BrowserPool являются хуки жизненного цикла. Вы можете получить к ним доступ через browserPoolOptions в обоих краулерах. Полный список browserPoolOptions можно найти в документации browser-pool.

const crawler = new Apify.PuppeteerCrawler({
browserPoolOptions: {
retireBrowserAfterPageCount: 10,
preLaunchHooks: [
async (pageId, launchContext) => {
const { request } = crawler.crawlingContexts.get(pageId);
if (request.userData.useHeadful === true) {
launchContext.launchOptions.headless = false;
}
}
]
}
})

Введение BrowserController

BrowserController - это класс browser-pool, отвечающий за управление браузером. Его цель - предоставить единый API для работы как с Puppeteer, так и с Playwright браузерами. Он работает автоматически в фоновом режиме, но если вам когда-либо потребуется корректно закрыть браузер, следует использовать browserController. Вы можете найти его в аргументах обработчика страницы.

const handlePageFunction = async ({ page, browserController }) => {
// Неправильное использование. Может привести к проблемам, так как обходит BrowserPool
await page.browser().close();

// Правильное использование. Обеспечивает корректное завершение
await browserController.close();

const cookies = [/* объекты cookie */];
// Неправильное использование. Будет работать только в Puppeteer, но не в Playwright
await page.setCookies(...cookies);

// Правильное использование. Будет работать в обоих случаях
await browserController.setCookies(page, cookies);
}

BrowserController также содержит важную информацию о браузере, например, контекст, в котором он был запущен. До SDK v1 это было сложно реализовать.

const handlePageFunction = async ({ browserController }) => {
// Информация о прокси, используемом браузером
browserController.launchContext.proxyInfo

// Сессия, используемая браузером
browserController.launchContext.session
}

Методы BrowserPool в сравнении с PuppeteerPool

Некоторые функции были удалены (в соответствии с предыдущими устареваниями), а некоторые были немного изменены:

// СТАРЫЙ СПОСОБ
await puppeteerPool.recyclePage(page);

// НОВЫЙ СПОСОБ
await page.close();
// СТАРЫЙ СПОСОБ
await puppeteerPool.retire(page.browser());

// НОВЫЙ СПОСОБ
browserPool.retireBrowserByPage(page);
// СТАРЫЙ СПОСОБ
await puppeteerPool.serveLiveViewSnapshot();

// НОВЫЙ СПОСОБ
// LiveView отсутствует в BrowserPool

Обновленные PuppeteerCrawlerOptions

Для обеспечения согласованности между PuppeteerCrawler и PlaywrightCrawler мы обновили параметры.

Удаление gotoFunction

Концепция настраиваемой gotoFunction не идеальна. Особенно учитывая, что мы используем модифицированную gotoExtended. Пользователям необходимо знать об этом при переопределении gotoFunction, если они хотят расширить стандартное поведение. Мы решили заменить gotoFunction на preNavigationHooks и postNavigationHooks.

Следующий пример показывает, как gotoFunction усложняет вещи:

const gotoFunction = async ({ request, page }) => {
// предварительная обработка
await makePageStealthy(page);

// Нужно помнить, как это делать:
const response = await gotoExtended(page, request, {/* нужно помнить значения по умолчанию */});

// постобработка
await page.evaluate(() => {
window.foo = 'bar';
});

// Нельзя забывать!
return response;
}

const crawler = new Apify.PuppeteerCrawler({
gotoFunction,
// ...
})

С preNavigationHooks и postNavigationHooks это намного проще. preNavigationHooks вызываются с двумя аргументами: crawlingContext и gotoOptions. postNavigationHooks вызываются только с crawlingContext.

const preNavigationHooks = [
async ({ page }) => makePageStealthy(page)
];

const postNavigationHooks = [
async ({ page }) => page.evaluate(() => {
window.foo = 'bar'
})
]

const crawler = new Apify.PuppeteerCrawler({
preNavigationHooks,
postNavigationHooks,
// ...
})

launchPuppeteerOptions => launchContext

Эти параметры всегда были источником путаницы, поскольку они объединяли пользовательские опции Apify с launchOptions Puppeteer.

const launchPuppeteerOptions = {
useChrome: true, // опция Apify
headless: false, // опция Puppeteer
}

Используйте новый объект launchContext, который явно определяет launchOptions.

const launchPuppeteerOptions = {
useChrome: true, // Apify option
headless: false, // Puppeteer option
}

Используйте новый объект launchContext, который явно определяет launchOptions.

const launchPuppeteerOptions = {
useChrome: true, // Apify option
headless: false, // Puppeteer option
}