创建项目文件夹,安装 puppeteer 包
mkdir crawler && cd crawler pnpm i puppeteer-core
运行并连接到 Chrome 浏览器
在 package.json 设置属性 type 为 "module" 以使用 ES 模块开发:
{
"type": "module",
"dependencies": {
"puppeteer-core": "^19.10.0"
}
}
创建 server.js 文件,添加以下代码:
import puppeteer from "puppeteer-core";
const browser = await puppeteer.launch({
executablePath: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
headless: false,
defaultViewport: null,
args: ["--start-maximized"],
});
选项 headless 值为 false 表示非无头模式,即显示浏览器界面。选项 defaultViewport 值为 null 不去影响 Chrome 浏览器视口大小。选项 args 传递了标记 --start-maximized 以最大化浏览器窗口。
打开微博首页
获取浏览器的第一个标签页,然后访问 https://weibo.com ,等待导航完成(导航到 https://weibo.com ,然后重定向到一个新地址),等待页面网络空闲:
const [page] = await browser.pages();
await page.goto("https://weibo.com");
await page.waitForNavigation();
await page.waitForNetworkIdle();
获取微博名称
使用 Chrome DevTools 获取一条微博的 XPath //*[@id="scroller"]/div[1]/div[1]/div/article ,然后删除最后一个 [1] ,我们就得到了可以获取界面上所有微博的 XPath //*[@id="scroller"]/div[1]/div/div/article 。
同样使用 Chrome DevTools 获取一条微博中的帐户名称的 XPath //*[@id="scroller"]/div[1]/div[1]/div/article/div/header/div[1]/div/div[1]/a/span ,移除与微博 XPath 相同的部分,然后在前面加上 xpath ,就得到了帐户名称的 XPath xpath/div/header/div[1]/div/div[1]/a/span 。
以下代码获取当前页面所有的微博的帐户名称:
const articles = await page.$x('//*[@id="scroller"]/div[1]/div/div/article');
const names = await Promise.all(
articles.map(
async article =>
await article.$eval(
"xpath/div/header/div[1]/div/div[1]/a/span",
node => node.innerText
)
)
);
无限滚动
微博在会在用户滚动页面时获取下一组微博,我们需要编程实现滚动。Puppeteer 没有这个功能,不过我非常幸运的找到了一个 NPM 库 puppeteer-autoscroll-down 。
let scrollCount = 0;
let start;
let end;
let allNames = [];
do {
// 获取微博名称部分在此省略
await scrollPageToBottom(page, { size: 500 });
start = performance.now();
await page.waitForNetworkIdle();
end = performance.now();
scrollCount++;
} while (end - start > 100 && scrollCount < 5);
微博在滚动后会自动加载下一组微博。我们会等待滚动导致的微博加载完成,并统计加载时间,以此判断是否有网络活动(没有网络活动可能说明全部内部容都已加载)。和滚动次数一起作为判断是否再次滚动的依据。
全部代码
import puppeteer from "puppeteer-core";
import { scrollPageToBottom } from "puppeteer-autoscroll-down";
// 运行并连接到浏览器
const browser = await puppeteer.launch({
executablePath: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
protocolTimeout: 99999999,
headless: false,
defaultViewport: null,
args: ["--start-maximized"],
});
// 获取第一个标签页
const [page] = await browser.pages();
// 访问微博并等待导航完成和网络空闲
await page.goto("https://weibo.com");
await page.waitForNavigation();
await page.waitForNetworkIdle();
let scrollCount = 0;
let start;
let end;
let allNames = [];
do {
// 获取所有微博的发布帐户名称
const articles = await page.$x('//*[@id="scroller"]/div[1]/div/div/article');
const names = await Promise.all(
articles.map(
async article =>
await article.$eval(
"xpath/div/header/div[1]/div/div[1]/a/span",
node => node.innerText
)
)
);
allNames = allNames.concat(names);
// 滚动页面并等待下一组微博加载完毕
await scrollPageToBottom(page, { size: 500 });
start = performance.now();
await page.waitForNetworkIdle();
end = performance.now();
scrollCount++;
} while (end - start > 500 && scrollCount < 5);
console.log(allNames);
参见
摘自:https://elementalgrady.com/posts/developing-crawlers-with-puppeteer/