2013-07-03 69 views
0

以下代碼是對soupselect demo example的修改。 它基本上取一些HTML並打印鏈接列表並將其存儲在一個變量:如何獲取此node.js函數以返回值

crawl = function(host) 
    var select = require('soupselect').select, 
     htmlparser = require("htmlparser"), 
     http = require('http'), 
     sys = require('sys'); 

    // fetch some HTML... 
    var http = require('http'); 
    var client = http.createClient(80, host); 
    var request = client.request('GET', '/',{'host': host}); 

    var newPages = [] 

    request.on('response', function (response) { 
     response.setEncoding('utf8'); 

     var body = ""; 
     response.on('data', function (chunk) { 
      body = body + chunk; 
     }); 

     response.on('end', function() { 

      // now we have the whole body, parse it and select the nodes we want... 
      var handler = new htmlparser.DefaultHandler(function(err, dom) { 
       if (err) { 
        sys.debug("Error: " + err); 
       } else { 

        // soupselect happening here... 
        var titles = select(dom, 'a.title'); 

        sys.puts("Top stories from reddit"); 
        titles.forEach(function(title) { 
         sys.puts("- " + title.children[0].raw + " [" + title.attribs.href + "]\n"); 
         newPages.push(title.attribs.href); 
        }) 
       } 
      }); 

      var parser = new htmlparser.Parser(handler); 
      parser.parseComplete(body); 
     }); 
    }); 
    request.end(); 
} 

我真正想要的是這個函數返回newPages 我希望能夠說newPages = crawl(host);麻煩是我不知道這是否有意義或在何處放置返回語句。我看到newPages在請求結束前存在,但在請求結束後爲空。

如何讓該函數的返回值爲newPages

+3

你不能。如果可以的話,就不需要回調。 [我的答案在這裏](http://stackoverflow.com/a/14220323/218196)試圖解釋同步和異步代碼之間的區別。儘管它專注於Ajax,但解決方案適用於任何使用異步代碼執行的情況。 –

回答

1

我喜歡用request,cheerioasync模塊來抓取網站。這段代碼更短,我認爲更具可讀性。

var request = require('request'); 
var cheerio = require('cheerio'); 
var async = require('async'); 

function crawl(url, contentSelector, linkSelector, callback) { 
    var results = []; 
    var visited = {}; 

    var queue = async.queue(crawlPage, 5); // crawl 5 pages at a time 
    queue.drain = callback; // will be called when finished 

    function crawlPage(url, done) { 
     // make sure to visit each page only once 
     if (visited[url]) return done(); else visited[url] = true; 

     request(url, function(err, response, body) { 
      if (!err) { 
       var $ = cheerio.load(body); // "jQuery" 
       results = results.concat(contentSelector($)); // add something to the results 
       queue.push(linkSelector($)); // add links found on this page to the queue 
      } 
      done(); 
     }); 
    } 
} 

function getStoryTitles($) { 
    return $('a.title').map(function() { return $(this).text(); }); 
} 

function getStoryLinks($) { 
    return $('a.title').map(function() { return $(this).attr('href'); }); 
} 

crawl('http://www.reddit.com', getStoryTitles, getStoryLinks, function(stories) { 
    console.log(stories); // all stories! 
}); 

最後,您會得到一個您可能首先想要的所有故事的數組,它只是一種不同的語法。您可以更新您的功能,類似於AndyD所建議的。

未來,您將能夠使用生成器,它可以讓您在沒有回調函數的情況下獲得更接近您想要的內容。有關更多詳細信息,請參閱this article

function* crawl(url) { 
    // do stuff 
    yield story; 
} 

var crawler = crawl('http://www.reddit.com'); 
var firstStory = crawler.next(); 
var secondStory = crawler.next(); 
// ... 
+0

我以前使用Cheerio,異步和請求,這是一個很好的組合。 – AndyD

+0

很好的爬蟲代碼btw。很光滑。 Upvoted。 – AndyD

1

菲利克斯是對的,你不能。這是最接近你可以得到:

你的函數簽名函數體內更改爲

crawl = function(host, done) 

,並更新到這一點:

titles.forEach(function(title) { 
         sys.puts("- " + title.children[0].raw + " [" + title.attribs.href + "]\n"); 
         newPages.push(title.attribs.href); 
         done(newPages); 
        }) 

,那麼你可以調用爬這樣的:

var processNewPages = function(pages){ 
// do something with pages here 
... 
}; 

crawl(host, processNewPages); 
+0

在這種情況下processNewPages被抓取N次。其中N是一個固定的數字。我試圖避免遞歸。那可能嗎?以及如何通過processNewPages運行爬行N次而不用搞砸? – algorithmicCoder

+0

請參閱下面的mak代碼,它使用一個隊列並且不進行遞歸。這很好。特別是使用async.queue。在我看來,如果你做任何Node.js工作,你需要知道async.js。 – AndyD