如何使用 Apify 抓取动态加载列表和单个页面?
How to scrape dynamic-loading listing and individual pages using Apify?
如何使用 Apify 的功能生成完整的 URL 列表,以便从索引页中抓取,当用户滚动到底部时,索引页中的项目会按顺序分批添加?换句话说,它是动态的 loading/infinite 滚动,而不是在单击按钮时进行操作。
具体来说,这个页面 - https://www.provokemedia.com/agency-playbook 除了最初显示的 13 个条目,我不能让它识别任何其他条目。
这些元素似乎位于每个段的底部,在每个段添加时 display: none
更改为 display: block
。此处的“style
”标记在原始源代码中不可见,只能通过 DevTools Inspector。
<div class="text-center" id="loader" style="display: none;">
<h5>Loading more ...</h5>
</div>
这是我的网络抓取工具的基本设置...
起始网址:
https://www.provokemedia.com/agency-playbook
{
"label": "START"
}
Link 选择器:
div.agencies div.column a
伪网址:
https://www.provokemedia.com/agency-playbook/agency-profile/[.*]
{
"label": "DETAIL"
}
页面功能:
async function pageFunction(context) {
const { request, log, skipLinks } = context;
// request: holds info about current page
// log: logs messages to console
// skipLinks: don't enqueue matching Pseudo Links on current page
// >> cf. https://docs.apify.com/tutorials/apify-scrapers/getting-started#new-page-function-boilerplate
// *********************************************************** //
// START page //
// *********************************************************** //
if (request.userData.label === 'START') {
log.info('Store opened!');
// Do some stuff later.
}
// *********************************************************** //
// DETAIL page //
// *********************************************************** //
if (request.userData.label === 'DETAIL') {
log.info(`Scraping ${request.url}`);
await skipLinks();
// Do some scraping.
return {
// Scraped data.
}
}
}
据推测,在 START 内容中,我需要确保显示要排队的整个列表,而不仅仅是 13 个。
我已经通读了 Apify 的文档,包括关于“Waiting for dynamic content”的文档。 await waitFor('#loader');
似乎是个不错的选择。
我将以下内容添加到开始部分...
let timeoutMillis; // undefined
const loadingThing = '#loader';
while (true) {
log.info('Waiting for the "Loading more" thing.');
try {
// Default timeout first time.
await waitFor(loadingThing, { timeoutMillis });
// 2 sec timeout after the first.
timeoutMillis = 2000;
} catch (err) {
// Ignore the timeout error.
log.info('Could not find the "Loading more thing", '
+ 'we\'ve reached the end.');
break;
}
log.info('Going to load more.');
// Scroll to bottom, to expose more
// $(loadingThing).click();
window.scrollTo(0, document.body.scrollHeight);
}
但是没用...
2021-01-08T23:24:11.186Z INFO Store opened!
2021-01-08T23:24:11.189Z INFO Waiting for the "Loading more" thing.
2021-01-08T23:24:11.190Z INFO Could not find the "Loading more thing", we've reached the end.
2021-01-08T23:24:13.393Z INFO Scraping https://www.provokemedia.com/agency-playbook/agency-profile/gci-health
与其他网页不同,当我在 DevTools 控制台中手动输入 window.scrollTo(0, document.body.scrollHeight);
时,此页面不会滚动到底部。
但是,当在控制台中手动执行时,此代码会添加一个小延迟 - setTimeout(function(){window.scrollBy(0,document.body.scrollHeight)}, 1);
- 发现 in this question - 确实 跳转到底部每次...
如果我添加 that 行来替换上面 while 循环的最后一行,但是,循环仍然记录它找不到元素。
我在使用这些方法吗?不知道该往哪个方向转。
@LukášKřivka 在 的回答为我的回答提供了框架...
总结:
- 创建一个函数来强制滚动到页面底部
- 获取所有元素
详情:
- 在
while
循环中,滚动到页面底部。
- 等等,例如。渲染新内容需要 5 秒。
- 保留目标 link 选择器数量的 运行 计数,以供参考。
- 直到没有更多项目加载。
仅当 pageFunction 正在检查索引页面时调用此函数(例如,用户数据中的任意页面名称,如 START/LISTING)。
async function pageFunction(context) {
// *********************************************************** //
// Few utilities //
// *********************************************************** //
const { request, log, skipLinks } = context;
// request: holds info about current page
// log: logs messages to console
// skipLinks: don't enqueue matching Pseudo Links on current page
// >> cf. https://docs.apify.com/tutorials/apify-scrapers/getting-started#new-page-function-boilerplate
const $ = jQuery;
// *********************************************************** //
// Infinite scroll handling //
// *********************************************************** //
// Here we define the infinite scroll function, it has to be defined inside pageFunction
const infiniteScroll = async (maxTime) => { //maxTime to wait
const startedAt = Date.now();
// count items on page
let itemCount = $('div.agencies div.column a').length; // Update the selector
while (true) {
log.info(`INFINITE SCROLL --- ${itemCount} items loaded --- ${request.url}`)
// timeout to prevent infinite loop
if (Date.now() - startedAt > maxTime) {
return;
}
// scroll page x, y
scrollBy(0, 9999);
// wait for elements to render
await context.waitFor(5000); // This can be any number that works for your website
// count items on page again
const currentItemCount = $('div.agencies div.column a').length; // Update the selector
// check for no more
// We check if the number of items changed after the scroll, if not we finish
if (itemCount === currentItemCount) {
return;
}
// update item count
itemCount = currentItemCount;
}
}
// *********************************************************** //
// START page //
// *********************************************************** //
if (request.userData.label === 'START') {
log.info('Store opened!');
// Do some stuff later.
// scroll to bottom to force load of all elements
await infiniteScroll(60000); // Let's try 60 seconds max
}
// *********************************************************** //
// DETAIL page //
// *********************************************************** //
if (request.userData.label === 'DETAIL') {
log.info(`Scraping ${request.url}`);
await skipLinks();
// Do some scraping (get elements with jQuery selectors)
return {
// Scraped data.
}
}
}
如何使用 Apify 的功能生成完整的 URL 列表,以便从索引页中抓取,当用户滚动到底部时,索引页中的项目会按顺序分批添加?换句话说,它是动态的 loading/infinite 滚动,而不是在单击按钮时进行操作。
具体来说,这个页面 - https://www.provokemedia.com/agency-playbook 除了最初显示的 13 个条目,我不能让它识别任何其他条目。
这些元素似乎位于每个段的底部,在每个段添加时 display: none
更改为 display: block
。此处的“style
”标记在原始源代码中不可见,只能通过 DevTools Inspector。
<div class="text-center" id="loader" style="display: none;">
<h5>Loading more ...</h5>
</div>
这是我的网络抓取工具的基本设置...
起始网址:
https://www.provokemedia.com/agency-playbook
{
"label": "START"
}
Link 选择器:
div.agencies div.column a
伪网址:
https://www.provokemedia.com/agency-playbook/agency-profile/[.*]
{
"label": "DETAIL"
}
页面功能:
async function pageFunction(context) {
const { request, log, skipLinks } = context;
// request: holds info about current page
// log: logs messages to console
// skipLinks: don't enqueue matching Pseudo Links on current page
// >> cf. https://docs.apify.com/tutorials/apify-scrapers/getting-started#new-page-function-boilerplate
// *********************************************************** //
// START page //
// *********************************************************** //
if (request.userData.label === 'START') {
log.info('Store opened!');
// Do some stuff later.
}
// *********************************************************** //
// DETAIL page //
// *********************************************************** //
if (request.userData.label === 'DETAIL') {
log.info(`Scraping ${request.url}`);
await skipLinks();
// Do some scraping.
return {
// Scraped data.
}
}
}
据推测,在 START 内容中,我需要确保显示要排队的整个列表,而不仅仅是 13 个。
我已经通读了 Apify 的文档,包括关于“Waiting for dynamic content”的文档。 await waitFor('#loader');
似乎是个不错的选择。
我将以下内容添加到开始部分...
let timeoutMillis; // undefined
const loadingThing = '#loader';
while (true) {
log.info('Waiting for the "Loading more" thing.');
try {
// Default timeout first time.
await waitFor(loadingThing, { timeoutMillis });
// 2 sec timeout after the first.
timeoutMillis = 2000;
} catch (err) {
// Ignore the timeout error.
log.info('Could not find the "Loading more thing", '
+ 'we\'ve reached the end.');
break;
}
log.info('Going to load more.');
// Scroll to bottom, to expose more
// $(loadingThing).click();
window.scrollTo(0, document.body.scrollHeight);
}
但是没用...
2021-01-08T23:24:11.186Z INFO Store opened!
2021-01-08T23:24:11.189Z INFO Waiting for the "Loading more" thing.
2021-01-08T23:24:11.190Z INFO Could not find the "Loading more thing", we've reached the end.
2021-01-08T23:24:13.393Z INFO Scraping https://www.provokemedia.com/agency-playbook/agency-profile/gci-health
与其他网页不同,当我在 DevTools 控制台中手动输入 window.scrollTo(0, document.body.scrollHeight);
时,此页面不会滚动到底部。
但是,当在控制台中手动执行时,此代码会添加一个小延迟 - setTimeout(function(){window.scrollBy(0,document.body.scrollHeight)}, 1);
- 发现 in this question - 确实 跳转到底部每次...
如果我添加 that 行来替换上面 while 循环的最后一行,但是,循环仍然记录它找不到元素。
我在使用这些方法吗?不知道该往哪个方向转。
@LukášKřivka 在
总结:
- 创建一个函数来强制滚动到页面底部
- 获取所有元素
详情:
- 在
while
循环中,滚动到页面底部。 - 等等,例如。渲染新内容需要 5 秒。
- 保留目标 link 选择器数量的 运行 计数,以供参考。
- 直到没有更多项目加载。
仅当 pageFunction 正在检查索引页面时调用此函数(例如,用户数据中的任意页面名称,如 START/LISTING)。
async function pageFunction(context) {
// *********************************************************** //
// Few utilities //
// *********************************************************** //
const { request, log, skipLinks } = context;
// request: holds info about current page
// log: logs messages to console
// skipLinks: don't enqueue matching Pseudo Links on current page
// >> cf. https://docs.apify.com/tutorials/apify-scrapers/getting-started#new-page-function-boilerplate
const $ = jQuery;
// *********************************************************** //
// Infinite scroll handling //
// *********************************************************** //
// Here we define the infinite scroll function, it has to be defined inside pageFunction
const infiniteScroll = async (maxTime) => { //maxTime to wait
const startedAt = Date.now();
// count items on page
let itemCount = $('div.agencies div.column a').length; // Update the selector
while (true) {
log.info(`INFINITE SCROLL --- ${itemCount} items loaded --- ${request.url}`)
// timeout to prevent infinite loop
if (Date.now() - startedAt > maxTime) {
return;
}
// scroll page x, y
scrollBy(0, 9999);
// wait for elements to render
await context.waitFor(5000); // This can be any number that works for your website
// count items on page again
const currentItemCount = $('div.agencies div.column a').length; // Update the selector
// check for no more
// We check if the number of items changed after the scroll, if not we finish
if (itemCount === currentItemCount) {
return;
}
// update item count
itemCount = currentItemCount;
}
}
// *********************************************************** //
// START page //
// *********************************************************** //
if (request.userData.label === 'START') {
log.info('Store opened!');
// Do some stuff later.
// scroll to bottom to force load of all elements
await infiniteScroll(60000); // Let's try 60 seconds max
}
// *********************************************************** //
// DETAIL page //
// *********************************************************** //
if (request.userData.label === 'DETAIL') {
log.info(`Scraping ${request.url}`);
await skipLinks();
// Do some scraping (get elements with jQuery selectors)
return {
// Scraped data.
}
}
}