最近在兼职做爬虫,同事在做越南的需求时,说起用cheerio的find找不到子元素。
页面请戳此处。一开始看到这个页面时,最明显的特征就是好多注释,又有同事说之前抓取facebook的数据的时候也常遇到find不到的情况,正则去掉注释后就可以了,所以当时把注意力集中在注释。
尝试在全文通过class查找之前find要查找的元素,是找的到的。
简化问题如下:
$parent.find(elem) --> not found
$(elem) --> found
查找cheerio的find方法:
exports.find = function(selectorOrHaystack) {
var elems = _.reduce(this, function(memo, elem) {
return memo.concat(_.filter(elem.children, isTag));
}, []);
var contains = this.constructor.contains;
var haystack;
if (selectorOrHaystack && typeof selectorOrHaystack !== 'string') {
if (selectorOrHaystack.cheerio) {
haystack = selectorOrHaystack.get();
} else {
haystack = [selectorOrHaystack];
}
return this._make(haystack.filter(function(elem) {
var idx, len;
for (idx = 0, len = this.length; idx < len; ++idx) {
if (contains(this[idx], elem)) {
return true;
}
}
}, this));
}
return this._make(select(selectorOrHaystack, elems, this.options));
};
发现经过重重的if,最后落在了this._make(select(selectorOrHaystack, elems, this.options));
,_make只是制作cheerio object的一个函数,而select则是css-select
这个模块的方法,到此处我就疑惑了,难道elem不是$parent的子元素,因为cheerio本身$(elem)也是通过select来查找的。继续追踪select方法:
var selectAll = getSelectorFunc(function selectAll(query, elems){
return (query === falseFunc || !elems || elems.length === 0) ? [] : findAll(query, elems);
});
function CSSselect(query, elems, options){
return selectAll(query, elems, options);
}
findAll又是什么来历?原来是css-select
的依赖domUtil
的方法
function findAll(test, elems){
var result = [];
for(var i = 0, j = elems.length; i < j; i++){
if(!isTag(elems[i])) continue;
if(test(elems[i])) result.push(elems[i]);
if(elems[i].children.length > 0){
result = result.concat(findAll(test, elems[i].children));
}
}
return result;
}
到此处,之前的疑惑越来越大,由于它是遍历children来递归查找的,意味着elem可能在此处判断为不是$parent的子元素。
为了证实这个猜想,机(yu)智(chun)的我于是用util.inspect() 方法将解析出来的dom obj打印了出来:
Anh Lương đi làm xa nhà tháng mới về một lần, hôm nay nghe tin anh Lương về lòng chị Ví thấy vui vui lạ. Chiều nay nghe tin anh Lương về, lòng chị Ví bỗng thấy vui lạ. Hai…
many hentry under
many hentry under
many hentry under
以上是出了问题的html,略长== 以下是我通过util.inspect() 打印出的部分,注意看.entry-summary是个注释,它的前一个兄弟元素居然是#main
{ data: ' .entry-summary ',
type: 'comment',
next: [Circular],
prev:
{ type: 'tag',
name: 'div',
attribs: { id: 'main' },
children:
[ [Object],
[Object],
[Object],
[Object],
[Object],
[Object],
[length]: 6 ],
next: [Circular],
prev:
{ data: '\n\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t',
type: 'text',
next: [Circular],
prev: [Object],
parent: [Circular] },
parent: [Circular] },
parent: [Circular] }
因为解析html是在htmlparser2这个库里进行的,于是我就去向作者提了个issue,当时的想法是:注释怎么可能是#main的兄弟节点,一定是bug== , 后来作者回复如下:
虽然回复如此,但是我看了以下domhandler,发现它只是组装dom object而已,并不涉及具体的parser过程,带着疑惑,我继续追查下去。
这次我学聪明了,既然定位到问题出在htmlparser2上,我索性把网站源码wget下来,借助htmlparser2的接口,在openTag和closeTag的时候打印出节点情况,结果发现:
Anh Lương đi làm xa nhà tháng mới về một lần, hôm nay nghe tin anh Lương về lòng chị Ví thấy vui vui lạ. Chiều nay nghe tin anh Lương về, lòng chị Ví bỗng thấy vui lạ. Hai…