JS `headless browser` (无头浏览器):自动化测试与网络爬虫高级应用

各位观众老爷,大家好!今天咱们聊聊一个听起来有点“高冷”,但用起来绝对“真香”的技术:JS headless browser,也就是 JavaScript 无头浏览器。

啥是无头浏览器?

简单来说,无头浏览器就像一个没有显示器的浏览器。它拥有浏览器的所有功能,比如解析 HTML、执行 JavaScript、渲染页面等等,但你看不到它干活的样子。想象一下,你让一个幽灵偷偷帮你上网,干完活悄悄溜走,这就是无头浏览器。

为啥要用无头浏览器?

你可能会问,既然看不到,那要它有啥用?别急,它的用处可大了,主要体现在以下两个方面:

  1. 自动化测试: 告别手动点击,让测试脚本像打了鸡血一样自动跑起来,解放你的双手,让你有更多时间摸鱼…啊不,是思考人生。
  2. 网络爬虫: 有些网站的数据可不是简单地用 requests 就能拿到的,需要 JavaScript 动态渲染。这时,无头浏览器就派上用场了,它可以模拟用户行为,拿到最终渲染后的页面,轻松抓取数据。

主流选手:Playwright, Puppeteer, Selenium

市面上无头浏览器框架不少,但最受欢迎的莫过于这三位:

  • Playwright: 微软出品,支持 Chromium、Firefox、WebKit 三大内核,API 简洁易懂,跨平台能力强,简直是“六边形战士”。
  • Puppeteer: Google Chrome 团队维护,专注于 Chromium 内核,性能优秀,社区活跃,是爬虫和测试的得力助手。
  • Selenium: 老牌自动化测试框架,支持多种浏览器,生态完善,但配置相对复杂,性能略逊于前两者。

今天我们重点介绍 Playwright,因为它实在太香了!

Playwright 入门:Hello World!

首先,你需要安装 Playwright:

npm install -D @playwright/test
npx playwright install

安装完成后,我们来写一个最简单的例子,打开 Google 首页并截图:

// playwright.config.js
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
  use: {
    headless: true, // 默认无头模式
  },
};
export default config;
// example.spec.js
const { test, expect } = require('@playwright/test');

test('opens Google and takes a screenshot', async ({ page }) => {
  await page.goto('https://www.google.com');
  await page.screenshot({ path: 'google.png' });
  await expect(page.locator('input[name="q"]')).toBeVisible(); // 添加一个断言
});

运行测试:

npx playwright test

如果一切顺利,你会在项目根目录下看到一张名为 google.png 的截图,上面是 Google 首页。是不是很简单?

Playwright 常用 API:玩转浏览器

Playwright 提供了丰富的 API,可以模拟各种用户行为,下面是一些常用的:

  • page.goto(url): 打开指定 URL。
  • page.click(selector): 点击指定元素。
  • page.fill(selector, value): 在指定输入框中输入内容。
  • page.locator(selector): 定位元素,返回一个 Locator 对象,可以链式调用各种方法。
  • page.textContent(selector): 获取指定元素的文本内容。
  • page.inputValue(selector): 获取指定输入框的值。
  • page.screenshot(options): 截屏。
  • page.waitForSelector(selector): 等待指定元素出现。
  • page.evaluate(function): 在浏览器环境中执行 JavaScript 代码。

举个例子,模拟登录 GitHub:

const { test, expect } = require('@playwright/test');

test('logs in to GitHub', async ({ page }) => {
  await page.goto('https://github.com/login');
  await page.fill('#login_field', 'your_username');  // 替换为你的用户名
  await page.fill('#password', 'your_password');  // 替换为你的密码
  await page.click('input[name="commit"]');

  await page.waitForSelector('.js-cookie-consent-reject');
  await page.click('.js-cookie-consent-reject');

  await expect(page).toHaveURL(/github.com/); // 简单的URL断言
});

