JS `Puppeteer` / `Playwright` 自动化浏览器脚本:模拟用户行为与数据抓取

各位朋友,各位同行,大家好!我是老码,今天咱们来聊聊自动化浏览器脚本,也就是用PuppeteerPlaywright这两位大咖,模拟用户行为,顺便抓点数据的故事。这玩意儿,说白了,就是让机器替咱们干一些重复性的网页操作,比如自动登录、填写表单、点击按钮,甚至还可以把网页上的数据扒下来,简直是懒人福音,效率神器!

第一章:浏览器自动化,从“Hello World”开始

咱们先来个最简单的例子,用Puppeteer打开一个网页,截个图。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch(); // 启动浏览器
  const page = await browser.newPage(); // 创建新页面
  await page.goto('https://www.example.com'); // 访问网页
  await page.screenshot({ path: 'example.png' }); // 截图保存
  await browser.close(); // 关闭浏览器
})();

这段代码的意思很简单:

  1. 引入puppeteer库。
  2. 启动一个浏览器实例。
  3. 创建一个新的页面。
  4. 让页面跳转到https://www.example.com
  5. 把页面截图保存为example.png
  6. 关闭浏览器。

运行这段代码,你就会在当前目录下看到一张名为example.png的图片,内容就是https://www.example.com这个网页的截图。

是不是很简单? 这就相当于咱们跟浏览器打了个招呼:“Hello World,给我截个图!”

第二章:Puppeteer vs Playwright:双雄争霸,各有千秋

PuppeteerPlaywright都是Node.js库,用来控制无头浏览器(Headless Browser)。 所谓无头浏览器,就是没有图形界面的浏览器,可以在后台运行,模拟用户的各种操作。

那它们有什么区别呢?咱们来简单对比一下:

特性 Puppeteer Playwright
浏览器支持 Chrome/Chromium Chromium, Firefox, WebKit
跨浏览器 不支持 支持
API设计 相对简洁 更强大,提供更多高级功能
稳定性 较为稳定 也在不断更新,可能存在一些小问题
适用场景 主要针对Chrome/Chromium的自动化测试和爬虫 需要支持多种浏览器的场景,更复杂的自动化任务

简单来说,Puppeteer更轻量级,上手更快,适合简单的自动化任务。Playwright功能更强大,支持更多浏览器,适合更复杂的场景。就像选车一样,看你跑什么路,干什么活。

第三章:模拟用户行为:让机器像人一样思考

光截个图可不行,咱们要让机器模拟用户的各种行为,比如点击按钮、填写表单、滚动页面等等。

3.1 点击按钮

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com'); // 替换成你的目标网址
  await page.click('#submit-button'); // 点击id为submit-button的按钮
  await browser.close();
})();

这段代码会点击页面上id为submit-button的按钮。 page.click()方法接收一个CSS选择器作为参数,用来定位要点击的元素。

3.2 填写表单

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/login'); // 替换成你的登录页面网址
  await page.type('#username', 'your_username'); // 填写用户名
  await page.type('#password', 'your_password'); // 填写密码
  await page.click('#login-button'); // 点击登录按钮
  await page.waitForNavigation(); // 等待页面跳转
  await browser.close();
})();

这段代码会填写用户名和密码,然后点击登录按钮。 page.type()方法接收两个参数:一个是CSS选择器,用来定位要填写的输入框;另一个是要输入的内容。 page.waitForNavigation()方法用来等待页面跳转,确保登录成功。

3.3 滚动页面

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com'); // 替换成你的目标网址
  await page.evaluate(() => {
    window.scrollBy(0, 1000); // 向下滚动1000像素
  });
  await browser.close();
})();

这段代码会向下滚动页面1000像素。 page.evaluate()方法可以在浏览器上下文中执行JavaScript代码。

3.4 模拟鼠标悬停

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com'); // 替换成你的目标网址
  await page.hover('#menu-item'); // 鼠标悬停在id为menu-item的元素上
  await browser.close();
})();

这段代码会模拟鼠标悬停在页面上id为menu-item的元素上。

第四章:数据抓取:把网页变成你的数据库

模拟用户行为只是第一步,更重要的是把网页上的数据抓取下来。

4.1 获取文本内容

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com'); // 替换成你的目标网址
  const title = await page.$eval('title', el => el.textContent); // 获取title标签的文本内容
  console.log(title);
  await browser.close();
})();

这段代码会获取网页的<title>标签的文本内容,并打印到控制台。 page.$eval()方法接收两个参数:一个是CSS选择器,用来定位要获取的元素;另一个是一个函数,用来处理获取到的元素。

4.2 获取属性值

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com'); // 替换成你的目标网址
  const href = await page.$eval('a', el => el.href); // 获取第一个a标签的href属性值
  console.log(href);
  await browser.close();
})();

这段代码会获取页面上第一个<a>标签的href属性值,并打印到控制台。

4.3 获取多个元素

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/products'); // 替换成你的目标网址
  const productTitles = await page.$$eval('.product-item .title', elements => {
    return elements.map(el => el.textContent);
  });
  console.log(productTitles);
  await browser.close();
})();

这段代码会获取页面上所有class为product-item的元素下的class为title的元素的文本内容,并打印到控制台。 page.$$eval()方法和page.$eval()方法类似,但是它可以获取多个元素。

4.4 处理动态内容

有些网页的内容是动态加载的,比如通过Ajax请求获取数据。 咱们需要等待这些数据加载完成才能抓取。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/dynamic-content'); // 替换成你的目标网址
  await page.waitForSelector('#content'); // 等待id为content的元素出现
  const content = await page.$eval('#content', el => el.textContent);
  console.log(content);
  await browser.close();
})();

