Puppeteer - 向下滚动直到你不能再
Puppeteer - scroll down until you can't anymore
我处于向下滚动时创建新内容的情况。新内容有一个特定的 class 名称。
如何继续向下滚动直到加载完所有元素?
换句话说,我想达到这样的阶段,如果我继续向下滚动,则不会加载任何新内容。
我正在使用代码向下滚动,再加上一个
await page.waitForSelector('.class_name');
这种方法的问题是,在加载所有元素后,代码继续向下滚动,没有创建新元素,最终出现超时错误。
这是代码:
await page.evaluate( () => {
window.scrollBy(0, window.innerHeight);
});
await page.waitForSelector('.class_name');
试一试:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false
});
const page = await browser.newPage();
await page.goto('https://www.yoursite.com');
await page.setViewport({
width: 1200,
height: 800
});
await autoScroll(page);
await page.screenshot({
path: 'yoursite.png',
fullPage: true
});
await browser.close();
})();
async function autoScroll(page){
await page.evaluate(async () => {
await new Promise((resolve, reject) => {
var totalHeight = 0;
var distance = 100;
var timer = setInterval(() => {
var scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if(totalHeight >= scrollHeight - window.innerHeight){
clearInterval(timer);
resolve();
}
}, 100);
});
});
}
来源:https://github.com/chenxiaochun/blog/issues/38
编辑
在计算中添加了 window.innerHeight
,因为可用的滚动距离是主体高度减去视口高度,而不是整个主体高度。
可以通过两种方式向下滚动到页面底部:
- 使用scrollIntoView(滚动到页面底部可以创建更多内容的部分)和选择器(即
document.querySelectorAll('.class_name').length
检查是否已生成更多内容)
- 使用scrollBy (to incrementally scroll down the page) and either setTimeout or setInterval(增量检查我们是否在页面底部)
这是一个使用 scrollIntoView
和选择器(假设 .class_name
是我们滚动到更多内容的选择器)的简单 JavaScript 实现,我们可以 运行在浏览器中:
方法一:使用scrollIntoView和选择器
const delay = 3000;
const wait = (ms) => new Promise(res => setTimeout(res, ms));
const count = async () => document.querySelectorAll('.class_name').length;
const scrollDown = async () => {
document.querySelector('.class_name:last-child')
.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
}
let preCount = 0;
let postCount = 0;
do {
preCount = await count();
await scrollDown();
await wait(delay);
postCount = await count();
} while (postCount > preCount);
await wait(delay);
在这个方法中,我们在滚动前(preCount
)和滚动后(postCount
)比较 .class_name
选择器的数量,以检查我们是否在页面底部:
if (postCount > precount) {
// NOT bottom of page
} else {
// bottom of page
}
这里有 2 种可能的实现,使用 setTimeout
或 setInterval
和 scrollBy
在普通 JavaScript 中,我们可以在浏览器控制台中 运行:
方法 2a:使用 setTimeout 和 scrollBy
const distance = 100;
const delay = 100;
while (document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight) {
document.scrollingElement.scrollBy(0, distance);
await new Promise(resolve => { setTimeout(resolve, delay); });
}
方法 2b:使用 setInterval 和 scrollBy
const distance = 100;
const delay = 100;
const timer = setInterval(() => {
document.scrollingElement.scrollBy(0, distance);
if (document.scrollingElement.scrollTop + window.innerHeight >= document.scrollingElement.scrollHeight) {
clearInterval(timer);
}
}, delay);
在此方法中,我们将 document.scrollingElement.scrollTop + window.innerHeight
与 document.scrollingElement.scrollHeight
进行比较,以检查我们是否位于页面底部:
if (document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight) {
// NOT bottom of page
} else {
// bottom of page
}
如果上面的任何 JavaScript 代码将页面一直滚动到底部,那么我们就知道它正在工作,我们可以使用 Puppeteer 自动执行此操作。
以下是示例 Puppeteer Node.js 脚本,它们将向下滚动到页面底部并等待几秒钟,然后关闭浏览器。
Puppeteer 方法 1:将 scrollIntoView 与选择器一起使用 (.class_name
)
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: ['--window-size=800,600']
});
const page = await browser.newPage();
await page.goto('https://example.com');
const delay = 3000;
let preCount = 0;
let postCount = 0;
do {
preCount = await getCount(page);
await scrollDown(page);
await page.waitFor(delay);
postCount = await getCount(page);
} while (postCount > preCount);
await page.waitFor(delay);
await browser.close();
})();
async function getCount(page) {
return await page.$$eval('.class_name', a => a.length);
}
async function scrollDown(page) {
await page.$eval('.class_name:last-child', e => {
e.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
});
}
Puppeteer 方法 2a:使用 setTimeout 和 scrollBy
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: ['--window-size=800,600']
});
const page = await browser.newPage();
await page.goto('https://example.com');
await scrollToBottom(page);
await page.waitFor(3000);
await browser.close();
})();
async function scrollToBottom(page) {
const distance = 100; // should be less than or equal to window.innerHeight
const delay = 100;
while (await page.evaluate(() => document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight)) {
await page.evaluate((y) => { document.scrollingElement.scrollBy(0, y); }, distance);
await page.waitFor(delay);
}
}
Puppeteer 方法 2b:使用 setInterval 和 scrollBy
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: ['--window-size=800,600']
});
const page = await browser.newPage();
await page.goto('https://example.com');
await page.evaluate(scrollToBottom);
await page.waitFor(3000);
await browser.close();
})();
async function scrollToBottom() {
await new Promise(resolve => {
const distance = 100; // should be less than or equal to window.innerHeight
const delay = 100;
const timer = setInterval(() => {
document.scrollingElement.scrollBy(0, distance);
if (document.scrollingElement.scrollTop + window.innerHeight >= document.scrollingElement.scrollHeight) {
clearInterval(timer);
resolve();
}
}, delay);
});
}
基于此 url
的回答
await page.evaluate(() => {
window.scrollBy(0, window.innerHeight);
});
您可以使用 page.keyboard
对象使用以下代码:
await page.keyboard.press('ArrowDown');
delay(2000) //wait for 2 seconds
await page.keyboard.press('ArrowUp');
function delay(milliseconds) { //function for waiting
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, milliseconds);
});
}
此处的许多解决方案都假定页面高度不变。即使页面高度发生变化(例如,在用户向下滚动时加载新内容),此实现也能正常工作。
await page.evaluate(() => new Promise((resolve) => {
var scrollTop = -1;
const interval = setInterval(() => {
window.scrollBy(0, 100);
if(document.documentElement.scrollTop !== scrollTop) {
scrollTop = document.documentElement.scrollTop;
return;
}
clearInterval(interval);
resolve();
}, 10);
}));
非常简单的解决方案
let lastHeight = await page.evaluate('document.body.scrollHeight');
while (true) {
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
await page.waitForTimeout(2000); // sleep a bit
let newHeight = await page.evaluate('document.body.scrollHeight');
if (newHeight === lastHeight) {
break;
}
lastHeight = newHeight;
}
容易得多:
await page.evaluate(async () => {
let scrollPosition = 0
let documentHeight = document.body.scrollHeight
while (documentHeight > scrollPosition) {
window.scrollBy(0, documentHeight)
await new Promise(resolve => {
setTimeout(resolve, 1000)
})
scrollPosition = documentHeight
documentHeight = document.body.scrollHeight
}
})
与@EdvinTr 类似的解决方案,它给了我很好的结果。
滚动对比页面的Y Offset,很简单
let originalOffset = 0;
while (true) {
await page.evaluate('window.scrollBy(0, document.body.scrollHeight)');
await page.waitForTimeout(200);
let newOffset = await page.evaluate('window.pageYOffset');
if (originalOffset === newOffset) {
break;
}
originalOffset = newOffset;
}
我处于向下滚动时创建新内容的情况。新内容有一个特定的 class 名称。
如何继续向下滚动直到加载完所有元素?
换句话说,我想达到这样的阶段,如果我继续向下滚动,则不会加载任何新内容。
我正在使用代码向下滚动,再加上一个
await page.waitForSelector('.class_name');
这种方法的问题是,在加载所有元素后,代码继续向下滚动,没有创建新元素,最终出现超时错误。
这是代码:
await page.evaluate( () => {
window.scrollBy(0, window.innerHeight);
});
await page.waitForSelector('.class_name');
试一试:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false
});
const page = await browser.newPage();
await page.goto('https://www.yoursite.com');
await page.setViewport({
width: 1200,
height: 800
});
await autoScroll(page);
await page.screenshot({
path: 'yoursite.png',
fullPage: true
});
await browser.close();
})();
async function autoScroll(page){
await page.evaluate(async () => {
await new Promise((resolve, reject) => {
var totalHeight = 0;
var distance = 100;
var timer = setInterval(() => {
var scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if(totalHeight >= scrollHeight - window.innerHeight){
clearInterval(timer);
resolve();
}
}, 100);
});
});
}
来源:https://github.com/chenxiaochun/blog/issues/38
编辑
在计算中添加了 window.innerHeight
,因为可用的滚动距离是主体高度减去视口高度,而不是整个主体高度。
可以通过两种方式向下滚动到页面底部:
- 使用scrollIntoView(滚动到页面底部可以创建更多内容的部分)和选择器(即
document.querySelectorAll('.class_name').length
检查是否已生成更多内容) - 使用scrollBy (to incrementally scroll down the page) and either setTimeout or setInterval(增量检查我们是否在页面底部)
这是一个使用 scrollIntoView
和选择器(假设 .class_name
是我们滚动到更多内容的选择器)的简单 JavaScript 实现,我们可以 运行在浏览器中:
方法一:使用scrollIntoView和选择器
const delay = 3000;
const wait = (ms) => new Promise(res => setTimeout(res, ms));
const count = async () => document.querySelectorAll('.class_name').length;
const scrollDown = async () => {
document.querySelector('.class_name:last-child')
.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
}
let preCount = 0;
let postCount = 0;
do {
preCount = await count();
await scrollDown();
await wait(delay);
postCount = await count();
} while (postCount > preCount);
await wait(delay);
在这个方法中,我们在滚动前(preCount
)和滚动后(postCount
)比较 .class_name
选择器的数量,以检查我们是否在页面底部:
if (postCount > precount) {
// NOT bottom of page
} else {
// bottom of page
}
这里有 2 种可能的实现,使用 setTimeout
或 setInterval
和 scrollBy
在普通 JavaScript 中,我们可以在浏览器控制台中 运行:
方法 2a:使用 setTimeout 和 scrollBy
const distance = 100;
const delay = 100;
while (document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight) {
document.scrollingElement.scrollBy(0, distance);
await new Promise(resolve => { setTimeout(resolve, delay); });
}
方法 2b:使用 setInterval 和 scrollBy
const distance = 100;
const delay = 100;
const timer = setInterval(() => {
document.scrollingElement.scrollBy(0, distance);
if (document.scrollingElement.scrollTop + window.innerHeight >= document.scrollingElement.scrollHeight) {
clearInterval(timer);
}
}, delay);
在此方法中,我们将 document.scrollingElement.scrollTop + window.innerHeight
与 document.scrollingElement.scrollHeight
进行比较,以检查我们是否位于页面底部:
if (document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight) {
// NOT bottom of page
} else {
// bottom of page
}
如果上面的任何 JavaScript 代码将页面一直滚动到底部,那么我们就知道它正在工作,我们可以使用 Puppeteer 自动执行此操作。
以下是示例 Puppeteer Node.js 脚本,它们将向下滚动到页面底部并等待几秒钟,然后关闭浏览器。
Puppeteer 方法 1:将 scrollIntoView 与选择器一起使用 (.class_name
)
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: ['--window-size=800,600']
});
const page = await browser.newPage();
await page.goto('https://example.com');
const delay = 3000;
let preCount = 0;
let postCount = 0;
do {
preCount = await getCount(page);
await scrollDown(page);
await page.waitFor(delay);
postCount = await getCount(page);
} while (postCount > preCount);
await page.waitFor(delay);
await browser.close();
})();
async function getCount(page) {
return await page.$$eval('.class_name', a => a.length);
}
async function scrollDown(page) {
await page.$eval('.class_name:last-child', e => {
e.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
});
}
Puppeteer 方法 2a:使用 setTimeout 和 scrollBy
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: ['--window-size=800,600']
});
const page = await browser.newPage();
await page.goto('https://example.com');
await scrollToBottom(page);
await page.waitFor(3000);
await browser.close();
})();
async function scrollToBottom(page) {
const distance = 100; // should be less than or equal to window.innerHeight
const delay = 100;
while (await page.evaluate(() => document.scrollingElement.scrollTop + window.innerHeight < document.scrollingElement.scrollHeight)) {
await page.evaluate((y) => { document.scrollingElement.scrollBy(0, y); }, distance);
await page.waitFor(delay);
}
}
Puppeteer 方法 2b:使用 setInterval 和 scrollBy
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: ['--window-size=800,600']
});
const page = await browser.newPage();
await page.goto('https://example.com');
await page.evaluate(scrollToBottom);
await page.waitFor(3000);
await browser.close();
})();
async function scrollToBottom() {
await new Promise(resolve => {
const distance = 100; // should be less than or equal to window.innerHeight
const delay = 100;
const timer = setInterval(() => {
document.scrollingElement.scrollBy(0, distance);
if (document.scrollingElement.scrollTop + window.innerHeight >= document.scrollingElement.scrollHeight) {
clearInterval(timer);
resolve();
}
}, delay);
});
}
基于此 url
的回答await page.evaluate(() => {
window.scrollBy(0, window.innerHeight);
});
您可以使用 page.keyboard
对象使用以下代码:
await page.keyboard.press('ArrowDown');
delay(2000) //wait for 2 seconds
await page.keyboard.press('ArrowUp');
function delay(milliseconds) { //function for waiting
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, milliseconds);
});
}
此处的许多解决方案都假定页面高度不变。即使页面高度发生变化(例如,在用户向下滚动时加载新内容),此实现也能正常工作。
await page.evaluate(() => new Promise((resolve) => {
var scrollTop = -1;
const interval = setInterval(() => {
window.scrollBy(0, 100);
if(document.documentElement.scrollTop !== scrollTop) {
scrollTop = document.documentElement.scrollTop;
return;
}
clearInterval(interval);
resolve();
}, 10);
}));
非常简单的解决方案
let lastHeight = await page.evaluate('document.body.scrollHeight');
while (true) {
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
await page.waitForTimeout(2000); // sleep a bit
let newHeight = await page.evaluate('document.body.scrollHeight');
if (newHeight === lastHeight) {
break;
}
lastHeight = newHeight;
}
容易得多:
await page.evaluate(async () => {
let scrollPosition = 0
let documentHeight = document.body.scrollHeight
while (documentHeight > scrollPosition) {
window.scrollBy(0, documentHeight)
await new Promise(resolve => {
setTimeout(resolve, 1000)
})
scrollPosition = documentHeight
documentHeight = document.body.scrollHeight
}
})
与@EdvinTr 类似的解决方案,它给了我很好的结果。 滚动对比页面的Y Offset,很简单
let originalOffset = 0;
while (true) {
await page.evaluate('window.scrollBy(0, document.body.scrollHeight)');
await page.waitForTimeout(200);
let newOffset = await page.evaluate('window.pageYOffset');
if (originalOffset === newOffset) {
break;
}
originalOffset = newOffset;
}