本文通过puppeteer实现对百度图片的抓取,这里简单介绍下puppeteer
puppeteer可以使我们编写一套代码控制浏览器动作,“你可以在浏览器中手动执行的绝大多数操作都可以使用 Puppeteer 来完成”
因此Puppeteer常用于测试和爬虫—官方文档
本项目源码已上传至GitHub
npm i puppeteer bufferutil utf-8-validate optimist
1.引入相关模块和初始配置
//baidu-img.js
const puppeteer = require('puppeteer')
const imgLoad = require('./imgload')
const httpUrl = 'https://image.baidu.com/'
var argv = require('optimist').argv;
let options = {
word:argv.word || '图片',
num:argv.num || 60,
dir:argv.dir || 'images',
delay:argv.delay || 600
}
...
2.图片下载逻辑
//imgload.js
const path = require('path')
const fs = require('fs')
const http = require('http')
const https = require('https')
const {promisify} = require('util')
const writeFile = promisify(fs.writeFile);
module.exports = async (src,dir)=>{
if(/\.(jpg|png|jpeg|gif)$/.test(src)){
await urlToImg(src,dir)
} else {
await base64ToImg(src,dir)
}
}
const urlToImg = (url,dir)=>{
const mod = /^https:/.test(url)?https:http
const ext = path.extname(url)
fs.mkdir(dir,function(err){
if(!err){
console.log('成功创建目录')
}
})
const file = path.join(dir, `${Date.now()}${ext}`)
//请求图片路径下载图片
mod.get(url,res=>{
res.pipe(fs.createWriteStream(file))
.on('finish',()=>{
console.log(file+' download successful');
})
})
}
//base64-download
const base64ToImg = async function(base64Str,dir){
//data:image/jpg;base64,/fdsgfsdgdfghdfdfh
try{
const matches = base64Str.match(/^data:(.+?);base64,(.+)$/)
const ext = matches[1].split('/')[1].replace('jpeg','jpg')//获取后缀
fs.mkdir(dir,function(err){
if(!err){
console.log('成功创建'+dir+'目录')
}
})
const file = path.join(dir, `${Date.now()}.${ext}`)
const content = matches[2]
await writeFile(file,content,'base64')
console.log(file+' download successful');
}catch(e){
console.log(e);
}
}
百度图片有两种各种一种是url的形式另一种是base64的形式,我们封装了两个函数分别处理url和base64
3.抓取图片逻辑
3.1
...
;( async function(){
let config = {
headless:true,//无界面操作 ,false表示有界面
defaultViewport:{
width:1920,
height:1000,
},
}
let browser = await puppeteer.launch(config)
let page = await browser.newPage()
await page.goto(httpUrl)
await page.focus('#kw')
await page.keyboard.sendCharacter(options.word);//搜索词
await page.click('.s_search')
...
我们将所以逻辑封装在自执行的异步函数
这里的部分比较简单,我们只需找到对应的元素,赋予相应的操作即可
当搜索按钮被点击的时候我们监听onLoad事件,进行图片的抓取
3.2
//页面搜索跳转 执行的逻辑
page.on('load',async ()=>{
console.warn('正在为你检索【'+options.word+'】图片请耐心等待...');
await page.evaluate((options)=>{
///获取当前窗口高度 处理懒加载
let height = document.body.offsetHeight
let timer = setInterval(()=>{
//窗口每次滚动当前窗口的2倍
height=height*2
window.scrollTo(0,height);
},2000)
window.onscroll=function(){
let arrs = document.querySelectorAll('.main_img')
//符合指定图片数
if(arrs.length>=options.num){
clearInterval(timer)
console.log(`为你搜索到${arrs.length}张【${options.word}】相关的图片\n准备下载(${options.num})张`);
}
}
},options)
})
由于百度图片使用了懒加载,这里我们通过page.evaluate
使浏览器执行我们自定义的js,在 page.evaluate
我们优雅的处理了懒加载,并监听页面滚动事件,每次滚动的时候计算页面图片的数量,并展示提示信息(console.log)这个打印并不只是打印,后面我们要监听console事件执行图片下载逻辑
3.3
await page.on('console',async msg=>{
console.log(msg.text());
//提取图片的src
let res = await page.$$eval('.main_img',eles=>eles.map((e=>e.getAttribute('src'))))
res.length = options.num
res.forEach(async (item,i)=>{
await page.waitFor(options.delay*i)//延迟执行
await imgLoad(item,options.dir)
})
await browser.close()
})
我们监听浏览器的console事件,当触发console时说明需要的图片已经找到,此时可以执行图片url提取,将其下载,至于为什么不在page.evaluate
执行图片下载逻辑 是因为page.evaluate
只能写“前端”的js图片下载需要用到fs、path等模块,我们在page.evaluate
里面是无法使用的
到此一个小爬虫完成
我们来看看效果