创建项目文件夹,安装 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/