文档章节

Node爬虫爬取网页静态资源

Will_Bean
 Will_Bean
发布于 2016/12/11 17:18
字数 1645
阅读 71
收藏 2
点赞 1
评论 0

准备工作

  1. 首先当然是安装node,这一步忽略。
  2. 然后是各种模块,本实例用到了http、fs、url、cheerio、request、async、phantom,前三个是node自带的,无需install。
  3. 因为要服务器渲染,所以要用到phantomjs,这个需要自行安装一下,最后再配置一下全局环境。

模块解释

  1. cheerio模块用于解析DOM树,进行DOM操作, 具体用法跟JQuery类似,对熟悉JQ的人来说,学会使用也就是几分钟的事。
  2. request模块,http模块的高级封装版,便于操作。
  3. async模块,解决“恶魔金字塔”问题。
  4. phantom模块,在服务器端渲染整个界面,为的是能够爬取到页面上一些通过js等动态加载的内容。

具体实现

  1. 公用接口
exports.Strategy = {//js,css,images等文件的保存策略
    "SAVE_IN_ROOT": 1,//保存在根目录下
    "SAVE_IN_SUB_DIR": 2//保存在各级子目录下
};
exports.uniqueArray = function (arr) {//数组去重
    var hash = {},
        len = arr.length,
        result = [];

    for (var i = 0; i < len; i++){
        if (!hash[arr[i]]){
            hash[arr[i]] = true;
            result.push(arr[i]);
        }
    }
    return result;
};
exports.timer = function (date,msg) {//简易版计时器
    console.log(msg + " : "+(new Date() - date) +"ms" );
};
  1. 配置
var config = {
    url: "http://localhost:8081/dhay/",//目的网站
    savePath: "J:/nodejs/open-source-spider",//保存路径
    containOutLink: false, //是否爬取外部链接
    totalNum: 10, //爬取页面上限,0为不限制
    endWith: "html",//文件结尾
    saveStrategy: publicAPI.Strategy.SAVE_IN_ROOT,
    getOuterJs: false,//是否爬取远端js
    getOuterCss: false,//是否爬取远端Css
    getOuterImages: false//是否爬取远端图片
};
    为了简化操作,判断是否为外部资源的方式简化为判断URL是否以http或https为开头,虽然不够严谨,但是能保证爬取下来的网页能根据URL获取到资源。
    保存策略目前也只实现了SAVE_IN_ROOT而已,懒~
  1. 全局变量
var list = [config.url]; //所要爬取的网页链接队列
var count = 0; //当前爬取的网页数
var date = null; 
var urlInfo = url.parse(config.url); //入口链接信息
  1. 获取服务器渲染之后的页面
