Node.js实战cheerio网页抓取器

96
阿狸不歌
2019.03.22 09:48 字数 371

网络抓取要识别Web页面,并将其转换成结构化数据。比如说,你要负责升级出版社那古老的静态网站,需要把之前的页面下载下来,经过分析后提取所有图书的书名、介绍、作者和售价。你肯定不想自己手工完成这项任务,所以决定写个Node程序来做这件事。这种程序就是网络抓取器。
                           —— 《Node.js实战》 (第2版) P267

Node.js实战 封面

找个出版社的静态网页,图灵社区不就是个正好的对象吗😄,那就以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);
});