Udemy Black Friday Sale — Thousands of Web Development & Software Development courses are on sale for only $10 for a limited time! Full details and course recommendations can be found here.javascript
本文将会教你如何用 JavaScript 自动化 web 爬虫,技术上用到了 Google 团队开发的 Puppeteer。 Puppeteer 运行在 Node 环境,能够用来操做 headless Chrome。何谓 Headless Chrome?通俗来说就是在不打开 Chrome 浏览器的状况下使用提供的 API 模拟用户的浏览行为。前端
若是你仍是不理解,你能够想象成使用 JavaScript 全自动化操做 Chrome 浏览器。java
先确保你已经安装了 Node 8 及以上的版本,没有的话,能够先到 官网 里下载安装。注意,必定要选“Current”处显示的版本号大于 8 的。node
若是你是第一次接触 Node,最好先看一下入门教程:Learn Node JS — The 3 Best Online Node JS Courses.android
安装好 Node 以后,建立一个项目文件夹,而后安装 Puppeteer。安装 Puppeteer 的过程当中会附带下载匹配版本的 Chromium(译者注:国内网络环境可能会出现安装失败的问题,能够设置环境变量 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = 1
跳过下载,反作用是每次使用 launch
方法时,须要手动指定浏览器的执行路径):ios
npm install --save puppeteer
复制代码
Puppeteer 安装好以后,咱们就能够开始写一个简单的例子。这个例子直接照搬自官方文档,它能够对给定的网站进行截图。git
首先建立一个 js 文件,名字随便起,这里咱们用 test.js
做为示例,输入如下代码:github
const puppeteer = require('puppeteer');
async function getPic() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://google.com');
await page.screenshot({path: 'google.png'});
await browser.close();
}
getPic();
复制代码
下面咱们来逐行分析上面的代码。web
getPic()
方法。细心的读者会发现,getPic()
前面有个 async
前缀,它表示 getPic()
方法是个异步方法。async
和 await
成对出现,属于 ES 2017 新特性。介于它是个异步方法,因此调用以后返回的是 Promise
对象。当 async
方法返回值时,对应的 Promise
对象会将这个值传递给 resolve
(若是抛出异常,那么会将错误信息传递给 Reject
)。chrome
在 async
方法中,可使用 await
表达式暂停方法的执行,直到表达式里的 Promise
对象彻底解析以后再继续向下执行。看不懂不要紧,后面我再详细讲解,到时候你就明白了。
接下来,咱们将会深刻分析 getPic()
方法:
const browser = await puppeteer.launch();
复制代码
这段代码用于启动 puppeteer,实质上打开了一个 Chrome 的实例,而后将这个实例对象赋给变量 browser
。由于使用了 await
关键字,代码运行到这里会阻塞(暂停),直到 Promise
解析完毕(不管执行结果是否成功)
const page = await browser.newPage();
复制代码
接下来,在上文获取到的浏览器实例中新建一个页面,等到其返回以后将新建的页面对象赋给变量 page
。
await page.goto('https://google.com');
复制代码
使用上文获取到的 page
对象,用它来加载咱们给的 URL 地址,随后代码暂停执行,等待页面加载完毕。
await page.screenshot({path: 'google.png'});
复制代码
等到页面加载完成以后,就能够对页面进行截图了。screenshot()
方法接受一个对象参数,能够用来配置截图保存的路径。注意,不要忘了加上 await
关键字。
await browser.close();
复制代码
最后,关闭浏览器。
在命令行输入如下命令执行示例代码:
node test.js
复制代码
如下是示例里的截图结果:
是否是很厉害?这只是热身,下面教你怎么在非 headless 环境下运行代码。
非 headless?百闻不如一见,本身先动手试一下吧,把第 4 行的代码:
const browser = await puppeteer.launch();
复制代码
换成这句:
const browser = await puppeteer.launch({headless: false});
复制代码
而后再次运行:
node test.js
复制代码
是否是更炫酷了?当配置了 {headless: false}
以后,就能够直观的看到代码是怎么操控 Chrome 浏览器的。
这里还有一个小问题,以前咱们的截图有点没截完整的感受,那是由于 page
对象默认的截屏尺寸有点小的缘故,咱们能够经过下面的代码从新设置 page
的视口大小,而后再截取:
await page.setViewport({width: 1000, height: 500})
复制代码
这下就好多了:
最终代码以下:
const puppeteer = require('puppeteer');
async function getPic() {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto('https://google.com');
await page.setViewport({width: 1000, height: 500})
await page.screenshot({path: 'google.png'});
await browser.close();
}
getPic();
复制代码
经过上面的例子,你应该掌握了 Puppeteer 的基本用法,下面再来看一个稍微复杂点的例子。
开始前,不妨先看看 官方文档。你会发现 Puppeteer 能干不少事,像是模拟鼠标的点击、填充表单数据、输入文字、读取页面数据等。
在接下来的教程里,咱们将爬一个叫 Books To Scrape 的网站,这个网站是专门用来给开发者作爬虫练习用的。
仍是在以前建立的文件夹里,新建一个 js 文件,这里用 scrape.js
做为示例,而后输入如下代码:
const puppeteer = require('puppeteer');
let scrape = async () => {
// Actual Scraping goes Here...
// Return a value
};
scrape().then((value) => {
console.log(value); // Success!
});
复制代码
有了上一个例子的经验,这段代码要看懂应该不难。若是你仍是看不懂的话......那也没啥问题就是了。
首先,仍是引入 puppeteer
依赖,而后定义一个 scrape()
方法,用来写爬虫代码。这个方法返回一个值,到时候咱们会处理这个返回值(示例代码是直接打印出这个值)
先在 scrape 方法中添加下面这一行测试一下:
let scrape = async () => {
return 'test';
};
复制代码
在命令行输入 node scrape.js
,不出问题的话,控制台会打印一个 test
字符串。测试经过后,咱们来继续完善 scrape
方法。
步骤 1:前期准备
和例 1 同样,先获取浏览器实例,再新建一个页面,而后加载 URL:
let scrape = async () => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto('http://books.toscrape.com/');
await page.waitFor(1000);
// Scrape
browser.close();
return result;
};
复制代码
再来分析一下上面的代码:
首先,咱们建立了一个浏览器实例,将 headless
设置为 false
,这样就能直接看到浏览器的操做过程:
const browser = await puppeteer.launch({headless: false});
复制代码
而后建立一个新标签页:
const page = await browser.newPage();
复制代码
访问 books.toscrape.com
:
await page.goto('http://books.toscrape.com/');
复制代码
下面这一步可选,让代码暂停执行 1 秒,保证页面能彻底加载完毕:
await page.waitFor(1000);
复制代码
任务完成以后关闭浏览器,返回执行结果。
browser.close();
return result;
复制代码
步骤 1 结束。
步骤 2: 开爬
打开 Books to Scrape 网站以后,想必你也发现了,这里面有海量的书籍,只是数据都是假的而已。先从简单的开始,咱们先抓取页面里第一本书的数据,返回它的标题和价格信息(红色边框选中的那本)。
查一下文档,注意到这个方法能模拟页面点击:
page.click(selector[, options])
selector
选择器,定位须要进行点击的元素,若是有多个元素匹配,以第一个为准。这里可使用开发者工具查看元素的选择器,在图片上右击选中 inspect:
上面的操做会打开开发者工具栏,以前选中的元素也会被高亮显示,这个时候点击前面的三个小点,选择 copy - copy selector:
有了元素的选择器以后,再加上以前查到的元素点击方法,获得以下代码:
await page.click('#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > div.image_container > a > img');
复制代码
而后就会观察到浏览器点击了第一本书的图片,页面也会跳转到详情页。
在详情页里,咱们只关心书的标题和价格信息 —— 见图中红框标注。
为了获取这些数据,须要用到 page.evaluate()
方法。这个方法能够用来执行浏览器内置 DOM API ,例如 querySelector()
。
首先建立 page.evaluate()
方法,将其返回值保存在 result
变量中:
const result = await page.evaluate(() => {
// return something
});
复制代码
一样,要在方法里选择咱们要用到的元素,再次打开开发者工具,选择须要 inspect 的元素:
标题是个简单的 h1
元素,使用下面的代码获取:
let title = document.querySelector('h1');
复制代码
其实咱们须要的只是元素里的文字部分,能够在后面加上 .innerText
,代码以下:
let title = document.querySelector('h1').innerText;
复制代码
获取价格信息同理:
恰好价格元素上有个 price_color
class,能够用这个 class 做为选择器获取到价格对应的元素:
let price = document.querySelector('.price_color').innerText;
复制代码
这样,标题和价格都有了,把它们放到一个对象里返回:
return {
title,
price
}
复制代码
回顾刚才的操做,咱们获取到了标题和价格信息,将它们保存在一个对象里返回,返回结果赋给 result
变量。因此,如今你的代码应该是这样:
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let price = document.querySelector('.price_color').innerText;
return {
title,
price
}
});
复制代码
而后只须要将 result
返回便可,返回结果会打印到控制台:
return result;
复制代码
最后,综合起来代码以下:
const puppeteer = require('puppeteer');
let scrape = async () => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto('http://books.toscrape.com/');
await page.click('#default > div > div > div > div > section > div:nth-child(2) > ol > li:nth-child(1) > article > div.image_container > a > img');
await page.waitFor(1000);
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let price = document.querySelector('.price_color').innerText;
return {
title,
price
}
});
browser.close();
return result;
};
scrape().then((value) => {
console.log(value); // Success!
});
复制代码
在控制台运行代码:
node scrape.js
// { title: 'A Light in the Attic', price: '£51.77' }
复制代码
操做正确的话,在控制台会看到正确的输出结果,到此为止,你已经完成了 web 爬虫。
稍加思考一下你会发现,标题和价格信息是直接展现在首页的,因此,彻底不必进入详情页去抓取这些数据。既然这样,不妨再进一步思考,可否抓取全部书的标题和价格信息?
因此,抓取的方式其实有不少,须要你本身去发现。另外,上面提到的直接在主页抓取数据也不必定可行,由于有些标题可能会显示不全。
拔高题
目标 —— 抓取主页全部书籍的标题和价格信息,而且用数组的形式保存返回。正确的输出应该是这样:
开干吧,伙计,其实实现起来和上面的例子相差无几,若是你以为实在太难,能够参考下面的提示。
提示:
其实最大的区别在于你须要遍历整个结果集,代码的大体结构以下:
const result = await page.evaluate(() => {
let data = []; // 建立一个空数组
let elements = document.querySelectorAll('xxx'); // 选择全部相关元素
// 遍历全部的元素
// 提取标题信息
// 提取价格信息
data.push({title, price}); // 将数据插入到数组中
return data; // 返回数据集
});
复制代码
若是提示了仍是作不出来的话,好吧,如下是参考答案。在之后的教程中,我会在下面这段代码的基础上再作一些拓展,同时也会涉及一些更高级的爬虫技术。你能够在 这里 提交你的邮箱地址进行订阅,有新的内容更新时咱们会通知你。
参考答案:
const puppeteer = require('puppeteer');
let scrape = async () => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto('http://books.toscrape.com/');
const result = await page.evaluate(() => {
let data = []; // 建立一个数组保存结果
let elements = document.querySelectorAll('.product_pod'); // 选择全部书籍
for (var element of elements){ // 遍历书籍列表
let title = element.childNodes[5].innerText; // 提取标题信息
let price = element.childNodes[7].children[0].innerText; // 提取价格信息
data.push({title, price}); // 组合数据放入数组
}
return data; // 返回数据集
});
browser.close();
return result; // 返回数据
};
scrape().then((value) => {
console.log(value); // 打印结果
});
复制代码
谢谢观看!若是你有学习 NodeJS 的意向,能够移步 Learn Node JS — The 3 Best Online Node JS Courses。
每周我都会发布 4 篇有关 web 开发的技术文章,欢迎订阅!或者你也能够在 Twitter 上 关注我
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。