Playwright 高级技巧:让爬虫更强大

  1. 处理动态内容:

    有些网站的内容是 JavaScript 动态生成的,直接抓取 HTML 源码是拿不到的。这时,我们需要让 Playwright 先加载页面,执行 JavaScript,然后才能拿到最终渲染后的内容。

    const { test, expect } = require('@playwright/test');
    
    test('fetches dynamic content', async ({ page }) => {
      await page.goto('https://example.com/dynamic-content'); // 假设这是一个动态内容的网站
      await page.waitForSelector('#content'); // 等待内容加载完成
      const content = await page.textContent('#content');
      console.log(content);
    });
  2. 模拟用户行为:

    有些网站会检测是否是机器人访问,为了避免被识别,我们可以模拟一些用户行为,比如:

    • 设置 User-Agent
    • 随机延迟
    • 模拟鼠标移动
    • 模拟滚动页面
    const { test, expect } = require('@playwright/test');
    
    test('simulates user behavior', async ({ page }) => {
      await page.setViewportSize({ width: 1920, height: 1080 }); // 设置窗口大小
      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.goto('https://example.com');
    
      await page.mouse.move(100, 100); // 模拟鼠标移动
      await page.waitForTimeout(1000); // 延迟 1 秒
      await page.mouse.move(200, 200);
      await page.waitForTimeout(500);
      await page.mouse.wheel(0, 100); // 模拟滚动页面
    });
  3. 处理验证码:

    验证码是爬虫的一大难题。常见的解决方案有:

    • 手动识别: 如果验证码比较简单,可以手动识别并输入。
    • 使用第三方服务: 有一些第三方服务可以识别验证码,比如阿里云、腾讯云等。
    • 绕过验证码: 有些网站的验证码可以通过一些技巧绕过,比如利用 Cookie、Referer 等。

    这里我们演示一下手动识别验证码:

    const { test, expect } = require('@playwright/test');
    const readline = require('readline').createInterface({
      input: process.stdin,
      output: process.stdout,
    });
    
    function askQuestion(query) {
      return new Promise(resolve => {
        readline.question(query, answer => {
          resolve(answer);
        });
      });
    }
    
    test('handles captcha', async ({ page }) => {
      await page.goto('https://example.com/captcha'); // 假设这是一个需要验证码的网站
      await page.screenshot({ path: 'captcha.png' }); // 截取验证码图片
    
      const captchaCode = await askQuestion('Please enter the captcha code: '); // 提示用户输入验证码
      readline.close();
    
      await page.fill('#captcha', captchaCode);
      await page.click('#submit');
    
      // 断言是否成功
      await expect(page.locator('#success-message')).toBeVisible({timeout: 10000});
    });

    注意: 这只是一个简单的示例,实际情况可能更复杂。

  4. 使用代理 IP:

    为了避免 IP 被封,可以使用代理 IP。Playwright 支持设置代理 IP:

    const { test, expect } = require('@playwright/test');
    
    test('uses proxy', async ({ browser }) => {
      const context = await browser.newContext({
        proxy: {
          server: 'http://your_proxy_ip:your_proxy_port', // 替换为你的代理 IP 和端口
          username: 'your_proxy_username', // 可选,如果代理需要用户名密码
          password: 'your_proxy_password', // 可选,如果代理需要用户名密码
        },
      });
    
      const page = await context.newPage();
      await page.goto('https://www.example.com');
      console.log(await page.content());
    
      await context.close();
    });
  5. 并发抓取:

    为了提高抓取效率,可以使用并发抓取。Playwright 支持创建多个页面(Page)或浏览器上下文(BrowserContext)并发执行。

    const { chromium } = require('playwright');
    
    async function scrapeData(url) {
      const browser = await chromium.launch();
      const page = await browser.newPage();
      await page.goto(url);
      const title = await page.title();
      console.log(`Title of ${url}: ${title}`);
      await browser.close();
    }
    
    async function main() {
      const urls = [
        'https://www.example.com',
        'https://www.google.com',
        'https://www.baidu.com',
      ];
    
      const promises = urls.map(url => scrapeData(url));
      await Promise.all(promises); // 并发执行所有抓取任务
    }
    
    main();

    注意: 并发抓取需要控制并发数量,避免给目标网站造成过大压力。

表格总结:Playwright 常用 API

API 描述 示例
page.goto(url) 打开指定 URL await page.goto('https://www.google.com');
page.click(selector) 点击指定元素 await page.click('#submit-button');
page.fill(selector, value) 在指定输入框中输入内容 await page.fill('#username', 'myusername');
page.locator(selector) 定位元素,返回一个 Locator 对象 const element = page.locator('#my-element');
page.textContent(selector) 获取指定元素的文本内容 const text = await page.textContent('#my-element');
page.inputValue(selector) 获取指定输入框的值 const value = await page.inputValue('#my-input');
page.screenshot(options) 截屏 await page.screenshot({ path: 'screenshot.png' });
page.waitForSelector(selector) 等待指定元素出现 await page.waitForSelector('#my-element');
page.evaluate(function) 在浏览器环境中执行 JavaScript 代码 const result = await page.evaluate(() => document.title);
page.selectOption(selector, value) 选择下拉选项(value为 option 的 value) await page.selectOption('select#choose-color', {value:'red'});
page.hover(selector) 鼠标悬停在指定元素上 await page.hover('button#submit');
page.dblclick(selector) 双击指定元素 await page.dblclick('div#special-area');
page.focus(selector) 聚焦指定元素 await page.focus('input#search-box');
page.keyboard.press(key) 模拟键盘输入, key 可以是 ‘Enter’, ‘ArrowLeft’, ‘A’ 等 await page.keyboard.press('Enter');
page.evaluateHandle(pageFunction) 在浏览器环境中执行 JavaScript 代码, 返回一个 JSHandle 对象 const elementHandle = await page.evaluateHandle(() => document.body);

总结

JS 无头浏览器是一个强大的工具,可以应用于自动化测试、网络爬虫等多个领域。Playwright 作为其中的佼佼者,凭借其简洁的 API、强大的功能和跨平台能力,越来越受到开发者的青睐。掌握 Playwright,你就可以轻松驾驭各种复杂的 Web 场景,让你的工作效率更上一层楼。

希望今天的分享对大家有所帮助。记住,技术是用来解决问题的,多动手实践,才能真正掌握它。下次有机会再和大家分享更多有趣的编程知识!

散会!

发表回复

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