内容简介:handless爬虫主要靠它。它可以模拟用户打开网页的过程,但是并没有打开网页。写过自动化测试的同学应该对这个会比较熟悉,因为用它爬虫的过程跟自动化测试的过程几乎是一样的。基于node的cli命令行工具。利用它,我们可以很方便的写出各种各样的cli命令。交互式命令行工具。什么叫做交互式命令行呢?其实就是类似npm init的时候,问一个问题,我们答一个问题,最后根据答案生成package.json的过程。
handless爬虫主要靠它。它可以模拟用户打开网页的过程,但是并没有打开网页。写过自动化测试的同学应该对这个会比较熟悉,因为用它爬虫的过程跟自动化测试的过程几乎是一样的。
commander
基于node的cli命令行工具。利用它,我们可以很方便的写出各种各样的cli命令。
inquirer
交互式命令行工具。什么叫做交互式命令行呢?其实就是类似npm init的时候,问一个问题,我们答一个问题,最后根据答案生成package.json的过程。
chalk
这个其实就是一个让我们在命令行中输出的文字更加优美的工具。
好了,介绍完了 工具 以后,让我们正式开始我们的项目。
项目介绍
首先,要搞清楚我们想要实现的功能。我们想要实现的功能就是,在 命令行 中输入我们想要下载的图片,然后node去网上爬取我们想要的图片(这里就先去百度图片爬吧),直接下载到本地。以及输入一个命令,可以清空我们输出目录中的图片。
文件目录
|-- Documents
|-- .gitignore
|-- README.md
|-- package.json
|-- bin
| |-- gp
|-- output
| |-- .gitkeeper
|-- src
|-- app.js
|-- clean.js
|-- index.js
|-- config
| |-- default.js
|-- helper
|-- questions.js
|-- regMap.js
|-- srcToImg.js
复制代码
以上是项目用到的一个简单的目录结构
- output 用以存放下载的图片
- bin cli工具会用到的文件
-
src
代码主要存放于此
- index.js 项目入口文件
- app.js 主要功能文件
- clean.js 用于清空图片操作的文件
- config 用于存放一些配置
- helper 用于存放一些辅助方法的文件
开始项目
首先我们看一下app.js。
我们用一个类包裹核心方法,是为了命令行工具可以更方便的调用我们的方法。
这个类很简单, constructor
接收参数, start
开启主要流程。 start
方法是一个async函数,因为 puppeteer
操作浏览器的过程几乎都是异步的。
接着我们用 puppeteer
生成page的实例,利用 goto
方法模拟进入百度图片页面。这时其实就是跟我们真实打开浏览器进入百度图片是一样的,只不过因为我们是handless的,所以我们无法感知打开浏览器的过程。
然后我们需要设置一下浏览器的宽度(想象一下),不能太大,也不能太小。太大会触发百度反爬虫机制,导致我们爬下来的图片是403或者别的错误。太小会导致爬到的图片非常少。
接下去我们聚焦搜索框,输入我们想要搜索的关键字(这个关键字呢就是我们在命令行输入的关键字),然后点击搜索。
等页面加载以后,我们用 page.$$eval
获取页面上所有 class
为 .main_img
的图片(具体规律需要自己去观察),再获取上面的 src
属性后,将 src
转为我们本地的图片。
到这里,app.js的任务就完成了。 很简单吧。
下面是代码。
const puppeteer = require('puppeteer');
const chalk = require('chalk');
const config = require('./config/default');
const srcToImg = require('./helper/srcToImg');
class App {
constructor(conf) {
//有传入的参数既用传入的参数,没有既用默认的参数
this.conf = Object.assign({}, config, conf);
}
async start () {
//用puppeteer生成一个browser的实例
//用browser再生成一个page的实例
const browser = await puppeteer.launch();
const page = await browser.newPage();
//打开搜索引擎,先写死百度
await page.goto(this.conf.searchPath);
console.log(chalk.green(`go to ${this.conf.searchPath}`));
//设置窗口大小,过大会引起反爬虫
await page.setViewport({
width: 1920,
height: 700
});
//搜索文字输入框聚焦
await page.focus('#kw');
//输入要搜索的关键字
await page.keyboard.sendCharacter(this.conf.keyword);
//点击搜索
await page.click('.s_search');
console.log(chalk.green(`get start searching pictures`));
//页面加载后要做的事
page.on('load', async () => {
console.log(chalk.green(`searching pictures done, start fetch...`));
//获取所有指定图片的src
const srcs = await page.$$eval('img.main_img', pictures => {
return pictures.map(img => img.src);
});
console.log(chalk.green(`get ${srcs.length} pictures, start download`));
srcs.forEach(async (src) => {
await page.waitFor(200);
await srcToImg(src, this.conf.outputPath);
});
});
}
};
module.exports = App;
复制代码
接下来我们看一下,如何把图片的src属性转化为我们本地的图片呢?我们看下helper下的srcToImg.js
首先,这个模块主要引入了node的 http
模块、 https
模块、 path
模块和 fs
模块及一些辅助工具,比如正则、将回调函数转化为promise的 promisify
和将输出更好看的 chalk
。
为什么我们要同时引入http和https模块呢?仔细观察百度图片搜索结果中的图片,我们可以发现,既有http的也有https的,所以我们引入两个模块,区分出具体的图片属于哪个就用哪个模块去请求图片。请求了图片以后,我们就用 fs
模块的 createWriteStream
方法,将图片存入我们的 output
目录中。
如果我们仔细观察了百度搜索结果中的图片的src,我们会发现,除了http和https开头的图片,还有base64的图片,所以我们要对base64的图片也做一下处理。
跟普通图片一样的处理,先根据 src
分割出扩展名,再计算出存储的路径和文件名,最后写入调用 fs
模块的 writeFile
方法写入文件(这里就简单的用writeFile了)。
以上,图片就存入本地了。
代码如下。
const http = require('http');
const https = require('https');
const path = require('path');
const fs = require('fs');
const { promisify } = require('util');
const chalk = require('chalk');
const writeFile = promisify(fs.writeFile);
const regMap = require('./regMap');
const urlToImg = promisify((url, dir) => {
let mod;
if(regMap.isHttp.test(url)){
mod = http;
}else if(regMap.isHttps.test(url)){
mod = https;
}
//获取图片的扩展名
const ext = path.extname(url);
//拼接图片存储的路径和扩展名
const file = path.join(dir, `${parseInt(Math.random() * 1000000)}${ext}`);
mod.get(url, res => {
//采用stream的形式,比直接写入更快捷
res.pipe(fs.createWriteStream(file)).on('finish', () => {
console.log(file);
});
});
});
const base64ToImg = async (base64Str, dir) => {
const matchs = base64Str.match(regMap.isBase64);
try {
const ext = matchs[1].split('/')[1].replace('jpeg', 'jpg');
const file = path.join(dir, `${parseInt(Math.random() * 1000000)}.${ext}`);
await writeFile(file, matchs[2], 'base64');
console.log(file);
} catch (error) {
console.log(chalk.red('无法识别的图片'));
}
};
module.exports = (src, dir) => {
if(regMap.isPic.test(src)){
urlToImg(src, dir);
}else{
base64ToImg(src, dir);
}
};
复制代码
我们再看一下如何清空output下的图片呢?这里我们还是用到了 node
的 fs
模块,首先利用 fs.readdir
方法读取 output
文件夹,然后遍历其下的文件,如果是图片,则调用 fs.unlink
方法删除它。也很简单,对吧。
代码如下
const fs = require('fs');
const regMap = require('./helper/regMap');
const config = require('./config/default');
const cleanPath = config.outputPath;
class Clean {
constructor() {}
clean() {
fs.readdir(cleanPath, (err, files) => {
if(err){
throw err;
}
files.forEach(file => {
if(regMap.isPic.test(file)){
const img = `${cleanPath}/${file}`;
fs.unlink(img, (e) => {
if(e) {
throw e;
}
});
}
});
console.log('clean finished');
});
}
};
module.exports = Clean;
复制代码
最后我们看一下如何写cli工具呢?首先我们需要在 bin
目录下新建一个脚本文件 gp
,如下
#! /usr/bin/env node
module.exports = require('../src/index');
复制代码
意思是找到 /usr/bin/env
下的 node
来启动第二行的代码
其次我们需要在package.json里加入一个 bin
对象,对象下属性名是我们命令的名字,属性是 bin
下的脚本文件的路径,如下
"bin": {
"gp": "bin/gp"
}
复制代码
接着我们来看下 index.js
const program = require('commander');
const inquirer = require('inquirer');
const pkg = require('../package.json');
const qs = require('./helper/questions');
const App = require('./app');
const Clean = require('./clean');
program
.version(pkg.version, '-v, --version');
program
.command('search')
.alias('s')
.description('get search pictures what you want.')
.action(async () => {
const answers = await inquirer.prompt(qs.startQuestions);
const app = new App(answers);
await app.start();
});
program
.command('clean')
.alias('c')
.description('clean all pictures in directory "output".')
.action(async () => {
const answers = await inquirer.prompt(qs.confirmClean);
const clean = new Clean();
answers.isRemove && await clean.clean();
});
program.parse(process.argv);
if(process.argv.length < 3){
program.help();
}
复制代码
我们引入 commander
和 inquirer
, program.command
方法是为我们生成命令名的, alias
是该命令的缩写, description
是该命令的描述, action
是该命令要做的事情。
我们首先用 command
生成了两个命令, search
和 clean
,接着可以看到,我们在 action
中用了 inquirer
, inquirer
的提问是一个异步的过程,所以我们也一样用了 async
和 await
, inquirer
接收一个问题数组,里面包含问题的type、name、message和验证方法等,具体的可以参考inquirer的文档。我们这里的问题如下,这里返回了两个数组,一个是用于输入关键字的时候的,一个是用于清空图片时确认的。提问数组中会验证是否有填写关键字,如果没有,则不会继续下一步并提示你该输入关键字,否则就正式开始爬虫流程。删除确认数组就是简单的一个确认,如果确认了,则开始删除图片。最后,用 program.parse
将命令注入到 node
的 process.argv
中,根据命令行有没有输入参数提示help信息。
至此,我们的程序大功告成。接下去我们只要将我们的程序发布到npm里,就可以让其他人下载来使用了~npm的发布我们这里就不再赘述啦,不清楚的同学网上随便搜一下就ok啦。
src/helper/questions.js
如下
const config = require('../config/default');
exports.startQuestions = [
{
type: 'input',
name: 'keyword',
message: 'What pictures do yo want to get ?',
validate: function(keyword) {
const done = this.async();
if(keyword === ''){
done('Please enter the keyword to get pictures');
return;
}
done(null, true);
}
}
];
exports.confirmClean = [
{
type: 'confirm',
name: 'isRemove',
message: `Do you want to remove all pictures in ${config.outputPath} ?`,
default: true,
}
];
复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Django 1.0 Template Development
Scott Newman / Packt / 2008 / 24.99
Django is a high-level Python web application framework designed to support the rapid development of dynamic websites, web applications, and web services. Getting the most out of its template system a......一起来看看 《Django 1.0 Template Development》 这本书的介绍吧!