zoukankan      html  css  js  c++  java
  • nodejs爬虫

    生活中遇到了问题,想去成都买个房,那哪个区域性价比高肯定要考虑一番了,最粗暴直接的就是看租售比,遂打算去链家网爬上各个小区的卖房单价和租房单价比上一比,python写爬虫无疑是最流行的了,但最近在研究node,感觉写个爬虫强化一下node姿势水平还是挺不错的。开整。

    首先http请求工具和dom解析工具是必不可少的,严谨的说是对于像我这样的菜鸟是必不可少的,http请求工具我选了 request,主流的还有 superagent 可选,dom解析 cheerio 应该是不二选择了,接口和 jquery 一样一样的。如果没接触过请先自行了解这两个库。

    基本环境先搭建好,这个不在讨论范围,

    github:https://github.com/huanqingli/node-web-spider

    1. 第一步我们先看抓一个网页是啥样的:

    router.get('/sell_price', (req, res, next) => {
                            request({
                                url:'http://cd.lianjia.com/ershoufang/pg1ng1nb1l1/',
                                headers: {
                                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/55.0.2883.9 Safari/537.3'
                            } }, function (error, response, body) {
                                if (!error && response.statusCode == 200) {
                                    res.send(body)                          
                                }
                            })
      }

    不出所料的话你访问本机 sell_price 路由的时候链家对应的页面就会出现了。

    其中有两点要说明,有的时候你不带User-Agent,网站会拒绝你的请求,chrome开发工具network里面随便找个连接,把里面User-Agent给考出来贴上,有备无患。

    链家是UTF-8编码,就没啥说法了,但是比如网易,它是gb2312编码的,你拿过来就成火星文了,此时需要搞个解码工具,iconv-lite应该是主流了。我去爬了下网易,要注意一点,request拿过来的时候已经帮你按UTF-8解码了,你需要request时候设置encoding: null(与设置url同级),拿回来个buffer,再用iconv-lite解码。

    2. 第二步我们抓过来是要获取里面的有用信息的,具体到我的目的,就是房价,面积,小区名等,

    var $=cheerio.load(body); 后,你用 jQuery 怎么取值就用 cheerio 怎么取值,比如说:

    $('.sellListContentlia.img').eq(i).attr('href') 一个页面有30个房源,这就拿到了第 i 个房源的连接,进而去获取房源详细信息。我抓了两次试了试,发现 IP 被拦住了,说我访问太频繁,要认证。其实大多数网站都有这个限制,这就不爽了,怎么办,找代理。

    3. 代理是这么爬下来的:

        router.get('/proxy',(req, res, next) => {
            function checkProxy(proxy) {
                return request({
                    url: 'http://cd.lianjia.com/ershoufang/pg1ng1nb1l1/',
                    proxy: "http://" + proxy['ip'] + ":" + proxy['port'],
                    timeout: 5000,  //2s没有返回则视为代理不行
                    headers: {
                        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/55.0.2883.9 Safari/537.3'
                    }
                }, function (error, response, body) {
                    if(!error) {
                        if (response.statusCode == 200) {
                            console.log(response.request['proxy']['href'], "useful!");
                            Ip.create({proxyIp:response.request['proxy']['href']}); //Ip是mogoose建立的数据库模块储存Ip
                        } else {
                            console.log(response.request['proxy']['href'], "failed!");
                        }
                    }
                });
            }
        
            request({
                url:'http://www.xicidaili.com/nn/1',
                headers: {
                    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/55.0.2883.9 Safari/537.3'
                } }, function (error, response, body) {
                if (!error && response.statusCode == 200) {
                    var $ = cheerio.load(body);
                    var trs = $("#ip_list tr");
                    for(var i=1;i<trs.length;i++){
                        var proxy = {};
                        var tr = trs.eq(i);
                        var tds = tr.children("td");
                        proxy['ip'] = tds.eq(1).text();
                        proxy['port'] = tds.eq(2).text();
                        var speed = tds.eq(6).children("div").attr("title");
                        speed = speed.substring(0, speed.length-1);
                        var connectTime = tds.eq(7).children("div").attr("title");
                        connectTime = connectTime.substring(0, connectTime.length-1);
                        if(speed <= 5 && connectTime <= 1) { //用速度和连接时间筛选一轮
                            checkProxy(proxy);
                        }
                    }
                }
            })
        
        });

    还是熟悉的配方还是同样的味道,从http://www.xicidaili.com/nn爬下代理信息,筛选一遍存到数据库。

    4. 存了二十几个能用的代理,开始爬售房连接吧:

        var urlsArray = [];
        const pageNum = 100;
        const housePerPage = 30;
        router.get('/sell_price', (req, res, next) => {
            function saveUrl(url) {
                return Url.create({sellUrl:url})  //Url是mogoose建立的数据库模块储存售房连接
            }
            var allHouseUrls = async function (ips) {
                var j=1 ;
                while(j<= pageNum){
                    await new Promise(
                        function (resolve, reject) {
                            request({
                                url:'http://cd.lianjia.com/ershoufang/pg'+j+'ng1nb1l1/',
                                proxy: ips[Math.floor(Math.random()*ips.length)].proxyIp,
                                timeout: 5000,
                                headers: {
                                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/55.0.2883.9 Safari/537.3'
                            } }, function (error, response, body) {
                                if (!error && response.statusCode == 200) {
                                    console.log(ips[Math.floor(Math.random()*ips.length)].proxyIp);
                                    var $ = cheerio.load(body);
                                    for(let i=0;i<housePerPage;i++){
                                        if($('.sellListContent li a.img').eq(i).attr('href')&&urlsArray.indexOf($('.sellListContent li a.img').eq(i).attr('href'))==-1){
                                            urlsArray.push($('.sellListContent li a.img').eq(i).attr('href'));
                                            saveUrl($('.sellListContent li a.img').eq(i).attr('href'));
                                        }
                                    }
                                    j+=1;
                                    resolve()
                                } else{
                                    console.log(error);
                                    resolve()
                                }
                            })
                        }
                    )
                }
            };
            Ip.find({},function (err, ips) {
                allHouseUrls(ips);
            })
        });
        
        module.exports = router;

    要说明的就两点取到代理 ip 后,每次请求随机一个ip,如果请求失败(事实证明失败率很高免费代理并不是很可靠),本次循环作废,重新随机一个 ip 再请求。如此进行下去,n长时间后终于成功发送了100个请求,请求了3000个连接。可想而知成功发送3000个请求将是一场灾难。转战安居客,每个页面可以获得50条房屋数据而且不限ip

    async,awaite,Promise用作异步控制,node7.6以上默认支持,还有其他好多异步方案可选,怎么着都行。

    5. 爬安居客代码如下:

        router.get('/sell_price', (req, res, next) => {
            var allHouseInfo = async function (area) {
                var j=1 ;
                while(j<= pageNum){
                    await new Promise(
                        function (resolve, reject) {
                            request({
                                url:'http://chengdu.anjuke.com/sale/'+area+'/b54-p'+j,
                                // proxy: ips[Math.floor(Math.random()*ips.length)].proxyIp,
                                // timeout: 2000,
                                headers: {
                                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.3 (KHTML, like Gecko) Chrome/55.0.2883.9 Safari/537.3'
                            } }, function (error, response, body) {
                                if (!error && response.statusCode == 200) {
                                    var $ = cheerio.load(body);
                                    var lists = $('#houselist-mod li');
                                    for(var i=0;i<lists.length;i++){
                                        var info = {
                                            area:lists.eq(i).find('.details-item').children('span').eq(0).text().slice(0,-2),
                                            price:lists.eq(i).find('.details-item').children('span').eq(2).text().slice(0,-4),
                                            year:lists.eq(i).find('.details-item').children('span').eq(4).text().slice(0,4),
                                            garden:lists.eq(i).find('.details-item').eq(1).children('span').attr('title').split('[')[0].slice(0,-2)
                                        };
                                        Sell.create(info);
                                        // console.log(info)
                                    }
                                    j+=1;
                                    resolve()
                                } else{
                                    console.log(error);
                                    resolve()
                                }
                            })
                        }
                    )
                }
            };
            allHouseInfo("gaoxin");
            allHouseInfo("wuhou");
            allHouseInfo("qingyang");
            allHouseInfo("jinjiang");
            allHouseInfo("chenghua");
            allHouseInfo("jinniu");
            allHouseInfo("longquanyi");
    });

    没有新东西,爬了12000条房屋数据,也就是2秒钟的事,存在数据库里备用。就写到这,后面的事儿就是数据分析啦。

  • 相关阅读:
    MAVEN学习笔记之私服Nexus(2)
    MAVEN学习笔记之基础(1)
    mybatis 高级映射和spring整合之逆向工程(7)
    IPC之共享内存
    IPC之SystemV
    IPC之消息队列
    IPC之信号量
    线程同步
    线程函数
    线程基础
  • 原文地址:https://www.cnblogs.com/lihuanqing/p/6555049.html
Copyright © 2011-2022 走看看