导言:对于大多数前端开发者而言,谈到命令行工具,你们确定都用过。可是谈到开发命令行工具,估计就没几人有了解了。本文旨在用最短的时间内,帮您开发一个实用(斜眼笑)的图片爬虫命令行应用。javascript
想追求更好的阅读体验,请移步拓跋的前端客栈。同时把项目地址放在显眼的位置css
Puppeteer 是 Google Chrome 团队官方的无界面(Headless)Chrome 工具。Chrome 做为浏览器市场的领头羊,Chrome Headless 将成为 web 应用 自动化测试 的行业标杆。因此咱们颇有必要来了解一下它。前端
Puppeteer 能够作的事情有不少,包括但不限于:java
安装 Puppeteer 很简单,以下: npm i --save puppeteer
or yarn add puppeteer
node
须要注意的是,因为用到了 ES7 的 async/await 语法 ,node 版本最好是 v7.6.0 或以上。git
因为本文不是专门讲 Puppeteer 的文章,故这部分暂且略过,你们能够去看下面的连接学习。github
Puppeteer Api Docchrome
Puppeteer 中文 Api Docshell
说了这么多,Puppeteer 与咱们要开发的命令行应用有什么关系呢?咱们准备制做一个抓图命令行工具,不使用传统的请求式爬虫,咱们使用 Puppeteer 这种无头浏览器,从 DOM 里抓图,这样能有效规避部分爬虫防护手段。
直接上代码,很好理解:
const puppeteer = require("puppeteer");
const getScreenShot = async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto("https://baidu.com");
await page.screenshot({ path: "baidu.png" });
await browser.close();
};
getScreenShot();
复制代码
这段代码的意思就是以 headless(无头)模式打开浏览器,而后打开一个新标签页,跳转到百度网址, 而且进行屏幕截图,保存为 baidu.png 为名的图片,最后关闭浏览器。
执行结果以下。
接下来学习如何用 Puppeteer 抓取网站信息了。
此次咱们来抓取 jd 书单信息。
// book info spider
const puppeteer = require("puppeteer");
const fs = require("fs");
const spider = async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://search.jd.com/Search?keyword=javascript");
const result = await page.evaluate(() => {
let elements = document.querySelectorAll(".gl-item");
const data = [...elements].map(i => {
return {
name: i.querySelector(".p-name em").innerText,
description: i.querySelector(".p-name i").innerText,
price: i.querySelector(".p-price").innerText,
shop: i.querySelector(".p-shopnum").innerText,
url: i.querySelector(".p-img a").href
};
});
return data; // 返回数据
});
browser.close();
return result;
};
spider().then(value => {
fs.writeFile(`${__dirname}/javascript.json`, JSON.stringify(value), err => {
if (err) {
throw err;
}
console.log("file saved!");
});
console.log(value); // Success!
});
复制代码
咱们作的就是跳转到关键字是 javascript 的页面,而后对页面的 dom 结构进行分析,找到图书列表所对应的书名、描述、价格、出版社、网页连接信息,而后把数据写入到 javascript.json 文件里去,方便咱们保存浏览。
逻辑很简单。这已是一个爬虫的雏形了,最后获得以下图所示的 json 文件,很是给力。
图片爬虫,这就是咱们要作的命令行应用的主题了。
一个最基本的思路是这样的:
打开浏览器 —> 跳转到百度图片 —> 获取 input 框的焦点 —> 输入 keywords —> 点击搜索按钮 —> 跳转至结果列表页 —> 下拉到底部 —> 操做 dom,获取全部图片的 src 备用 —> 根据 src 将对应图片保存到本地 —> 关闭浏览器
代码实现之:
首先是浏览器操做部分
const browser = await puppeteer.launch(); // 打开浏览器
const page = await browser.newPage(); // 打开新tab页
await page.goto("https://image.baidu.com"); // 跳转到百度图片
console.log("go to https://image.baidu.com"); // 获取input框的焦点
await page.focus("#kw"); // 把焦点定位到搜索input框
await page.keyboard.sendCharacter("猫咪"); // 输入关键字
await page.click(".s_search"); // 点击搜索按钮
console.log("go to search list"); // 提示跳转到搜索列表页
复制代码
而后是图片处理部分
page.on("load", async () => {
await autoScroll(page); // 向下滚动加载图片
console.log("page loading done, start fetch...");
const srcs = await page.evaluate(() => {
const images = document.querySelectorAll("img.main_img");
return Array.prototype.map.call(images, img => img.src);
}); // 获取全部img的src
console.log(`get ${srcs.length} images, start download`);
for (let i = 0; i < srcs.length; i++) {
await convert2Img(srcs[i], target);
console.log(`finished ${i + 1}/${srcs.length} images`);
} // 保存图片
console.log(`job finished!`);
await browser.close();
});
复制代码
由于百度图片是往下滚动就能够继续懒加载。所以,咱们想要加载更多图片,能够先往下滚动一下子。而后经过分析 dom 结构来获取列表里全部图片的 src,最后进行下载。
执行如下,就能获得一系列猫咪的图片:
图片下载的地方只写了主函数,更详细的代码能够去参见github.
至此,咱们用 Node 和 Puppeteer 开发出了一个最基本的图片爬虫工具。
这个图片爬虫工具目前还有点 low 啊,咱们的目标是要开发一个交互式的命令行应用,确定不能止于此。有哪些能够进一步优化的点呢?通过简单的思考,我列了一下:
Commander 是一款重量轻,表现力和强大的命令行框架。提供了用户命令行输入和参数解析强大功能。
const program = require("commander");
program
.version("0.0.1")
.description("a test cli program")
.option("-n, --name <name>", "your name", "zhl")
.option("-a, --age <age>", "your age", "22")
.option("-e, --enjoy [enjoy]")
.action(option => {
console.log('name: ', option.name);
console.log('age: ', option.age);
console.log('enjoy: ', option.enjoy);
});
program.parse(process.argv);
复制代码
Commander十分容易上手,上面这一段代码仅用了寥寥数行,就定义了一个命令行的输入与输出。其中:
option("-n, --name <name>", "your name", "GK")
,第一项是传参的值,-n是简写形式,--name是全称形式, 表示输入的参数,<>是必填项,若是是[],则是选填项。第二项“your name"是求助help时的提示信息,告诉用户应该输入的内容,最后一项"GK"是默认值。要查询更详细的api,请参考Commander Api文档。
执行一下上述脚本,能够获得:
这样命令行就能够作到简单的交互效果了。可是有没有以为不够好看呢,别急,继续往下看。
inquirer能够为Node制做可嵌入式的美观的命令行界面。
能够提供问答式的命令输入:
能够提供多种形式的选择界面:
能够对输入信息进行校验:
最后能够对输入信息进行处理:
上面的例子是inquirer的官方例子,能够参考pizza.js
inquirer的文档能够查看inquirer documents
有了inquirer,咱们就能够制做更为精美的交互式命令行工具了。
chalk的语法很是简单:
const chalk = require('chalk');
const log = console.log;
// Combine styled and normal strings
log(chalk.blue('Hello') + ' World' + chalk.red('!'));
// Compose multiple styles using the chainable API
log(chalk.blue.bgRed.bold('Hello world!'));
// Pass in multiple arguments
log(chalk.blue('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz'));
// Nest styles
log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!'));
// Nest styles of the same type even (color, underline, background)
log(chalk.green(
'I am a green line ' +
chalk.blue.underline.bold('with a blue substring') +
' that becomes green again!'
));
复制代码
能够输出以下信息,一看便懂:
想必有人看到过下面知乎的控制台效果,既然要作点有意思的事情,今天咱们不妨也把这种效果加到命令行程序里面,提高一下逼格。
首先咱们准备一副ASCII码用来打印,各位能够自行搜索text转ASCII,网上的转化方案不要太多。咱们准备制做的命令行image spider就制做一个IMG SPD的ASCII码字符串吧~
通过挑选,效果如图:
这种复杂的字符串怎么打印出来呢?直接保存为string必定是不行的,格式会乱的一塌糊涂。
想要能完整的打印出格式来,有一个取巧的方法,以注释的形式打印出来。什么能保存注释呢?~~ function。
因此事情就简单到了打印一个function。可是直接打印函数仍是不行的,这时候就用到了能够怼天怼地的toString()方法,咱们只须要把注释中间的部分用正则匹配出来就好了,easy~
最后看一看效果,铛铛铛铛~
这里使用一种叫作Shebang的技术。
Shebang(也称为 Hashbang )是一个由#和!构成的字符序列 #! ,其出如今文本文件的第一行的前两个字符。 在文件中存在 Shebang 的状况下,类 Unix 操做系统的程序加载器会分析 Shebang 后的内容,将这些内容做为解释器指令,并调用该指令,并将载有 Shebang 的文件路径做为该解释器的参数。
node下咱们使用#! /usr/bin/env node便可
这时候咱们即可以取消文件的扩展名.js了。
package.json里面配置
"bin": {
"img-spd": "app"
},
复制代码
执行npm link,它将会把img-spd这个字段复制到npm的全局模块安装文件夹node_modules内,并建立符号连接(symbolic link,软连接),也就是将 app 的路径加入环境变量 PATH。
这时,在任意目录下,直接命令行输入img-spd便可运行此命令行
至此,要改进的地方已经所有修改完毕,快来看看咱们的成品吧~
看着一整个文件夹的gakki,感受满满的幸福要溢出来了
最后用动图来展现一下:
npm install -g img-spd
复制代码
img-spd
复制代码
or
Usage: img-spd [options]
img-spd is a spider get images from image.baidu.com
Options:
-v --version output the version number
-k, --key [key] input the image keywords to download
-i, --interval [interval] input the operation interval(ms,default 200)
-n, --number [number] input the operation interval(ms,default 200)
-m, --headless [headless] choose whether the program is running in headless mode
-h, --help output usage information
复制代码