我有这样一个页面:
<html>
<body>
<table>
<thead>
<tr>
<th>Link</th><th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://www.google.com">Google</a></td><td>Search engine</td>
</tr>
<tr>
<td><a href="https://github.com">Github</a></td><td>Code management</td>
</tr>
</tbody>
</table>
</body>
</html>
我想解析表格的每一行,并按照每个链接(获取HTML页面标题)来创建一个像这样的网站数组:
[ { name: 'Google',
title: 'Google',
descr: 'Search engine' },
{ name: 'Github',
title: 'GitHub · Where software is built',
descr: 'Code management' } ]
我认为这是使用Promises和Q库开始学习的一个很好的例子,但我没有理解promises是如何工作的。 在我写的代码下面:
var request = require('request');
var cheerio = require('cheerio');
var Q = require('q');
var sites = [];
var loadPage = function(url){
var deferred = Q.defer();
request(url, function (error, response, html) {
if (!error && response.statusCode == 200) {
var $ = cheerio.load(html);
deferred.resolve($);
} else {
deferred.reject(new Error(error));
}
});
return deferred.promise;
}
var parseRows = function($){
var promises = [];
$("tbody tr").each(function(){
var $cells = $('td', this);
var $firstC = $cells.eq(0);
var name = $firstC.text();
var link = $firstC.find('a').attr('href');
var descr = $cells.eq(1).text();
promises.push(Q.fcall(function () {
var site = {name: name, descr: descr};
loadPage(link).then(function($){
var title = $("title").text();
console.log(title);
// here I don't know how to set the title
// as obj's attribute
});
return site;
}));
});
return Q.all(promises);
}
var displayTitles = function(res){
for (var i = 0, len = res.length; i < len; i++) {
var obj = res[i];
}
return Q.fcall(function () {
return sites;
});
}
loadPage('http://127.0.0.1/sample.html')
.then(parseRows)
.then(displayTitles)
.done();
我对loadPage功能感到满意,但我仍然坚持使用parseRows,因为我无法将标题设置为&#34; site&#34;宾语。此外,displayTitles最初是为了处理获取页面标题所需的逻辑而开发的,但现在几乎没用了。
我如何修改上面的代码,以便以更干净和可读的方式将所需的数组作为输出?
答案 0 :(得分:0)
我认为您的主要问题是Q.fcall
即时解决,而不是pageLoad
解决后。一点重组应该有所帮助:
var promises = [];
// ...
$("tbody tr").each(function(){
// ..
promises.push(loadPage(link).then(function($){
var site = {name: name, descr: descr};
site.title = $("title").text();
return site;
}));
});
return Q.all(promises);
至于如何进一步压缩代码,你可以试试这个:
var parseRows = function ($) {
return Q.all($("tbody tr").map(function () {
var $cells = $('td', this);
var $firstC = $cells.eq(0);
var name = $firstC.text();
var link = $firstC.find('a').attr('href');
var descr = $cells.eq(1).text();
return loadPage(link).then(function ($) {
// are you sure there is a TITLE element? Did you perhaps mean .title?
return {name: name, descr: descr, title: $("title").text()};
});
});
};
我不知道你想在displayTitles
函数中实现什么,所以我无法帮助那里。但我很确定你不需要额外的Q.fcall
包装器。根据{{1}}(据我记得),你应该能够简单地Promises
来解决。此外,个人而言,我坚持使用原生Promises API,但最近所有浏览器都支持IE(http://caniuse.com/#search=promise),但似乎您仍在使用节点。
答案 1 :(得分:0)
在使用Q框架后,我决定遵循@lordvald建议,然后切换到原生的Promises API。下面是回答我问题的代码:
var request = require('request')
var cheerio = require('cheerio')
var loadPage = function(url) {
var promise = new Promise(function(resolve, reject) {
request(url, function(error, response, html) {
if (!error && response.statusCode == 200) {
resolve(cheerio.load(html))
} else {
reject(new Error(error))
}
})
})
return promise
}
var parseRows = function($) {
return $('tbody tr').map(function() {
var $cells = $('td', this)
var firstC = $cells.eq(0)
var url = firstC.eq(0).find("a").attr("href")
return {
name: firstC.text(),
descr: $cells.eq(1).text(),
url: url
}
}).get()
}
var loadSiteTitle = function(sites) {
return Promise.all(sites.map(function(site) {
return loadPage(site.url).then(function($) {
site.title = $("title").text()
delete site.url
return site
})
}))
}
loadPage('http://127.0.0.1/sample.html')
.then(parseRows)
.then(loadSiteTitle)
.then(function(sites) {
console.log(sites)
})
.catch(function(e) {
console.log('Unexpected error: ' + e.message)
process.exit(1)
})