各位朋友,各位同行,大家好!我是老码,今天咱们来聊聊自动化浏览器脚本,也就是用Puppeteer
和Playwright
这两位大咖,模拟用户行为,顺便抓点数据的故事。这玩意儿,说白了,就是让机器替咱们干一些重复性的网页操作,比如自动登录、填写表单、点击按钮,甚至还可以把网页上的数据扒下来,简直是懒人福音,效率神器!
第一章:浏览器自动化,从“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(); // 关闭浏览器
})();
这段代码的意思很简单:
- 引入
puppeteer
库。 - 启动一个浏览器实例。
- 创建一个新的页面。
- 让页面跳转到
https://www.example.com
。 - 把页面截图保存为
example.png
。 - 关闭浏览器。
运行这段代码,你就会在当前目录下看到一张名为example.png
的图片,内容就是https://www.example.com
这个网页的截图。
是不是很简单? 这就相当于咱们跟浏览器打了个招呼:“Hello World,给我截个图!”
第二章:Puppeteer
vs Playwright
:双雄争霸,各有千秋
Puppeteer
和Playwright
都是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 保持登录状态
如果需要抓取需要登录才能访问的页面,我们需要保持登录状态。 通常的做法是:
- 先登录一次,获取cookie。
- 把cookie保存下来。
- 下次访问时,把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设计上有很多相似之处,学习了其中一个,上手另一个也会比较容易。
第九章:结束语
好了,今天的分享就到这里。 希望通过今天的讲解,大家能够对自动化浏览器脚本有一个初步的了解。 Puppeteer
和Playwright
是两个强大的工具,可以帮助我们完成很多重复性的工作,提高效率。 但是也要注意遵守规则,做一个文明的爬虫。
祝大家学习愉快,编码顺利! 咱们下次再见!