phantom.create().then(function (ph) {
        ph.createPage().then(function (page) {
            page.open(url).then(function (status) {    
                if (status == 'success') {
                    page.property('content').then(function (html) {
                        console.log(html);
                    }
                }
            })
        });
  1. 解析DOM树
var $ = cheerio.load(html);
var js, css , images;
//获取js列表
var scripts = $("script");
js = getJs(scripts);
console.log(js);
//获取css
var stylesheets = $("link[rel='stylesheet']");
css = getCss(stylesheets);
console.log(css);
//获取图片
var imgs = $("img");
images = getImages(imgs);
console.log(images);
//获取链接
if(!config.totalNum || count < config.totalNum){
      var links = $("a");
      getLink(links,url);
}
    获取页面中所有的js、css和img,分别进行调用函数,返回所要爬取的文件的url数组。
  1. getJs()等函数
function getJs(scripts) {
    var res = [];
    scripts.each(function (i, script) {
        var src = script.attribs.src;
        if (!src)
            return;
        if (!config.getOuterJs) {
            if (/^https?/.test(src))
                return;
        }
        res.push(src);
    });
    return publicAPI.uniqueArray(res);
}
    getJs()函数,遍历每一个元素,判断其src属性是否存在,不存在则跳过,再根据配置判断是否获取外部文件,满足各条件的加入到res数组中,最后去重后返回结果。
    getCss、getImages和getLink方法与上述类似,不同的是getLink多了一些判断和URL格式化操作。
  1. 保存网页文本
var saveHtml = function (url, html, callback) {
    url = url.match(/https?:\/\/((?:(?![\?])[\S])*)/)[1];//截取?以前的字符串
    var endWith = /\/$/.test(url); //判断是否以反斜杠结尾
    url = endWith ? url.match(/(\S*)\/$/)[1] : url; //去除反斜杠
    var reg = new RegExp(/\.html|\.htm|\.asp|\.jsp$/); //判断是否以这些字符串结尾
    var usePathAsName = reg.test(url);
    var array = url.split("/"); //以反斜杠分割字符串
    array[0] = urlInfo.hostname;
    var length = usePathAsName ? array.length-1: array.length;
    var currentPath = config.savePath;

    for (var i = 0; i < length; i++) { //遍历数组,逐层判断当前路径是否存在指定文件夹,不存在则创建
        (function (i) {
            currentPath += "/" + array[i];
            if (fs.existsSync(currentPath)) {
                write(i, callback)
            } else {
                try {
                    fs.mkdirSync(currentPath);
                    write(i, callback)
                }catch (err){
                    console.log(err);
                }
            }
        })(i)
    }

    function write(index, callback) { // 若为最后一个元素,执行写入操作
        if (index == length - 1) {
            var fileName = endWith ? "index." + config.endWith : usePathAsName ? array[array.length-1] : array[array.length-1] + config.endWith; //根据情况选择文件名
            fs.writeFile(currentPath + "/" + fileName, html, function (err) {
                if (err) {
                    console.log(err, "appendFile");
                } else {
                    callback();
                }
            });
        }
    }
};
  1. 保存js等资源
var saveJs = function (url, js, callback ,callback2) {
    if(!js.length){
        callback2(null);
        return;
    }
    var length = js.length;
    var count = 0;
    var root = config.savePath + "/" + urlInfo.hostname;
    if(config.saveStrategy == publicAPI.Strategy.SAVE_IN_ROOT){
        if(fs.existsSync(root)){
            write(callback,callback2)
        }else{
            fs.mkdirSync(root);
            write(callback,callback2)
        }
    }

    function write(callback,callback2) {
        for(var i = 0;i<length;i++){
            (function (i) {
                var reg = new RegExp(/^\//);
                var path = reg.test(js[i]) ? js[i].substring(1) : js[i];
                var array = path.split("/");
                var currentPath = root;
                var len = array.length;
                for(var j =0;j<len-1;j++){ //创建对应目录
                    (function (j) {
                        currentPath += "/" + array[j];
                        if (fs.existsSync(currentPath)) {
                            if(j == len-2){
                                fetch(js[i],function () {
                                    callback(count, js[i])
                                },callback2)
                            }
                        } else {
                            try {
                                fs.mkdirSync(currentPath);console.log(j,4);
                                if(j == len-2){
                                    fetch(js[i],function () {
                                        callback(count, js[i])
                                    },callback2)
                                }
                            }catch (err){
                                console.log("error!")
                            }
                        }
                    })(j);
                }
            })(i);
        }
    }

    function fetch(js,callback,callback2) {
        request(url+js,function (err,res,body) { //获取资源,再写入
            //console.log(body);
            js = js.match(/((?:(?![\?])[\S])*)/)[1];
            if(err){
                count++;
            }else{
                fs.writeFile(root +"/"+ js,body,function (err) {
                    count++;
                    if(count == length)
                        callback2(null);
                    if(err){
                        console.log(err);
                    }else{
                        callback();
                    }
                });

            }
        })
    }
};
    保存js等资源的方法比保存网页的更为复杂,主要是因为一个页面可能存在多个js、css等资源,这里有两种保存的策略,一是直接将所有文件保存在根目录下,如根目录如localhost,现有js文件链接为“localhost:8080//abc/js/main/js”,将该js文件保存在localhost/js/下,另一种是保存在对应目录下,即localhost/abc/js/下,但是第二种方法会导致出现很多重复的文件,就没有实现出来。
    获取css、images的方法类似,不做赘述。
  1. 并行执行写入操作
async.parallel([
    function (callback) {
        saveHtml(url, html, function () {
            console.log("Page:"+(count+1)+"    Url:"+url+"    success!\n");
            callback(null);
        });
    },
    function (callback) {
        saveJs(url,js, function (x, js) {
            console.log("Page:"+(count+1)+"    Js"+(x+1)+"     Src:"+js+"    Success!\n");
        },callback);
    },
    function (callback) {
        saveCss(url, css, function (x, css) {
            console.log("Page:"+(count+1)+"    Css"+(x+1)+"     Src:"+css+"    Success!\n");
        },callback);
    },
    function (callback) {
        saveImage(url, images, function (x,img) {
            console.log("Page:"+(count+1)+"    Images"+(x+1)+"     Src:"+img+"    Success!\n");
        },callback);
    }
],function (err) {
    page.close();
    if(err){
        console.log(err,"ERROR IN PARALLEL PAGE "+(count+1));
    }else {
        count++;
        console.log("Page:"+(count)+" finished!");
        var cur = list.shift(),next = list[0];
        next = /^https?:/.test(next) ? next : cur+'/'+next;
        if( count < config.totalNum)
            requirePage(next);
    }
})
    使用async模块,并行处理写入操作,当所有写入操作结束后,从list队列获取下一跳地址,循环操作。
  1. 测试 爬取http://localhost:8081/ 结果如图: 输入图片说明

总结

    之前帮别人做网站的时候看到有些网站做得不错,就想复制下来参考参考,结果发现手动复制也是怪累的,现在团队准备搞搞node爬虫,借此机会就自己写了个爬虫来爬网站。写这鬼东西花了两三天,但是很多功能都没有实现,如第二种保存策略,很多可以优化的地方也懒得优化,如phantom的create可以独立出来,也有成堆的bug,如没有全部写入操作完成就进入下一环了,啊啊啊,没时间,懒得搞了,到此为止!

    完整的代码放在github了:https://github.com/WillBean/os-spider.git

© 著作权归作者所有

共有 人打赏支持
Will_Bean
粉丝 0
博文 7
码字总数 4874
作品 0
广州
程序员
Python爬虫(1.爬虫的基本概念)

爬虫的基本概念 1. 网络爬虫的组成 网络爬虫由控制结点、爬虫结点、资源库构成,如图1 所示: 图1 网络爬虫的控制节点和爬虫节点结构的关系 可以看到,网络爬虫中可以有多个控制节点,每个控...

lhs322 ⋅ 04/20 ⋅ 0

分布式网络爬虫实例——获取静态数据和动态数据

前言 刚刚介绍完基于PyHusky的分布式爬虫原理及实现,让我们具备了设计分布式网络爬虫方便地调动计算资源来实现高效率的数据获取能力。可以说,有了前面的基础,已经能够解决互联网上的绝大部...

happengft ⋅ 2017/04/11 ⋅ 0

手把手教你写网络爬虫(2):迷你爬虫架构

原文出处:拓海 介绍 大家好!回顾上一期,我们在介绍了爬虫的基本概念之后,就利用各种工具横冲直撞的完成了一个小爬虫,目的就是猛、糙、快,方便初学者上手,建立信心。对于有一定基础的读...

拓海 ⋅ 04/27 ⋅ 0

Python爬虫入门并不难,甚至进阶也很简单

互联网的数据爆炸式的增长,而利用 Python 爬虫我们可以获取大量有价值的数据: 1.爬取数据,进行市场调研和商业分析 爬取知乎优质答案,筛选各话题下最优质的内容; 抓取房产网站买卖信息,...

菜鸟学python ⋅ 06/07 ⋅ 0

SuperSpider——打造功能强大的爬虫利器

1.爬虫的介绍 图1-1 爬虫(spider) 网络爬虫(web spider)是一个自动的通过网络抓取互联网上的网页的程序,在当今互联网中得到越来越广泛的使用。这种技术一般用来爬取网页中链接,资源等,当...

云栖希望。 ⋅ 2017/12/04 ⋅ 0

基于PyHusky的分布式爬虫原理及实现

原理 爬虫是我们获取互联网数据的一个非常有效的方法,而分布式爬虫则是利用许多台机器协调工作来加快抓取数据效率的不二途径。分布式爬虫是由访问某些原始网址开始,在获取这些网址的内容后...

happengft ⋅ 2017/04/06 ⋅ 0

你写过 Web 爬虫, 那么你写过 P2P 爬虫吗?

网络爬虫爱好者为了爬取视频, 图片, 文档, 软件, 可能只想到写一个 Web 爬虫, 从各大网站爬取. 但是你知道吗? 这个世界上, 还有 DHT 协议, BitTorrent 协议. 想想看, 全世界每天有那么多人通...

CrazySpiderMan ⋅ 2016/03/03 ⋅ 13

java爬虫系列(一)——爬虫入门

爬虫框架介绍 java爬虫框架非常多,比如较早的有Heritrix,轻量级的crawler4j,还有现在最火的WebMagic。 他们各有各的优势和劣势,我这里顺便简单介绍一下吧。 Heritrix 优势 java的第一批爬...

Mr_OOO ⋅ 2017/12/31 ⋅ 0

python+selenium+PhantomJS抓取动态内容

本人使用的是python3.6.1+selenium3.4.3+PhantomJS2.1.1来做一个小爬虫,爬取网页:http://t.10jqka.com.cn/guba/1...中动态加载的内容( ) 静态网页会爬取,不知道哪位大神可以帮忙写下这个...

LeoGQ ⋅ 2017/07/03 ⋅ 0

网络爬虫,如何做到 “盗亦有道” ?

网络爬虫的实质,其实是从网络上“偷”数据。通过网络爬虫,我们可以采集到所需要的资源,但是同样,使用不当也可能会引发一些比较严重的问题。 因此,在使用网络爬虫时,我们需要做到“盗亦...

bestdwd ⋅ 2017/06/09 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Windows下安装运行phpMyAdmin

首先确保安装了phpMyAdmin,其次要求服务器是打开的。 如果是在Windows下,建议下载安装WampServer,这是一个集成软件,集成了Apache+MySQL+PHP的开发环境,而且也自带了phpMyAdmin这个软件。...

临江仙卜算子 ⋅ 8分钟前 ⋅ 0

jdk1.8 安装及环境变量配置

1.根据jdk 的软件安装包,首先安装,jdk,

kuchawyz ⋅ 8分钟前 ⋅ 0

给Java字节码加上”翅膀“的JIT编译器

给Java字节码加上”翅膀“的JIT编译器 上面文章在介绍Java的内存模型的时候,提到过由于编译器的优化会导致重排序的问题,其中一个比较重要的点地方就是关于JIT编译器的功能。JIT的英文单词是...

九劫散仙 ⋅ 9分钟前 ⋅ 0

PCI简介(二)

1.x86处理器系统地址空间简介 1.1 CPU地址空间 CPU地址空间是指CPU所能寻址的空间大小,比如对于32位CPU来说,其所能寻址的空间大小为0~4G。这是由CPU自身的地址总线数目决定的。这段空间也被...

深山野老 ⋅ 10分钟前 ⋅ 0

spring中的InitializingBean接口

好久没更博了,真有点怀念,前段时间刚和上家公司say bye,这次进的是电商公司,今天刚开始看代码,逻辑很复杂。 今天看的注册功能,里面见到一个知识点,现在记录一下,今天看项目时见到里面...

千元机爱好者 ⋅ 12分钟前 ⋅ 0

机器学习:数据预处理之独热编码(One-Hot)

前言 ———————————————————————————————————————— 在机器学习算法中,我们经常会遇到分类特征,例如:人的性别有男女,祖国有中国,美国,法国等。 ...

NateHuang ⋅ 20分钟前 ⋅ 0

MyBatis之输入与输出(resultType、resultMap)映射

在MyBatis中,我们通过parameterType完成输入映射(指将值映射到sql语句的占位符中,值的类型与dao层响应方法的参数类型一致),通过resultType完成输出映射(从数据库中输出,通过dao层的方法查...

瑟青豆 ⋅ 20分钟前 ⋅ 0

屏蔽运营商广告劫持

在今天早上我查找知乎时再次遇到了恶心的运营商广告劫持,右下角硕大的广告直接让知乎挂掉了,我刷了五次知乎才好,之前休息的时候逛知乎也是多次加载错误,估计也是这劫持的锅,相信各位也遇...

gcudwork ⋅ 24分钟前 ⋅ 0

java web 进度条实现原理

资料路径 https://blog.csdn.net/fengsheng5210/article/details/79305026

zaolonglei ⋅ 25分钟前 ⋅ 0

命令行输出java版本与环境变量配置的不一样问题解决

问题:java10刚出来,本着好奇的心,急切的装了体验一下,然后实际项目需求还是java8,所以体验完了就把环境变量改回来了,但是出现了一个问题,命令行输出java版本与环境变量配置的不一样,...

消散了的诗意 ⋅ 27分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部