快速抓取网页文章标题和内容,适合node.js爬虫使用,服务于ElasticSearch。
In my case, the speed of spider is about 700 thousands documents per day, 22 million per month, and the maximize crawling speed is 450 per minute, avg 80 per minute, the memory cost are about 200 megabytes on each spider kernel, and the accuracy is about 90%, the rest 10% can be fixed by customizing Score Rules or Selectors. it's better than any other readability modules.

Server infos: * 20M bandwidth of fibre-optical * 8 Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz cpus * 32G memory
npm install read-art --production
read(html/uri [, options], callback)
It supports the definitions such as: * html/uri Html or Uri string. * options An optional options object, including: - output The data type of article content, head over to Output to get more information. - killBreaks A value indicating whether or not kill breaks, blanks, tab symbols(\r\t\n) into one `
,trueby default.
- **minTextLength** If the content is less than[minTextLength]characters, don't even count it,25by default.
- **minParagraphs** A number indicates whether or not take the top candidate as a article candidate,3by default, i.e.: IftopCandidatedom has more than3
children,topCandidatewill be considered as the article dom, otherwise, it will be the parent oftopCandidate(not).
- **tidyAttrs** Remove all the attributes on elements,falseby default.
- **dom** Will return the whole cheerio dom when this property is set totrue,falseby default, try to useart.domto get the dom object in callback function.
- **damping** The damping to calculate score of parent node,1/2by default. e.g.: the score of current document node is20, the score of parent will be20 * damping.
- **scoreRule** Customize the score rules of each node, one arguments will be passed into the callback function, [read more](#score_rule).
- **selectors** Customize the data extract [selectors](#selectors).
- **imgFallback** Customize the way to get source of image, [read more](#imgfallback).
- **thresholdScore** A number/function indicates whether or not drop the article content, [read more](#threshold_score).
- **thresholdLinkDensity** A0~1decimal indicates whether or not drop the article content, [read more](#threshold_linkdensity).
- **options from [cheerio](https://github.com/cheeriojs/cheerio)**
- **options from [req-fast](https://github.com/Tjatse/req-fast)**
* **callback** Fire after the article has been crawled -callback(error, article, options, response), arguments are:
- **error**Errorobject when exception has been caught.
- **article** The article object, including:article.title,article.contentandarticle.html.
- **options** The request options.
- **response** The response of your request, including:response.headers,response.redirects,response.cookiesandresponse.statusCode`.
Head over to test or examples directory for a complete example.
var read = require('read-art');
// read from google:
read('http://google.com', function(err, art, options, resp){
if(err){
throw err;
}
var title = art.title, // title of article
content = art.content, // content of article
html = art.html; // whole original innerHTML
console.log('[STATUS CODE]', resp && resp.statusCode);
});
// or:
read({
uri: 'http://google.com',
charset: 'utf8'
}, function(err, art, options, resp){
});
// what about html?
read('<title>node-art</title><body>
hello, read-art!
</body>', function(err, art, options, resp){
});
// of course could be
read({
uri: '<title>node-art</title><body>
hello, read-art!
</body>'
}, function(err, art, options, resp){
});
CAUTION: Title must be wrapped in a <title> tag and content must be wrapped in a <body> tag.
With High Availability: spider2
In some situations, we need to customize score rules to crawl the correct content of article, such as BBS and QA forums.
There are two effective ways to do this:
- minTextLength
It's useful to get rid of useless elements (P / DIV), e.g. minTextLength: 100 will dump all the blocks that node.text().length is less than 100.
javascript:
scoreRule: function(node){
if (node.hasClass('w740')) {
return 100;
}
}The elements which have the w740 className will get 100 bonus points, that will make the node to be the topCandidate, which means it's enough to make the text of DIV/P.w740 to be the content of current article.
read('http://club.autohome.com.cn/bbs/thread-c-66-37239726-1.html', {
minTextLength: 0,
scoreRule: function(node){
if (node.hasClass('w740')) {
return 100;
}
}
}, function(err, art){
});
Some times we wanna extract article somehow, e.g.: pick the text of .article>h3 as title, and pick .article>.author as the author data:
read({
html: '<title>read-art</title><body>
<h3 title="--read-art--">Who Am I</h3>
hi, dude, i am <b>readability</b>
aka read-art...
<small class="author" data-author="Tjatse X">Tjatse</small>
</body>',
selectors: {
title: {
selector: '.article>h3',
extract: ['text', 'title']
},
content: '.article p.section1',
author: {
selector: '.article>small.author',
extract: {
shot_name: 'text',
full_name: 'data-author'
}
}
},
}, function (err, art) {
// art.title === {text: 'Who Am I', title: '--read-art--'}
// art.content === 'hi, dude, i am <b>readability</b>'
// art.author === {shot_name: 'Tjatse', full_name: 'Tjatse X'}
});
Properties:
- selector the query selector, e.g.: #article>.title, .articles:nth-child(3)
- extract the data that you wanna extract, could be String, Array or Object.
Notes The binding data will be an object or array (object per item) if the extract option is an array object, title and content will override the default extracting methods, and the output of content depends on the output option.
Should be one of following types:
- Boolean Fallback to img.src = (node.data('src') || node.attr('data-src')) when set to true.
- String Customize the attribute name, it will take node.attr([imgFallback]) as src of img.
- Function Give users maximum customizability and scalability of source attribute on img, e.g.:
javascript
imgFallback: function(node){
return node.attr('base') + '/' + node.attr('rel-path');
}
read({
imgFallback: true
}, function(err, art){});
read({
imgFallback: 'the-src-attr'
}, function(err, art){});
read({
imgFallback: function(node){
return 'http://img-serv/' + node.attr('relative-path');
}
}, function(err, art){});
Customize the threshold of anchors and nodes' scores.
The thresholdScore is a threshold number which to identify whether or not to discard children of top candidate directly (skip deeper tag/text/link density checking), should be one of following types:
- Number A finite number.
- Function Calculate the threshold score by yourself, two arguments are passing in:
- node The top candidate (mostly like article dom).
- scoreKey The data key to storage score, you can get score by node.data(scoreKey).
After read-art got the top candidate, it starts to analyze the children of top candidate, if the score of current child is greater than thresholdScore, the child will be appended to article body directly.
Math.max(10, topCandidate.data(scoreKey) * 0.2) by default.
read({
thresholdScore: 20
}, function(err, art){});
read({
thresholdScore: function(node, scoreKey){
return Math.max(10, node.data(scoreKey) * 0.2);
}
}, function(err, art){});
thresholdLinkDensity is used to identify whether current child of top candidate is a navigator, ad or relative-list, 0.25 by default, so if the text length of anchors in current child devides by text length of top candidate is greater than thresholdLinkDensity, the child will be discarded.
read({
thresholdLinkDensity: 0.25
}, function(err, art){});
We're using different regexps to iterates over elements (cheerio objects), and removing undesirable nodes.
read.use(function(){
//[usage]
});
The [usage] could be one of following:
- this.reset()
Reset the settings to default.
- this.skipTags([tags], [override])
Remove useless elements by tagName, e.g. this.skipTags('b,span'), if [override] is set to true, skiptags will be "b,span", otherwise it will be appended to the origin, i.e. :
aside,footer,label,nav,noscript,script,link,meta,style,select,textarea,iframe,b,span
this.regexps.positive([re], [override])
If positive regexp test id + className of node success, it will be took as a candidate. [re] is a regexp, e.g. /dv101|dv102/ will match the element likes `...or
..., if[override]is set totrue,positivewill be/dv101|dv102/i`, otherwise it will be appended to the origin, i.e. :
/article|blog|body|content|entry|main|news|pag(?:e|ination)|post|story|text|dv101|dv102/i
this.regexps.negative([re], [override])
If negative regexp test id + className of node success, it will not be took as a candidate. [re] is a regexp, e.g. /dv101|dv102/ will match the element likes `...or
..., if[override]is set totrue,negativewill be/dv101|dv102/i`, otherwise it will be appended to the origin, i.e. :
/com(?:bx|ment|-)|contact|comment|captcha|foot(?:er|note)?|link|masthead|media|meta|outbrain|promo|related|scroll|shoutbox|sidebar|sponsor|util|shopping|tags|tool|widget|tip|dialog|copyright|bottom|dv101|dv102/i
this.regexps.unlikely([re], [override])
If unlikely regexp test id + className of node success, it probably will not be took as a candidate. [re] is a regexp, e.g. /dv101|dv102/ will match the element likes `...or
..., if[override]is set totrue,unlikelywill be/dv101|dv102/i`, otherwise it will be appended to the origin, i.e. :
/agegate|auth?or|bookmark|cat|com(?:bx|ment|munity)|date|disqus|extra|foot|header|ignore|link|menu|nav|pag(?:er|ination)|popup|related|remark|rss|share|shoutbox|sidebar|similar|social|sponsor|teaserlist|time|tweet|twitter|\bad[\s_-]?\b|dv101|dv102/i
this.regexps.maybe([re], [override])
If maybe regexp test id + className of node success, it probably will be took as a candidate. [re] is a regexp, e.g. /dv101|dv102/ will match the element likes `...or
..., if[override]is set totrue,maybewill be/dv101|dv102/i`, otherwise it will be appended to the origin, i.e. :
/and|article|body|column|main|column|dv101|dv102/i
this.regexps.div2p([re], [override])
If div2p regexp test id + className of node success, all divs that don't have children block level elements will be turned into p's. [re] is a regexp, e.g. /<(span|label)/ will match the element likes <span>... or <label>..., if [override] is set to true, div2p will be /<(span|label)/i, otherwise it will be appended to the origin, i.e. :
/<(a|blockquote|dl|div|img|ol|p|pre|table|ul|span|label)/i
this.regexps.images([re], [override])
If images regexp test src attribute of node success, it will be picked as a normal img, otherwise will be dropped. [re] is a regexp, e.g. /\.(gif|jpe?g|png)$/i will match the image that src likes /path/to/foo.jpg, if [override] is set to true, readart.regexps.images will be replaced by `[r
$ claude mcp add node-readability \
-- python -m otcore.mcp_server <graph>