Обновление до версии 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
}