利用node.js爬取指定排名网站的JS引用库详解

前言

本文给大家介绍的爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。下面话不多说了,来一起看看详细的介绍:

所用到的node主要模块

  • express 不用多说
  • request http模块
  • cheerio 运行在服务器端的jQuery
  • node-inspector node调试模块
  • node-dev 修改文件后自动重启app

关于调试Node

在任意一个文件夹,执行node-inspector,通过打开特定页面,在页面上进行调试,然后运行app,使用node-dev app.js来自动重启应用。

所碰到的问题

1. request请求多个页面

由于请求是异步执行的,和分别返回3个页面的数据,这里只爬取了50个网站,一个页面有20个,所以有3页,通过循环里套request请求,来实现。

通过添加请求头可以实现基本的反爬虫

处理数据的方法都写在analyData()里面,造成后面的数据重复存储了,想了很久,才想到一个解决方法,后面会写到是怎么解决的。

for (var i = 1; i < len+1; i++) {
 (function(i){
 var options = {
 url: 'http://www.alexa.cn/siterank/' + i,
 headers: {
 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
 }
 };
 request(options, function (err, response, body) {
 analyData(body,rank);
 })
 })(i)
 }

2. 多层回调

仔细观察代码,你会发现,处理数据的方法使用了如下的多层回调,也可以不使用回调,写在一个函数内部;因为,每层都要使用上一层的数据,造成了这样的写法。

function f1(data1){
 f2(data1);
}

function f2(data2){
 f3(data2);
}

function f3(data3){
 f4(data4);
}

3. 正则获取JS库

由于获取页面库,首先需要获取到script的src属性,然后通过正则来实现字符串匹配。

<script src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>

获取到的script可能是上面这样的,由于库名的命名真是各种各样,后来想了一下,因为文件名是用.js结尾的,所以就以点号为结尾,然后把点号之前的字符截取下来,这样获得了库名,代码如下。

var reg = /[^\/\\]+$/g;
var libName = jsLink.match(reg).join('');
var libFilter = libName.slice(0,libName.indexOf('.'));

4.cheerio模块获取JS引用链接

这部分也花了一点时间,才搞定,cheerio获取DOM的方法和jQuery是一样的,需要对返回的DOM对象进行查看,就可以看到对象里隐藏好深的href属性,方法大同小异,你也可以使用其他选择器,选择到script标签

var $ = cheerio.load(body);
var scriptFile = $('script').toArray();

