JavaScript-рендеринг
JavaScript-рендеринг - это процесс выполнения JavaScript на странице для изменения её структуры или содержимого. Его также называют клиентским рендерингом, в противоположность серверному рендерингу. Некоторые современные сайты используют клиентский рендеринг, некоторые - серверный, а многие передовые веб-сайты комбинируют оба подхода.
Сайт Crawlee не использует JavaScript-рендеринг для отображения контента, поэтому давайте рассмотрим другой пример. Магазин Apify - это библиотека скраперов и автоматизаций, называемых акторами, которые можно использовать бесплатно. Этот сайт использует JavaScript-рендеринг для отображения списка акторов, что делает его отличным примером для демонстрации.
import { CheerioCrawler } from 'crawlee';
const crawler = new CheerioCrawler({
async requestHandler({ $, request }) {
// Извлекаем текстовое содержимое карточки актора
const actorText = $('.ActorStoreItem').text();
console.log(`АКТОР: ${actorText}`);
}
})
await crawler.run(['https://apify.com/store']);
При запуске кода вы увидите, что краулер не выведет содержимое карточки актора.
АКТОР:
Это происходит потому, что Магазин Apify использует клиентский JavaScript для рендеринга контента, а CheerioCrawler
не может его выполнить, поэтому текст никогда не появляется в HTML страницы.
Вы можете проверить это с помощью Chrome DevTools. Если перейти на https://apify.com/store, кликнуть правой кнопкой мыши и выбрать Просмотр исходного кода страницы, поиск ActorStoreItem не даст результатов. Но если снова кликнуть правой кнопкой, выбрать Инспектировать и искать тот же ActorStoreItem, вы найдете множество совпадений.
Как это возможно? Потому что Просмотр исходного кода показывает оригинальный HTML до выполнения JavaScript - то, что получает CheerioCrawler
. А Инспектировать показывает текущий HTML после выполнения JavaScript. Понимая это, неудивительно, что CheerioCrawler
не может найти данные. Для этого нам нужен безголовый браузер.
Безголовые браузеры
Чтобы получить содержимое .ActorStoreItem
, необходимо использовать безголовый браузер. Можно выбрать одну из двух библиотек для управления браузером: Puppeteer или Playwright. Выбор прост: если вы знаете одну из них - используйте её. Если знаете обе или не знаете ни одной - выбирайте Playwright, так как в большинстве случаев он лучше.
Ожидание рендеринга элементов
Вот примеры кода для обеих библиотек. Playwright немного приятнее в использовании, но обе библиотеки справятся с задачей. Главное отличие в том, что Playwright автоматически ждет появления элементов, а в Puppeteer нужно явно указывать ожидание.
- PlaywrightCrawler
- PuppeteerCrawler
import { PlaywrightCrawler } from 'crawlee';
const crawler = new PlaywrightCrawler({
async requestHandler({ page }) {
// page.locator указывает на элемент в DOM
// используя CSS-селектор, но он не обращается к нему ещё.
const actorCard = page.locator('.ActorStoreItem').first();
// При вызове одного из методов locator Playwright
// ожидает, пока элемент будет отрендерен, и затем обращается к нему.
const actorText = await actorCard.textContent();
console.log(`ACTOR: ${actorText}`);
},
});
await crawler.run(['https://apify.com/store']);
import { PuppeteerCrawler } from 'crawlee';
const crawler = new PuppeteerCrawler({
async requestHandler({ page }) {
// Puppeteer не имеет автоматической функции ожидания,
// поэтому мы должны явно ожидать элемент.
await page.waitForSelector('.ActorStoreItem');
// Puppeteer не имеет вспомогательных методов, таких как locator.textContent,
// поэтому мы должны вручную извлечь значение с помощью встроенного JavaScript.
const actorText = await page.$eval('.ActorStoreItem', (el) => {
return el.textContent;
});
console.log(`ACTOR: ${actorText}`);
},
});
await crawler.run(['https://apify.com/store']);
При запуске кода вы увидите неотформатированное содержимое первой карточки актора:
АКТОР: Web Scraperapify/web-scraperСканирует произвольные веб-сайты используя [...]
Мы не шутим
Если вы не верите, что нужно ждать появления элементов, запустите следующий код без ожидания:
- PlaywrightCrawler
- PuppeteerCrawler
import { PlaywrightCrawler } from 'crawlee';
const crawler = new PlaywrightCrawler({
async requestHandler({ page }) {
// Сразу извлекаем текст со страницы, не дожидаясь
// появления элемента в DOM
const actorText = await page.$eval('.ActorStoreItem', (el) => {
return el.textContent;
});
console.log(`ACTOR: ${actorText}`);
},
});
await crawler.run(['https://apify.com/store']);
import { PuppeteerCrawler } from 'crawlee';
const crawler = new PuppeteerCrawler({
async requestHandler({ page }) {
// Здесь мы не ждем селектора и сразу
// извлекаем текст со страницы.
const actorText = await page.$eval('.ActorStoreItem', (el) => {
return el.textContent;
});
console.log(`ACTOR: ${actorText}`);
},
});
await crawler.run(['https://apify.com/store']);
В обоих случаях запрос будет повторен несколько раз и в итоге завершится с ошибкой:
ОШИБКА [...] Error: не удалось найти элемент, соответствующий селектору ".ActorStoreItem"
Это происходит потому, что при попытке доступа к элементу в браузере он еще не отрендерен в DOM.
Это руководство лишь затрагивает концепцию JavaScript-рендеринга и использования безголовых браузеров. Чтобы узнать больше, продолжите с курсом по Puppeteer и Playwright в Академии Apify. Он бесплатный и с открытым исходным кодом ❤️.