这段代码会等待页面上id为content的元素出现,然后再获取其文本内容。 page.waitForSelector()方法可以等待指定的元素出现。 还可以使用page.waitForTimeout()来等待一段时间。

第五章:进阶技巧:让你的脚本更强大

5.1 设置请求头

有时候,我们需要设置请求头来模拟不同的浏览器或者用户。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'); // 设置User-Agent
  await page.setExtraHTTPHeaders({
    'Accept-Language': 'zh-CN,zh;q=0.9' // 设置Accept-Language
  });
  await page.goto('https://example.com'); // 替换成你的目标网址
  await browser.close();
})();

5.2 使用代理

为了避免被网站封禁,我们可以使用代理服务器。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    args: ['--proxy-server=http://your_proxy_address:your_proxy_port'] // 设置代理服务器
  });
  const page = await browser.newPage();
  await page.goto('https://example.com'); // 替换成你的目标网址
  await browser.close();
})();

5.3 处理弹窗

有些网页会有弹窗,我们需要处理这些弹窗才能继续操作。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  page.on('dialog', async dialog => {
    console.log(dialog.message());
    await dialog.dismiss(); // 关闭弹窗
    // 或者 await dialog.accept('your_answer'); // 接受弹窗,并输入内容
  });

  await page.goto('https://example.com/alert'); // 替换成你的目标网址
  await page.click('#trigger-alert'); // 触发弹窗
  await browser.close();
})();

5.4 保持登录状态

如果需要抓取需要登录才能访问的页面,我们需要保持登录状态。 通常的做法是:

  1. 先登录一次,获取cookie。
  2. 把cookie保存下来。
  3. 下次访问时,把cookie设置到请求头中。
const puppeteer = require('puppeteer');
const fs = require('fs');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // 登录
  await page.goto('https://example.com/login'); // 替换成你的登录页面网址
  await page.type('#username', 'your_username'); // 填写用户名
  await page.type('#password', 'your_password'); // 填写密码
  await page.click('#login-button'); // 点击登录按钮
  await page.waitForNavigation(); // 等待页面跳转

  // 获取cookie
  const cookies = await page.cookies();
  fs.writeFileSync('cookies.json', JSON.stringify(cookies)); // 保存cookie

  await browser.close();
})();

// 下次访问时:
(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // 读取cookie
  const cookiesString = fs.readFileSync('cookies.json', 'utf-8');
  const cookies = JSON.parse(cookiesString);
  await page.setCookie(...cookies); // 设置cookie

  await page.goto('https://example.com/protected'); // 访问需要登录的页面
  // ...
  await browser.close();
})();

第六章:实战案例:抓取商品信息

咱们来个实际的例子,抓取某个电商网站的商品信息,包括商品标题、价格和图片链接。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/products'); // 替换成你的商品列表页面网址

  const products = await page.$$eval('.product-item', elements => {
    return elements.map(el => {
      const title = el.querySelector('.title').textContent;
      const price = el.querySelector('.price').textContent;
      const imageUrl = el.querySelector('img').src;
      return { title, price, imageUrl };
    });
  });

  console.log(products);
  await browser.close();
})();

这段代码会抓取页面上所有class为product-item的元素的商品标题、价格和图片链接,并打印到控制台。

第七章:注意事项:爬虫有风险,使用需谨慎

虽然自动化浏览器脚本很强大,但是也要注意一些问题:

  • 尊重网站的robots.txt协议。 robots.txt文件告诉爬虫哪些页面可以抓取,哪些页面不能抓取。
  • 不要频繁访问同一个网站。 过于频繁的访问可能会被网站认为是恶意攻击,导致IP被封禁。
  • 遵守法律法规。 不要抓取涉及个人隐私或者侵犯版权的数据。
  • 网站结构变化。 网站结构变化可能会导致你的脚本失效,需要及时更新。

总而言之,爬虫有风险,使用需谨慎。 要遵守规则,做一个文明的爬虫。

第八章:Playwright 代码示例

以上大部分代码都使用了Puppeteer,现在我们来用Playwright实现类似的功能。

8.1 Playwright 打开网页并截图

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://www.example.com');
  await page.screenshot({ path: 'example_playwright.png' });
  await browser.close();
})();

8.2 Playwright 点击按钮

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com'); // 替换成你的目标网址
  await page.click('#submit-button'); // 点击id为submit-button的按钮
  await browser.close();
})();

8.3 Playwright 填写表单

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/login'); // 替换成你的登录页面网址
  await page.fill('#username', 'your_username'); // 填写用户名
  await page.fill('#password', 'your_password'); // 填写密码
  await page.click('#login-button'); // 点击登录按钮
  await page.waitForNavigation(); // 等待页面跳转
  await browser.close();
})();

注意,Playwright 使用 page.fill() 替代了 Puppeteer 的 page.type(),其他基本一致。

8.4 Playwright 获取文本内容

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com'); // 替换成你的目标网址
  const title = await page.textContent('title'); // 获取title标签的文本内容
  console.log(title);
  await browser.close();
})();

Playwright 提供了 page.textContent() 简化了文本内容的获取。

总的来说,Playwright 和 Puppeteer 在API设计上有很多相似之处,学习了其中一个,上手另一个也会比较容易。

第九章:结束语

好了,今天的分享就到这里。 希望通过今天的讲解,大家能够对自动化浏览器脚本有一个初步的了解。 PuppeteerPlaywright是两个强大的工具,可以帮助我们完成很多重复性的工作,提高效率。 但是也要注意遵守规则,做一个文明的爬虫。

祝大家学习愉快,编码顺利! 咱们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注