scriptFile.forEach(function(item,index){
 if (item.attribs.src != null) {
 obtainLibName(item.attribs.src,index);
}

5.存储数据到数据库

存储数据的逻辑是先获取所有的script信息,然后push到一个缓存数组,由于push后面,紧跟着存储到数据库的方法,这两个方法都写在循环里面的,例如爬取5个网站,每个网站存储一次,后面也会跟着存储,造成数据重复存储。解决方法是存储数据的一般逻辑是先查,再存,这个查比较重要,查询的方法也有多种,这里主要是根据库名来查找唯一的数据对象,使用findOne方法。注意,由于node.js是异步执行的,这里的闭包,每次只传一个i值进去,执行存储的操作。

// 将缓存数据存储到数据库
function store2db(libObj){
 console.log(libObj);
 for (var i = 0; i < libObj.length; i++) {
 (function(i){
 var jsLib = new JsLib({
 name: libObj[i].lib,
 libsNum: libObj[i].num
 });

 JsLib.findOne({'name': libObj[i].lib},function(err,libDoc){
 if(err) console.log(err);
 // console.log(libDoc)
 if (!libDoc){
 jsLib.save(function(err,result){
 if(err) console.log('保存数据出错' + err);
 });
 }

 })
 })(i)
 }
 console.log('一共存储' + libObj.length + '条数据到数据库');
}

6.分页插件

本爬虫前端使用了bootstrap.paginator插件,主要是前台分页,返回数据,根据点击的页数,来显示对应的数据,后期考虑使用AJAX请求的方式来实现翻页的效果,这里的注意项,主要是最后一页的显示,最好前面做个判断,因为返回的数据,不一定刚好是页数的整数倍

function _paging(libObj) {
 var ele = $('#page');
 var pages = Math.ceil(libObj.length/20);
 console.log('总页数' + pages);
 ele.bootstrapPaginator({
 currentPage: 1,
 totalPages: pages,
 size:"normal",
 bootstrapMajorVersion: 3,
 alignment:"left",
 numberOfPages:pages,
 itemTexts: function (type, page, current) {
 switch (type) {
 case "first": return "首页";
 case "prev": return "上一页";
 case "next": return "下一页";
 case "last": return "末页";
 case "page": return page;
 }
 },
 onPageClicked: function(event, originalEvent, type, page){
 // console.log('当前选中第:' + page + '页');
 var pHtml = '';
 var endPage;
 var startPage = (page-1) * 20;
 if (page < pages) {
 endPage = page * 20;
 }else{
 endPage = libObj.length;
 }
 for (var i = startPage; i < endPage; i++) {
 pHtml += '<tr><td>';
 pHtml += (i+1) + '</td><td>';
 pHtml += libObj[i].name + '</td><td>';
 pHtml += libObj[i].libsNum + '</td></tr>';
 }
 libShow.html(pHtml);
 }
 })
 }

完整代码

1. 前端

$(function () {
 var query = $('.query'),
 rank = $('.rank'),
 show = $('.show'),
 queryLib = $('.queryLib'),
 libShow = $('#libShow'),
 libName = $('.libName'),
 displayResult = $('.displayResult');

 var checkLib = (function(){

 function _query(){
 query.click(function(){
 $.post(
 '/query',
 {
 rank: rank.val(),
 },
 function(data){
 console.log(data);
 }
 )
 });
 queryLib.click(function(){
 var inputLibName = libName.val();
 if (inputLibName.length == 0) {
 alert('请输入库名~');
 return;
 }
 $.post(
 '/queryLib',
 {
 libName: inputLibName,
 },
 function(data){
 if(data.length == 0){
 alert('没有查询到名为' + inputLibName + '的库');
 libName.val('');
 libName.focus();
 libShow.html('')
 return;
 }
 var libHtml = '';
 for (var i = 0; i < data.length; i++) {
 libHtml += '<tr><td>';
 libHtml += (i+1) + '</td><td>';
 libHtml += data[i].name + '</td><td>';
 libHtml += data[i].libsNum + '</td></tr>';
 }
 libShow.html(libHtml);
 }
 )
 });
 }

 function _showLibs(){
 show.click(function(){
 $.get(
 '/getLibs',
 {
 rank: rank.val(),
 },
 function(data){
 console.log('一共返回'+ data.length + '条数据');
 console.log(data)
 var libHtml = '';
 for (var i = 0; i < 20; i++) {
 libHtml += '<tr><td>';
 libHtml += (i+1) + '</td><td>';
 libHtml += data[i].name + '</td><td>';
 libHtml += data[i].libsNum + '</td></tr>';
 }
 displayResult.show();
 libShow.html(libHtml);// 点击显示按钮,显示前20项数据
 _paging(data);
 }
 )
 });
 }

 //翻页器
 function _paging(libObj) {
 var ele = $('#page');
 var pages = Math.ceil(libObj.length/20);
 console.log('总页数' + pages);
 ele.bootstrapPaginator({
 currentPage: 1,
 totalPages: pages,
 size:"normal",
 bootstrapMajorVersion: 3,
 alignment:"left",
 numberOfPages:pages,
 itemTexts: function (type, page, current) {
 switch (type) {
 case "first": return "首页";
 case "prev": return "上一页";
 case "next": return "下一页";
 case "last": return "末页";
 case "page": return page;
 }
 },
 onPageClicked: function(event, originalEvent, type, page){
 // console.log('当前选中第:' + page + '页');
 var pHtml = '';
 var endPage;
 var startPage = (page-1) * 20;
 if (page < pages) {
 endPage = page * 20;
 }else{
 endPage = libObj.length;
 }
 for (var i = startPage; i < endPage; i++) {
 pHtml += '<tr><td>';
 pHtml += (i+1) + '</td><td>';
 pHtml += libObj[i].name + '</td><td>';
 pHtml += libObj[i].libsNum + '</td></tr>';
 }
 libShow.html(pHtml);
 }
 })
 }

 function init() {
 _query();
 _showLibs();
 }

 return {
 init: init
 }

 })();

 checkLib.init();

})

2.后端路由

var express = require('express');
var mongoose = require('mongoose');
var request = require('request');
var cheerio =require('cheerio');
var router = express.Router();
var JsLib = require('../model/jsLib')

/* 显示主页 */
router.get('/', function(req, res, next) {
 res.render('index');
});

// 显示库
router.get('/getLibs',function(req,res,next){
 JsLib.find({})
 .sort({'libsNum': -1})
 .exec(function(err,data){
 res.json(data);
 })
})

// 库的查询
router.post('/queryLib',function(req,res,next){
 var libName = req.body.libName;

 JsLib.find({
 name: libName
 }).exec(function(err,data){
 if (err) console.log('查询出现错误' + err);
 res.json(data);
 })
})

router.post('/query',function(req,res,next) {
 var rank = req.body.rank;
 var len = Math.round(rank/20);

 for (var i = 1; i < len+1; i++) {
 (function(i){
 var options = {
 url: 'http://www.alexa.cn/siterank/' + i,
 headers: {
 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
 }
 };
 request(options, function (err, response, body) {
 analyData(body,rank);
 })
 })(i)
 }
 res.json('保存成功')
})

var sites = [];
var flag = 0;
function analyData(data,rank) {
 if(data.indexOf('html') == -1) return false;
 var $ = cheerio.load(data);// 传递 HTML
 var sitesArr = $('.info-wrap .domain-link a').toArray();//将所有a链接存为数组

 console.log('网站爬取中``')
 for (var i = 0; i < 10; i++) { // ***这里后面要改,默认爬取前10名
 var url = sitesArr[i].attribs.href;
 sites.push(url);//保存网址,添加wwww前缀
 }
 console.log(sites);
 console.log('一共爬取' + sites.length +'个网站');
 console.log('存储数据中...')

 getScript(sites);
}

// 获取JS库文件地址
function getScript(urls) {
 var scriptArr = [];
 var src = [];
 var jsSrc = [];
 for (var j = 0; j < urls.length; j++) {
 (function(i,callback){
 var options = {
 url: urls[i],
 headers: {
 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
 }
 }

 request(options, function (err, res, body) {
 if(err) console.log('出现错误: '+err);
 var $ = cheerio.load(body);
 var scriptFile = $('script').toArray();
 callback(scriptFile,options.url);
 })
 })(j,storeLib)
 };

 function storeLib(scriptFile,url){
 flag++;// 是否存储数据的标志
 scriptFile.forEach(function(item,index){
 if (item.attribs.src != null) {
 obtainLibName(item.attribs.src,index);
 }
 })

 function obtainLibName(jsLink,i){
 var reg = /[^\/\\]+$/g;
 var libName = jsLink.match(reg).join('');
 var libFilter = libName.slice(0,libName.indexOf('.'));

 src.push(libFilter);
 }

 // console.log(src.length);
 // console.log(calcNum(src).length)
 (function(len,urlLength,src){
 // console.log('length is '+ len)
 if (len == 10 ) {// len长度为url的长度才向src和数据库里存储数据,防止重复储存
 // calcNum(src);//存储数据到数据库 // ***这里后面要改,默认爬取前10名
 var libSrc = calcNum(src);
 store2db(libSrc);
 }
 })(flag,urls.length,src)
 }
}// getScript END

// 将缓存数据存储到数据库
function store2db(libObj){
 console.log(libObj);
 for (var i = 0; i < libObj.length; i++) {
 (function(i){
 var jsLib = new JsLib({
 name: libObj[i].lib,
 libsNum: libObj[i].num
 });

 JsLib.findOne({'name': libObj[i].lib},function(err,libDoc){
 if(err) console.log(err);
 // console.log(libDoc)
 if (!libDoc){
 jsLib.save(function(err,result){
 if(err) console.log('保存数据出错' + err);
 });
 }

 })
 })(i)
 }
 console.log('一共存储' + libObj.length + '条数据到数据库');
}
// JS库排序算法
function calcNum(arr){
 var libObj = {};
 var result = [];
 for (var i = 0, len = arr.length; i < len; i++) {

 if (libObj[arr[i]]) {
 libObj[arr[i]] ++;
 } else {
 libObj[arr[i]] = 1;
 }
 }

 for(var o in libObj){
 result.push({
 lib: o,
 num: libObj[o]
 })
 }

 result.sort(function(a,b){
 return b.num - a.num;
 });

 return result;
}

module.exports = router;

源码下载

github下载地址 (本地下载)

后记

通过这个小爬虫,学习到很多知识,例如爬虫的反爬虫有哪些策越,意识到node.js的异步执行特性,前后端是怎么进行交互的。同时,也意识到有一些方面的不足,后面还需要继续改进,欢迎大家的相互交流。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 利用Node.js制作爬取大众点评的爬虫

    前言 Node.js天生支持并发,但是对于习惯了顺序编程的人,一开始会对Node.js不适应,比如,变量作用域是函数块式的(与C.Java不一样):for循环体({})内引用i的值实际上是循环结束之后的值,因而引起各种undefined的问题:嵌套函数时,内层函数的变量并不能及时传导到外层(因为是异步)等等. 一. API分析 大众点评开放了查询餐馆信息的API,这里给出了城市与cityid之间的对应关系, 链接:http://m.api.dianping.com/searchshop.json

  • Node.js环境下编写爬虫爬取维基百科内容的实例分享

    基本思路 思路一(origin:master):从维基百科的某个分类(比如:航空母舰(key))页面开始,找出链接的title属性中包含key(航空母舰)的所有目标,加入到待抓取队列中.这样,抓一个页面的代码及其图片的同时,也获取这个网页上所有与key相关的其它网页的地址,采取一个类广度优先遍历的算法来完成此任务. 思路二(origin:cat):按分类进行抓取.注意到,维基百科上,分类都以Category:开头,由于维基百科有很好的文档结构,很容易从任一个分类,开始,一直把其下的所有分类全都抓

  • node+experss实现爬取电影天堂爬虫

    上周写了一个node+experss的爬虫小入门.今天继续来学习一下,写一个爬虫2.0版本. 这次我们不再爬博客园了,咋玩点新的,爬爬电影天堂.因为每个周末都会在电影天堂下载一部电影来看看. talk is cheap,show me the code! 抓取页面分析 我们的目标: 1.抓取电影天堂首页,获取左侧最新电影的169条链接 2.抓取169部新电影的迅雷下载链接,并且并发异步抓取. 具体分析如下: 1.我们不需要抓取迅雷的所有东西,只需要下载最新发布的电影即可,比如下面的左侧栏.一共有

  • 利用node.js写一个爬取知乎妹纸图的小爬虫

    前言 说起写node爬虫的原因,真是羞羞呀.一天,和往常一样,晚上吃过饭便刷起知乎来,首页便是推荐的你见过最漂亮的女生长什么样?,点进去各种漂亮的妹纸爆照啊!!!,看的我好想把这些好看的妹纸照片都存下来啊!一张张点击保存,就在第18张得时候,突然想起.我特么不是程序员么,这种手动草做的事,怎么能做,不行我不能丢程序员的脸了,于是便开始这次爬虫之旅. 原理 初入爬虫的坑,没有太多深奥的理论知识,要获取知乎上帖子中的一张图片,我把它归结为以下几步. 准备一个url(当然是诸如你见过最漂亮的女生长什么

  • node.js爬虫爬取拉勾网职位信息

    简介 用node.js写了一个简单的小爬虫,用来爬取拉勾网上的招聘信息,共爬取了北京.上海.广州.深圳.杭州.西安.成都7个城市的数据,分别以前端.PHP.java.c++.python.Android.ios作为关键词进行爬取,爬到的数据以json格式储存到本地,为了方便观察,我将数据整理了一下供大家参考 数据结果 上述数据为3月13日22时爬取的数据,可大致反映各个城市对不同语言的需求量. 爬取过程展示 控制并发进行爬取 爬取到的数据文件 json数据文件 爬虫程序 实现思路 请求拉钩网的

  • node爬取微博的数据的简单封装库nodeweibo使用指南

    一.前言 就在去年12月份,有个想法是使用node爬取微博的数据,于是简单的封装了一个nodeweibo这个库.时隔一年,没有怎么维护,中途也就将函数形式改成了配置文件.以前做的一些其他的项目也下线了,为了是更加专注前端 & node.js.偶尔看到下载量一天超过60多,持续不断的有人在用这个库,但是看下载量很少也就没有更新.但是昨天,有人pull request这个分支了,提出一些中肯的建议和有用的代码.于是就认真回顾了下nodeweibo,发布了v2.0.3这个版本. 二.什么是nodewe

  • 使用nodejs爬取前程无忧前端技能排行

    最近准备换工作,需要更新一下技能树.为做到有的放矢,想对招聘方的要求做个统计.正好之前了解过nodejs,所以做了个爬虫搜索数据. 具体步骤: 1.  先用fiddler分析请求需要的header和body. 2.  再用superagent构建上述数据发送客户端请求. 3.  最后对返回的数据使用cheerio整理. 折腾了几个晚上,只搞出了个架子,剩余工作等有时间再继续开发. /*使用fiddler抓包,需要配置lan代理,且设置如下参数*/ process.env.https_proxy

  • 利用node.js爬取指定排名网站的JS引用库详解

    前言 本文给大家介绍的爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库.下面话不多说了,来一起看看详细的介绍: 所用到的node主要模块 express 不用多说 request http模块 cheerio 运行在服务器端的jQuery node-inspector node调试模块 node-dev 修改文件后自动重启app 关于调试Node 在任意一个文件夹,执行node-inspector,通过打开特定页面,在页面上进行调试,然后运行app

  • python requests库爬取豆瓣电视剧数据并保存到本地详解

    首先要做的就是去豆瓣网找对应的接口,这里就不赘述了,谷歌浏览器抓包即可,然后要做的就是分析返回的json数据的结构: https://movie.douban.com/j/search_subjects?type=tv&tag=%E5%9B%BD%E4%BA%A7%E5%89%A7&sort=recommend&page_limit=20&page_start=0 这是接口地址,可以大概的分析一下各个参数的规则: type=tv,表示的是电视剧的分类 tag=国产剧,表示是

  • Python3爬虫学习之将爬取的信息保存到本地的方法详解

    本文实例讲述了Python3爬虫学习之将爬取的信息保存到本地的方法.分享给大家供大家参考,具体如下: 将爬取的信息存储到本地 之前我们都是将爬取的数据直接打印到了控制台上,这样显然不利于我们对数据的分析利用,也不利于保存,所以现在就来看一下如何将爬取的数据存储到本地硬盘. 1 对.txt文件的操作 读写文件是最常见的操作之一,python3 内置了读写文件的函数:open open(file, mode='r', buffering=-1, encoding=None, errors=None,

  • Python爬取微信小程序通用方法代码实例详解

    背景介绍 最近遇到一个需求,大致就是要获取某个小程序上的数据.心想小程序本质上就是移动端加壳的浏览器,所以想到用Python去获取数据.在网上学习了一下如何实现后,记录一下我的实现过程以及所踩过的小坑.本文关键词:Python,小程序,Charles抓包 目标小程序: 公众号"同城商圈网"左下角"找商家"->汽车维修->小车维修->所有的商家信息,如下图所示: 环境 PC端:Windows 10 移动端:iPhone 软件:Charles Char

  • 如何使用Node.js爬取任意网页资源并输出PDF文件到本地

    需求: 使用Node.js爬取网页资源,开箱即用的配置 将爬取到的网页内容以PDF格式输出 如果你是一名技术人员,那么可以看我接下来的文章,否则,请直接移步到我的github仓库,直接看文档使用即可 仓库地址:附带文档和源码 本需求使用到的技术:Node.js和puppeteer puppeteer 官网地址: puppeteer地址 Node.js官网地址:链接描述 Puppeteer是谷歌官方出品的一个通过DevTools协议控制headless Chrome的Node库.可以通过Puppe

  • 如何利用python多线程爬取天气网站图片并保存

    目录 1.1 题目 1.2 思路 1.2.1 发送请求 1.2.2 解析网页 1.2.3 获取结点 1.2.4 数据保存 (单线程) 1.2.4 数据保存 (多线程) 总结 1.1 题目 指定一个网站,爬取这个网站中的所有的所有图片,例如中国气象网(www.weather.com.cn),分别使用单线程和多线程的方式爬取.(限定爬取图片数量为学号后3位) 输出信息: 将下载的Url信息在控制台输出,并将下载的图片存储在images子文件中,并给出截图. 1.2 思路 1.2.1 发送请求 构造请

  • python利用urllib实现爬取京东网站商品图片的爬虫实例

    本例程使用urlib实现的,基于python2.7版本,采用beautifulsoup进行网页分析,没有第三方库的应该安装上之后才能运行,我用的IDE是pycharm,闲话少说,直接上代码! # -*- coding: utf-8 -* import re import os import urllib import urllib2 from bs4 import BeautifulSoup def craw(url,page): html1=urllib2.urlopen(url).read(

  • ajax+node+request爬取网络图片的实例(宅男福利)

    注:本文只讨论技术不涉及商业,如有侵权请告知,未经本人同意转载后果自负! 本文是通过浏览器端ajax,node端request-json进行爬取"尤果网"部分图片资源,纯属技术方面兴趣,不涉及商业方面: 先上图: 如果没有node基础请自行学习~ 获取图片原理:通过request请求html文件,利用正则匹配图片路径获取到当前页面图片的数组,发送到浏览器端,进行展示: 1.安装request-json (cnpm i request-json --save) 2.安装express(c

  • Python利用Scrapy框架爬取豆瓣电影示例

    本文实例讲述了Python利用Scrapy框架爬取豆瓣电影.分享给大家供大家参考,具体如下: 1.概念 Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中. 通过Python包管理工具可以很便捷地对scrapy进行安装,如果在安装中报错提示缺少依赖的包,那就通过pip安装所缺的包 pip install scrapy scrapy的组成结构如下图所示 引擎Scrapy Engine,用于中转调度其他部分的信号和数据

  • Python3实现爬取指定百度贴吧页面并保存页面数据生成本地文档的方法

    分享给大家供大家参考,具体如下:Python3实现爬取指定百度贴吧页面并保存页面数据生成本地文档的方法.分享给大家供大家参考,具体如下: 首先我们创建一个python文件, tieba.py,我们要完成的是,输入指定百度贴吧名字与指定页面范围之后爬取页面html代码,我们首先观察贴吧url的规律,比如: 百度贴吧LOL吧第一页:http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=0 第二页: http://tieba.baidu.com/f?kw=lol

随机推荐