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 模块

    结尾

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

  • 相关阅读:
    干嘛
    有一种姑娘 你只想温柔以待
    衡阳拜佛
    浅谈程序员的英语学习【转】
    linux下用C编写的基于smtp的邮件发送程序【转】
    清空消息队列的缓存buffer
    不要对C++类对象或struct对象做memset操作
    xml解析编辑网站
    gdb调试
    喂狗机制 Software Dog
  • 原文地址:https://www.cnblogs.com/everlose/p/12835533.html
Copyright © 2011-2022 走看看