0から1までnodejs爬虫類の小さいプログラムを学びます.

18532 ワード

爬虫類とは
wikiはこう説明しています.
「自動化ブラウズネットワーク」というプログラムです.あるいはネットロボットです.これらはインターネット検索エンジンや他の類似サイトに広く使われ、これらのサイトのコンテンツや検索方法を取得または更新します.それらは自動的にすべてのアクセス可能なページの内容を収集して、検索エンジンのためにさらに処理することができます(ダウンロードしたページを整理することができます).ユーザーが必要な情報をより速く検索することができます.
robotsプロトコル
robots.txtはウェブサイトのルートディレクトリに保存されているASCIIコードのテキストファイルで、通常はウェブ検索エンジンのローミングマシン(ネットワーク爬虫ともいう)に教えていますが、このウェブサイトのどの内容が検索エンジンのローミングマシンによって取得されてはいけないもので、どれがローミングマシンによって取得されますか?Robots.txt , , 。はっきり言って、これは強制的に守らなければならない規定ではありません.これは君子間の協議にすぎません.君子が小人を防ぐためですが、この協議を守らないと不正競争につながるかもしれません.
今簡単にrobots.txtの中のいくつかの配置規則を列挙して、大まかな印象があって、爬虫類の論理に対する理解にも役立ちます.
  • は、すべてのロボットを許可します.User-agent:*
  • は、特定のロボットのみを許可する.spider
  • すべてのロボットをブロックします.Displalow:/
  • ロボットの特定のディレクトリへのアクセスを禁止します.
  • アンチ爬虫類(Anti-Stard)
    一般サイトは三つの方面から反爬虫類:*ユーザーから要求されたHeaders*ユーザー行為*ウェブサイトディレクトリとデータローディング方式*
    前の二つは比較的に出会いやすいです.ほとんどのサイトはこれらの角度から爬虫類を反対します.第三のいくつかのajaxのウェブサイトは採用することができて、このようにして登り取りの難度を増大しました.
    Headersを通してアンチ爬虫類
    多くのサイトでHeadersを検出しました.-User-Agent-Referer : Headers, User-Agent Headers ; Referer 。ユーザー行動に基づくアンチ爬虫類
  • は、ユーザ行動を検出することによって、
  • 同じIPで短時間で何度も同じページ
  • にアクセスします.
  • 同じアカウントで短時間に何度も同じ動作を行います.
  • :1、 , ip, ip;2、 ダイナミックページのアンチ爬虫
    上記のいくつかの状況はほとんどが静止画ページに現れています.一部のウェブサイトがあります.私たちが必要とするデータはajaxを通じて入手したり、JavaScriptを通じて生成したりします. : ajax , , json 。予備の知識が必要です.
  • Javascript及びJQuery
  • 簡単なnodejs基礎
  • httpネットワークトラッキングとURLベース
  • インストールが必要な依存ライブラリ
  • superagent
  • cherio
  • eventproxy
  • async
  • superagent
    superagentは軽いhttp側のライブラリで、nodejsの中で非常に便利なクライアント要求代理モジュールです.私達がget、postなどのネットワーク要求をしやすいです.
    チェロオ
    Nodejs版のjQueryは、ウェブページからcss selectorでデータを取得するために使用されています.使い方はjqueryと同じです.
    イベントプログラム
    eventproxyモジュールは、これらの非同期操作が完了したかどうかを管理するために、同時にN個のhttp要求を送り、その後得られたデータを利用して後期の処理を行います.完成を要求したら、自動的にあなたが提供した処理関数を呼び出して、キャプチャしたデータをパラメータとして転送して処理しやすくなります.
    async
    asyncはプロセス制御キットであり、直接で強力な非同期機能を提供しています.mapLimit(arr、limit、iterator、calback)
    強力な同期機能もあります.map Series(arr、iterator、calback)
    爬虫類の実践
    うそばかり言っていますが、始めましょう.
    依存ライブラリとグローバル変数を先に定義します.
    // node     
    const path = require('path')
    const url = require('url');
    const fs = require('fs')
    // npm      
    const superagent = require('superagent');
    const cheerio = require('cheerio');
    const eventproxy = require('eventproxy');
    const async = require('async');
    const mkdir = require('mkdirp')
    //       URL
    var targetUrl = 'https://cnodejs.org/';
    //-------- 1 ----------
    //       
    superagent.get(targetUrl)
        .end(function(err, res){
            console.log(res);
        })
    三行コード~、確かに爬虫類プログラムです.画面情報をterminalに出力します.
    ページ上のリソースを取得するためには、cherio(node版のjQuery)を使用して、ページ上で指定されたクラスまたはidの内容を選択します.だから、まず自分がページを取得したい構造を分析して、google chromeの要素選択器を開きます.
    1のプログラムをcherioに参加してurlsを取得します.
    // -------- 2 ----------
    //   cheerio        
    
    superagent.get(targetUrl)
        .end(function(err, res){
            var $ = cheerio.load(res.text);
            $('#topic_list .topic_title').each(function(index, element){
            var href = $(element).attr('href');
            console.log(href);
            })
        })
    出力:
    これは全部相対パスですか?腫れはどうすればいいですか?急がないでください.urlモジュールがあります.
    // ---------- 3 -------------
    var href = url.resolve(targetUrl, $(element).attr('href'));
    続いてプログラム出力を実行します.
    urlsを取得するのは最初のステップだけです.ここでurlsが指すページの内容を取得します.例えば、二次ページのタイトルと最初のコメントを取得してから印刷します.
    ここでは、eventproxyモジュールに参加して、指定回数非同期を優雅に制御した後、コールバック関数を実行します.
    // ---------- 4 -------------
    //   eventproxy        
    
    var topicUrls = []; 
    
    function getTopicUrls() {
        // ----3----      
    };
    getTopicUrls()
    var ep = new eventproxy();
    // eventproxy           
    ep.after('crawled', topicUrls.length, function(topics) {
        topics = topics.map(function(topicPair) {
            var topicUrl = topicPair[0];
            var topicHtml = topicPair[1];
            var $ = cheerio.load(topicHtml);
            return ({
                title: $('.topic_full_title').text(),
                href: topicUrl,
                comment1: $('.reply_content .markdown-text').eq(0).text().trim()
            });
        });
        console.log('outcome');
        console.log(topics);
    });
    
    topicUrls.forEach(function(topicUrl) {
        superagent.get(topicUrl)
            .end(function(err, res){
                console.log('fetch--' + topicUrl + '--successfully');
                // eventproxy   after  ,       ,        ,        
                ep.emit('crawled', [topicUrl, res.text]);
            });
    });
    出力:
    えっと、ここはoutcomeが空いていますか?何の鬼ですか?またコードを確認しました.shit非同期をコントロールしていないので、topicUrls.forEach()を実行する時、topicUrlsは空です.もちろんですか?全部ありません.神器promiseに加入して改良してみましょう.
    //---------- 5 -----------
    //   promise  
    var topicUrls = []; 
    function getTopicUrls() {
        return new Promise(function(resolve){
            ... //   ----4----    
        });
    };
    getTopicUrls().then(function(topicUrls){
        ... //   ----4----    
    })
    出力:
    サプライズが来ました.ページには私たちが望むタイトル、url、コメントが出ていますが、予想に合わない点があります.出力ログをよく見てください.空いている対象文字列がたくさんあります.
    ページはアクセスできませんか?
    私達はこの出力していないページをコピーして、ブラウザで車を返すと、ページは実際にアクセスできます.ブラウザでは一回だけお願いします.爬虫類プログラムでは、nodeの高合併特性のため、同じ時間に何度もお願いします.サーバーの負荷を超えると、サーバーが崩壊します.ですから、サーバーには一般的にアンチ爬虫法がありますが、私たちはたまたまこのような状況に遭遇しました.どうやって証明しますか?私たちはurlsが指すすべてのページを直接出力します.(tips:terminalは出力が多すぎるので、linuxコマンド_;lessを使って、出力ログのページをめくることを制御できます.)
    出力をよく見ると、すぐに以下のログが見つかります.
    ページは全部503です.つまりサーバーは私たちの訪問を拒否しました.
    私達は私達のプログラムを改良して、asyncは登場して、プログラムの合併を制御して、そして遅延を設定します.
    // ---------- 6 -----------
    //     ,     5
    //              
    
    var topicUrls = []; 
    function getTopicUrls() {
        return new Promise(function(resolve){
            superagent.get(targetUrl)
                .end(function(err, res){
                    if (err) {
                        return console.log('error:', err)
                    }
                    var $ = cheerio.load(res.text);
                    $('#topic_list .topic_title').each(function(index, element){
                        var href = url.resolve(targetUrl, $(element).attr('href'));
                        topicUrls.push(href);
                        resolve(topicUrls);
                    })
                });
        });
    };
    getTopicUrls().then(function(topicUrls){
        var ep = new eventproxy();
        ep.after('crawled', topicUrls.length, function(topics) {
            topics = topics.map(function(topicPair) {
                var topicUrl = topicPair[0];
                var topicHtml = topicPair[1];
                var $ = cheerio.load(topicHtml);
                return ({
                    title: $('.topic_full_title').text(),
                    href: topicUrl,
                    comment1: $('.reply_content .markdown-text').eq(0).text().trim()
                });
            });
            console.log('------------------------ outcomes -------------------------');
            console.log(topics);
            console.log('        ' + topics.length + ' ')
        });
        var curCount = 0;
        //     
        function concurrentGet(url, callback) {
            var delay = parseInt((Math.random() * 30000000) % 1000, 10);
            curCount++;
            setTimeout(function() {
                console.log('       ', curCount, ',      ', url, ',  ' + delay + '  ');  
                superagent.get(url)
                    .end(function(err, res){
                        console.log('fetch--' + url + '--successfully');
                        ep.emit('crawled', [url, res.text]);
                    });
                curCount--;
                callback(null,url +'Call back content');
            }, delay);
        }
    
        //   async          
        // mapLimit(arr, limit, iterator, [callback])
        //     
        async.mapLimit(topicUrls, 5 ,function (topicUrl, callback) {
                concurrentGet(topicUrl, callback);
            });
    })
    出力ログをもう一度見てください.
    強迫症の患者は気持ちがいいです.
    ちょっと待ってください.もう一つのポイントがあります.多くのプログラム猿オタクにとって(ここでは万字を省略します)、上の基礎があります.爬虫類とストレージを実現するのも簡単です.例えば、先ほど話した例の中の二級ページの作者の顔写真をダウンロードします.ページを分析する手順は多く説明を加えなくて、直接コードをつけます.
    var dir = './images'
    //         
    mkdir(dir, function(err) {
        if(err) {
            console.log(err);
        }
    });
    
    //---------- 7 -----------
    //     ,     5
    //     
    
    var topicUrls = []; 
    function getTopicUrls() {
        return new Promise(function(resolve){
            ...//   ---6---
        });
    };
    getTopicUrls().then(function(topicUrls){
        var ep = new eventproxy();
        ep.after('crawled', topicUrls.length, function(topics) {
            var imgUrls = []
            topics = topics.map(function(topicPair) {
                ...//   ---6---
                imgUrls.push($('.user_avatar img').attr('src'));
            });
            //                          ,    async.mapSeries    
            async.mapSeries(imgUrls, function (imgUrl, callback) {
                //      
                const stream = fs.createWriteStream(dir + '/' + path.basename(imgUrl) + '.jpg');
                const res = superagent.get(imgUrl);
                // res.type('jpg')
                res.pipe(stream);
                console.log(imgUrl, '--    ');
                callback(null, 'Call back content');
            });
            console.log('------------------------ outcomes -------------------------');
            console.log('        ' + topics.length + ' ');
        });
        var curCount = 0;
        //     
        function concurrentGet(url, callback) {
            ...//   ---6---
        }
        //   async       
        async.mapLimit(topicUrls, 5 ,function (topicUrl, callback) {
                concurrentGet(topicUrl, callback);
            });
    })
    
    プログラムを実行すると、イメージフォルダの下に写真がたくさんあります.