网络抓取要识别Web页面,并将其转换成结构化数据。比如说,你要负责升级出版社那古老的静态网站,需要把之前的页面下载下来,经过分析后提取所有图书的书名、介绍、作者和售价。你肯定不想自己手工完成这项任务,所以决定写个Node程序来做这件事。这种程序就是网络抓取器。
—— 《Node.js实战》 (第2版) P267
找个出版社的静态网页,图灵社区不就是个正好的对象吗😄,那就以Node.js实战(第2版)这本书为例吧!
1、提取图书书名
详情页中图书名称HTML代码
<h2>
Node.js实战(第2版)
</h2>
提取片段的cheerio代码如下
const html = `
<h2>
Node.js实战(第2版)
</h2>
`;
const cheerio = require('cheerio');
const $ = cheerio.load(html);
console.log($('.book-title h2').text().trim());
2、提取简介
<div class="book-intro readmore">
本书是Node.js的实战教程,涵盖了为开发产品级Node应用程序所需要的一切特性、技巧以及相关理念。 从搭建Node开发环境,到一些简单的演示程序,到开发复杂应用程序所必不可少的异步编程。第2版介绍了全栈开发者所需的全部技术,包括前端构建系统、选择Web框架、在Node中与数据库的交互、编写测试和部署Web程序,等等。
</div>
提取代码如下
$('.book-intro').text().trim();
3、提取作者
上面两个比较简单,只是热热身,作者的提取就稍微有些麻烦了。
HTML代码
<div class="book-author">
<span>
[英] 亚历克斯•杨 等 (作者)
</span>
<span>
<a href="/space/87796">吴海星</a> (译者)
</span>
</div>
可以看到有一个作者,有一个译者,怎么把他们分别提取出来呢。我们先看看下面的代码
console.log($('.book-author').children().length);
运行的结果是2,说明在book-author这个节点下面有两个子节点,这与我们看到的HTML代码相符。
用一个each函数遍历,并把它们分别打印出来:
$('.book-author').children().each((i, e)=>{
console.log($(e).text().trim());
});
再做些进一步的处理
$('.book-author').children().each((i, e)=>{
let 名字 = $(e).text().trim();
if (名字.indexOf('(作者)') != -1) {
console.log('作者:', 名字.replace(/\(作者\)/, '').trim());
}
if (名字.indexOf('(译者)') != -1) {
console.log('译者:', 名字.replace(/\(译者\)/, '').trim());
}
});
完整代码
const superagent = require('superagent');
const cheerio = require('cheerio');
const url = 'http://www.ituring.com.cn/book/1993';
superagent.get(url).end( function(err, res) {
// 抛错拦截
if (err) {
return
throw Error(err)
}
const book = {};
let $ = cheerio.load(res.text,{
decodeEntities: false
});
book.title = $('.book-title h2').text().trim();
book.intro = $('.book-intro').text().trim();
book.status = 出版状态 = $('li:contains("出版状态")').text().replace(/出版状态/, '');
$('.book-author').children().each((i, e)=>{
let 名字 = $(e).text().trim();
if (名字.indexOf('(作者)') != -1) {
book.auther = 名字.replace(/\(作者\)/, '').trim();
}
if (名字.indexOf('(译者)') != -1) {
book.translator = 名字.replace(/\(译者\)/, '').trim();
}
});
let 定价 = $('li:contains("定 价")').text().replace(/定 价/, '');
if (定价) {
book.price = 定价;
let 有电子书 = false;
let 找电子书 = $('dt').filter( function() {
let 有吗 = $(this).text().trim() === '电子书';
if (有吗 === true) {
有电子书 = true;
return true;
}
});
if (有电子书) {
book.ePrice = 找电子书.next().children('.price').text().trim();
}
}
book.tags = [];
$('.post-tag').each((i, e)=>{
book.tags.push($(e).text());
});
console.log(book);
});