说到爬虫,很多人都认为是很高大上的东西。哇塞,是不是可以爬妹纸图啊,是不是可以爬小片片啊。答案就是对的。爬虫可以完成这些东西的操作。但是,作为一个正直的程序员,我们要在法律允许范围内用爬虫来为我们服务,而不是为所欲为。(ps:此处应有掌声,谢谢。)
今天,我带来一个用Node.js写的爬虫。一说到教程呢,可能大多数人认为比较枯燥无味。那这样好了,我教大家爬妹纸图,上干货:
是不是瞬间有了动力了?
说到爬虫呢,其实从客观上来说,“所有网站皆可爬”。互联网的内容都是人写出来的,而且都是偷懒写出来的(不会第一页是a,下一页是8),所以肯定有规律,这就给人有了爬取的可能,可以说,天下没有不能爬的网站。而且即使网站不同,但是原理都类似,大部分爬虫都是从 发送请求——>获得页面——>解析页面——>下载内容——>储存内容 这样的流程来进行,只是用的工具不同,可能你用python,我用Node,他用PHP,但是思路也是与上面相同。
既然是用node完成爬虫,那么我们就要用到node环境,如果不会配的话,请参考我的第一篇博客。
好的,我们从爬虫流程开始分析我们需要的一些模块。
首先,我们需要发送请求获得页面,在这里呢,我们用到了request-promise模块。
const rp = require("request-promise"), //进入request-promise模块 async getPage(URL) { const data = { url, res: await rp({ url: URL }) }; return data //这样,我们返回了一个对象,就是这个页面的url和页面内容。 }
其次,解析页面,我们使用一个叫做Cheerio的模块将上面返回的对象中的res解析成类似JQ的调用模式。Cheerio使用一个非常简单,一致的DOM模型。因此解析,操作和渲染非常高效。初步的端到端基准测试表明cheerio 比JSDOM快大约8倍。
const cheerio = require("cheerio");//引入Cheerio模块 const $ = cheerio.load(data.res); //将html转换为可操作的节点
此时,我们要对我们即将进行爬取的页面进行分析。“www.mzitu.com/125685”,这是我们进行爬取的网址,F12查看DOM结构:
根据这个结构我们可以使用$(".main-image").find("img")[0].attribs.src来爬取这张图片的地址(如果不知道为什么是attribs.src的话可以一步一步console.log()一下看看)。
最后,到了最关键的时候,我们使用fs模块进行创建文件夹以及下载文件。这里用到了fs模块的几个指令:
1.fs.mkdirSync(downloadPath):查看是否存在这个文件夹。
2.fs.mkdirSync(downloadPath):创建文件夹。
3.fs.createWriteStream(`${downloadPath}/${index}.jpg`):写入文件,这里需要注意的是fs.createWriteStream 似乎不会自己创建不存在的文件夹,所以在使用之前需要注意,保存文件的文件夹一定要提前创建。
好的,大体的方法就是以上的几个模块和步骤。
在这里,我针对这个网站的一些情况进行一下分析:
1.这个网站一个页面只有一张图片,但是每个页面的网址都是有根据的。“http://www.mzitu.com/125685”(当你输入“http://www.mzitu.com/125685/1”时也会跳转此页面),“http://www.mzitu.com/125685/2”等等。那么我们可以根据这个规律去爬取,并且我们需要在页面的下方的页码栏中获得这一组图图片的页码:
2.我们一般不会只爬取一组图片,但是这个网站的图片的标题也就是最后的六位数基本没有规律可言,那么我们只能从最开始的首页入手。具体方法不多做描述,与获取图片的URL方式相同。
3.同理,我们爬取完一页的目录之后会进行对第二个目录的爬取,“http://www.mzitu.com/page/2/”,其原理和第一条相同。
4.但是,有的网站存在防盗链的情况,面对这种措施,我们需要伪造一个请求头来避开这个情况。这个可以从F12的Network中查到,看到这里的朋友我想也会明白。
let headers = { Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Cache-Control": "no-cache", Host: "i.meizitu.net", Pragma: "no-cache", "Proxy-Connection": "keep-alive", Referer: data.url,//根据爬取的网址跟换 "Upgrade-Insecure-Requests": 1, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.19 Safari/537.36" };
以上就是我的全部思路。
代码:
业务代码:
const rp = require("request-promise"), //进入request-promise模块 fs = require("fs"), //进入fs模块 cheerio = require("cheerio"), //进入cheerio模块 depositPath = "D:/blog/reptile/meizi/"; //存放照片的地址 let downloadPath; //下载图片的文件夹地址 module.exports = { async getPage(url) { const data = { url, res: await rp({ url: url }) }; return data; }, getUrl(data) { let list = []; const $ = cheerio.load(data.res); //将html转换为可操作的节点 $("#pins li a") .children() .each(async (i, e) => { let obj = { name: e.attribs.alt, //图片网页的名字,后面作为文件夹名字 url: e.parent.attribs.href //图片网页的url }; list.push(obj); //输出目录页查询出来的所有链接地址 }); return list; }, getTitle(obj) { downloadPath = depositPath + obj.name; if (!fs.existsSync(downloadPath)) {//查看是否存在这个文件夹 fs.mkdirSync(downloadPath);//不存在就建文件夹 console.log(`${obj.name}文件夹创建成功`); return true; } else { console.log(`${obj.name}文件夹已经存在`); return false; } }, getImagesNum(res, name) { if (res) { let $ = cheerio.load(res); let len = $(".pagenavi") .find("a") .find("span").length; if (len == 0) { fs.rmdirSync(`${depositPath}${name}`);//删除无法下载的文件夹 return 0; } let pageIndex = $(".pagenavi") .find("a") .find("span")[len - 2].children[0].data; return pageIndex;//返回图片总数 } }, //下载相册照片 async downloadImage(data, index) { if (data.res) { var $ = cheerio.load(data.res); if ($(".main-image").find("img")[0]) { let imgSrc = $(".main-image").find("img")[0].attribs.src;//图片地址 let headers = { Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Cache-Control": "no-cache", Host: "i.meizitu.net", Pragma: "no-cache", "Proxy-Connection": "keep-alive", Referer: data.url, "Upgrade-Insecure-Requests": 1, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.19 Safari/537.36" };//反防盗链 await rp({ url: imgSrc, resolveWithFullResponse: true, headers }).pipe(fs.createWriteStream(`${downloadPath}/${index}.jpg`));//下载 console.log(`${downloadPath}/${index}.jpg下载成功`); } else { console.log(`${downloadPath}/${index}.jpg加载失败`); } } } };
主体逻辑代码:
const model = require("./model"), basicPath = "http://www.mzitu.com/page/"; let start = 1, end = 10; const main = async url => { let list = [], index = 0; const data = await model.getPage(url); list = model.getUrl(data); downLoadImages(list, index);//下载 }; const downLoadImages = async (list, index) => { if (index == list.length) { start++; if (start < end) { main(basicPath + start);//进行下一页图片组的爬取。 } return false; } if (model.getTitle(list[index])) { let item = await model.getPage(list[index].url),//获取图片所在网页的url imageNum = model.getImagesNum(item.res,list[index].name);//获取这组图片的数量 for (var i = 1; i <= imageNum; i++) { let page = await model.getPage(list[index].url + `/${i}`);//遍历获取这组图片每一张所在的网页 await model.downloadImage(page, i);//下载 } index++; downLoadImages(list, index);//循环完成下载下一组 } else { index++; downLoadImages(list, index);//下载下一组 } }; main(basicPath + start);
此次项目已上传我的Github仓库https://github.com/lunlunshiwo/NodeJs-crawler,求star,谢谢。
总结:
至于后续操作,比如存到分类保存到本地和MongoDB数据库这样的操作,我下次再写,请关注我。
郑重提升,爬虫虽好,一定不能触犯法律。
如果本本文触犯您的利益,请留言。
如果觉得本文不错,不要吝啬您的点赞和关注。谢谢。