各位观众老爷,大家好!今天咱们聊聊一个听起来有点“高冷”,但用起来绝对“真香”的技术:JS headless browser,也就是 JavaScript 无头浏览器。
啥是无头浏览器?
简单来说,无头浏览器就像一个没有显示器的浏览器。它拥有浏览器的所有功能,比如解析 HTML、执行 JavaScript、渲染页面等等,但你看不到它干活的样子。想象一下,你让一个幽灵偷偷帮你上网,干完活悄悄溜走,这就是无头浏览器。
为啥要用无头浏览器?
你可能会问,既然看不到,那要它有啥用?别急,它的用处可大了,主要体现在以下两个方面:
- 自动化测试: 告别手动点击,让测试脚本像打了鸡血一样自动跑起来,解放你的双手,让你有更多时间摸鱼…啊不,是思考人生。
- 网络爬虫: 有些网站的数据可不是简单地用
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 高级技巧:让爬虫更强大
-
处理动态内容:
有些网站的内容是 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); });
-
模拟用户行为:
有些网站会检测是否是机器人访问,为了避免被识别,我们可以模拟一些用户行为,比如:
- 设置 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); // 模拟滚动页面 });
-
处理验证码:
验证码是爬虫的一大难题。常见的解决方案有:
- 手动识别: 如果验证码比较简单,可以手动识别并输入。
- 使用第三方服务: 有一些第三方服务可以识别验证码,比如阿里云、腾讯云等。
- 绕过验证码: 有些网站的验证码可以通过一些技巧绕过,比如利用 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}); });
注意: 这只是一个简单的示例,实际情况可能更复杂。
-
使用代理 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(); });
-
并发抓取:
为了提高抓取效率,可以使用并发抓取。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 场景,让你的工作效率更上一层楼。
希望今天的分享对大家有所帮助。记住,技术是用来解决问题的,多动手实践,才能真正掌握它。下次有机会再和大家分享更多有趣的编程知识!
散会!