zoukankan      html  css  js  c++  java
  • Node 爬虫心得

    简介

    使用 Node 爬取信息和其他语言几乎步骤相同,都同样是以下几点

    • 发起请求
    • 解析内容
    • 避免反爬虫
    • 爬虫策略更新

    注意:爬正规网站可能会有法律风险,但是那些小站,甚至自身就有问题的那种,总不怕啥问题。

    发起请求

    举个例子,笔者随手找了一个种子搜索站。发送下图请求,返回的是一个html页面

    接着我们分析页面html代码找到列表第一项的资源的超链接为 '/0AA61E5C1B7B665BC02BCCAF55F3EF7837AFA4F0.html',加上此站域名从而发送下图请求

    具体解析页面html代码抓取到想要的文本的方法,可以很粗暴的选择正则表达式。当抓取完毕资源,应该存储到本地,并且开始重新发送请求再来一遍。

    Demo 代码如下:

    var http = require('http');
    // http.request(options, callback);
    http.get('http://bt2.bt87.cc/search/SMD31_ctime_1.html', function(res) {
        var data = '';
        res.setEncoding("utf8"); 
    
        res.on('data', function(chunk) {
            data += chunk;
        }).on('end', function() {
    
            console.log(data)
        });
    });
    

    这里的data就是我们抓去到的html片段大概长这样

    第二幅图里的 magnet:xxxxxxxx,这种格式就是我们要的资源链接,迅雷可用。

    调整代码如下:

    var http = require('http');
    var count = 31;
    var start =  function (id) {
        http.get('http://bt2.bt87.cc/search/SMD'+ id + '_ctime_1.html', function(res) {
            var data = '';
            res.setEncoding("utf8"); 
    
            res.on('data', function(chunk) {
                data += chunk;
            }).on('end', function() {
                //var href = 第一个ul里的第一个第一个a标签的href属性
                http.get('http://bt2.bt87.cc' + href, function(res1) {
                    var data1 = '';
                    res1.setEncoding("utf8"); 
    
                    res1.on('data', function(chunk) {
                        data1 += chunk;
                    }).on('end', function() {
                        //var magnet = 正则匹配带有magnet关键字的信息
                        /* fs.appendFile(path, content, function (err){}) */
    
                        //重新开始请求
                        start(id + 1);
                    });
                });
            });
        });
    };
    start(count);
    

    代码优化

    上面的代码陷入了回调地狱里,十分难看,并且也不健壮。任何一个环节出差错都会导致后面代码不执行而停止循环请求。

    解决办法是,我们可以使用 ES6 的 Promise 语法,毕竟 Node 自 8 后,完全支持 Promise。改造我们的请求函数和文件操作函数。

    得到了爬取内容后,就得解析,解析 HTML 可以用 cheerio,类 JQuery 语法。但简单点直接正则吧,代码如下:

    //第一个请求,请求资源列表
    var getResourceUrl = function (url) {
        return new Promise(function (resolve, reject) {
            http.get(url, function(response) {
                var html = '';
                response.on('data', function(data) {
                    html += data;
                });
                response.on('end', function() {
                    var ul = html.match(/<ul class="media-list media-list-set">[sS]*</ul>/);
                    if (ul) {
                        resolve(ul[0]);
                    } else {
                        reject('can not match ul dom');
                    }
                    
                });
            }).on('error', function() {
                reject('getResourceUrl failed');
            });
        });
    };
    //第二个请求,请求具体的某个资源
    var getMagnet = function (url) {
        return new Promise(function (resolve, reject) {
            http.get(url, function(response) {
                var html = '';
                response.on('data', function(data) {
                    html += data;
                });
                response.on('end', function() {
                    var magnet = html.match(/magnet:??[^"|<]+/);
                    if (magnet) {
                        resolve(html);
                    } else {
                        reject('can not match magnetReg');
                    }
                    
                });
            }).on('error', function (res) {
                reject(res);
            });
        });
    };
    //追加文件
    var appendFile = function (path, content) {
        return new Promise(function (resolve, reject) {
            fs.appendFile(path, content, {flag:'a'}, function (err) {
                if (err) {
                    reject('append ' + path + ' failed');
                } else {
                    resolve('append ' + path + ' success');
                }
            });
        });
    };
    

    然后我们的调用的代码就成了这样

    //开始函数
    var start = function () {
    
        getResourceUrl(url);
        .then(function (html) {
            //var href = 第一个ul里的第一个第一个a标签的href属性
            return getMagnet('http://bt2.bt87.cc' + href);
        }, function (res) {
            return Promise.reject(res);
        })
        .then(function (resArr) {
            //var magnet = 正则匹配带有magnet关键字的信息
            return appendFile('./SMD.txt', magnet);
        }, function (res) {
            console.log(res);
            return Promise.reject(res);
        })
        .then(function (resArr) {
            console.log('writeFile success!');
            start();
        }, function (res) {
            console.log(res);
            start();
        });
    };
    

    简单又粗暴,而且某个环节掉了链子,比方说第一次请求匹配不到我们要的链接,也能把错误传递到最后的then里而重新 start() 一个请求,不会中断。

    内容解析

    具体怎么匹配到我们想要的资源,正则是一个王道的办法,比如下面代码

    //匹配magnet磁力链接
    var magnetReg = /magnet:??[^"|<]+/;
    //匹配ul标签
    var ulReg = /<ul class="media-list media-list-set">[sS]*</ul>/
    //匹配a标签
    var aReg = /<a class="title".* href="/w+.html")/g;
    

    但是这里可以有更简便的办法,就是利用cheerio库来DOM结构的html文本。

    var cheerio = require('cheerio');
    
    ...
    
    getResourceUrl(url);
    .then(function (html) {
        //var href = 第一个ul里的第一个第一个a标签的href属性
        var $ = cheerio.load(html);
        var $body = $('.media-body');
        var href = $body.eq(0).find('.title').attr('href');
        return getMagnet(href);
    }, function (res) {
        return Promise.reject(res);
    });
    

    就是这么容易,第二个请求也是如法炮制,最后输出到 SMD.txt 文件里的就是这种格式

    避免反爬虫

    笔者曾经在爬取妹子图网站上的图片的时候曾经遇到过,爬虫返回 403,这表示网站采用了防爬技术,反爬虫一般会采用比较简单即会检查用户代理(User Agent)信息。再请求头部构造一个User Agent就行了。也可能会检测Referer请求头,还有cookie等。高级的反爬虫会统计一个 ip 在一小时内请求量是否超过限制,达到则封锁 ip,这样的方案就需要加上代理,下面代码演示了一个伪造 User Agent 头并且连代理的最基本例子

    var http = require('http');
    
    var opt = {
        //代理服务器的ip或者域名,默认localhost
        host: '122.228.179.178',
        //代理服务器的端口号,默认80
        port: 80,
        //path是访问的路径
        path: 'http://www.163.com',
        //希望发送出去的请求头
        headers: {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36',
    
        }
    };
    
    http.get(opt, function(res) {
        var data = '';
        res.setEncoding("utf8"); 
    
        res.on('data', function(chunk) {
            data += chunk;
        }).on('end', function() {
    
            console.log(data)
        });
    });
    

    如果目标网站封锁了我方的IP地址的话,我们只要改变options参数里的host就能解决,这个代理ip只要在搜索引擎上输入“免费代理ip”就有了,比方说这个网站。不过不是每个免费代理ip都能用,难免有些失效了,所以狡猾的程序员会事先抓取网站提供的免费代理ip用它发送请求,如果能发送的了则证明ip可用。可用的一堆ip当作ip池,在爬虫的时候不停轮换使用。诚可谓道高一尺魔高一丈。

    爬虫策略

    加了 IP 能突破多数的反爬设置,但 IP 并非无限的,若短时间发的太多,还是可能被数据投毒,或者直接封禁。故而需要一些策略。

    举个简单的例子是爬取一阵,休息一两分钟再继续,并且控制爬取速度。

    思考题

    源自本人的一次面试,面试官问:如何写一个多线程的爬虫。

    提示:Node 里多线程是没办法,但是可以用多进程模式,关注一下 Node cluster 模块

    结尾

    献上我的源码一份,望不吝点赞。

  • 相关阅读:
    CSUFT 1002 Robot Navigation
    CSUFT 1003 All Your Base
    Uva 1599 最佳路径
    Uva 10129 单词
    欧拉回路
    Uva 10305 给任务排序
    uva 816 Abbott的复仇
    Uva 1103 古代象形文字
    Uva 10118 免费糖果
    Uva 725 除法
  • 原文地址:https://www.cnblogs.com/everlose/p/12835533.html
Copyright © 2011-2022 走看看