一个用来爬小说的简单的NODE.JS爬虫

Posted 前端大牛爱好者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个用来爬小说的简单的NODE.JS爬虫相关的知识,希望对你有一定的参考价值。

小说就准备点天下霸唱和南派三叔的系列,本人喜欢看,而且数据也好爬。

貌似因为树大招风的原因,这两作者的的书被盗版的很多,乱改的也多。

然后作者就直接在网上开放免费阅读了,还提供了官网,猜想作者应该是允许爬虫来爬内容的。

《盗墓笔记》和《鬼吹灯》系列这两官网从第一眼的界面风格来看还差不多,最后发现还真是一个队伍开发的,服务器都是一个。

因为最开始爬数据的时候两次请求之间没有间隔时间,请求太频繁了,然后突然就没法访问了。

立马反映过来是不是因为服务器端的保护措施,导致被封IP了。

然后在别的电脑上和手机上都还能继续访问,发现还真是被封IP了,大约要等30分钟才能解封。

而且要是在爬《盗墓》数据的时候被封IP,访问《鬼吹灯》的站点也是被封了的,哈哈。

后来每次爬章节内容的时候都间隔500毫秒,就没有被封过了,这个间隔感觉还可以更短些,只要不影响其他读者的正常访问都是允许的吧。

爬数据的同时可以ping站点,若一直有返回,IP就没被封。

页面的结构很简单很语义化。每本小说的章节目录部分html结构都是这样的:

一个用来爬小说的简单的NODE.JS爬虫

章节内容的结构:

一个用来爬小说的简单的NODE.JS爬虫

获取数据非常方便,对爬虫很友好,中国好网站!

下面说下这个爬虫:

最开始准备直接用node.js + cheerio + request就搞定,爬数据不需要提供接口访问,甚至连express都不需要,直接做成一个命令行工具。

最后想了想,还是不太行,因为后面数据爬下来以后,还要给APP端提供小说接口,所以还是需要一个完整的server端,而且还要和数据库交互,能有ORM最好,免得直接写SQL。

于是想起了两年前曾经使用过得ThinkJS,现已经更新到2.2.x版本了。

这是国内的一个基于Node.js的MVC框架。

相比于Express或Koa来说提供了更强大和完善的功能,应该把它和Sails一起比较。多的就不介绍了,官网文档很全面。

先整理一个小说的条目,两个作者的小说加起来大致有28本:

// book.js
export default {
    /**
     * 天下霸唱 19本
     */

    guichuideng_1: {
        id1,
        name'鬼吹灯1之精绝古城',
        uri'http://www.guichuideng.org/jing-jue-gu-cheng',
        author'天下霸唱',
        publish_date'2006-09',
        publisher'安徽文艺出版社',
        cover'guichuideng_1',
    },
    guichuideng_2: {
        id2,
        name'鬼吹灯2之龙岭迷窟',
        uri'http://www.guichuideng.org/long-ling-mi-ku',
        author'天下霸唱',
        publish_date'2006-11',
        publisher'安徽文艺出版社',
        cover'guichuideng_2',
    },
    guichuideng_3: {
        id3,
        name'鬼吹灯3之云南虫谷',
        uri'http://www.guichuideng.org/yun-nan-chong-gu',
        author'天下霸唱',
        publish_date'2006-11',
        publisher'安徽文艺出版社',
        cover'guichuideng_3',
    },
    guichuideng_4: {
        id4,
        name'鬼吹灯4之昆仑神宫',
        uri'http://www.guichuideng.org/kun-lun-shen-gong',
        author'天下霸唱',
        publish_date'2006-12',
        publisher'安徽文艺出版社',
        cover'guichuideng_4',
    },
    // ......  省略
}

ThinkJS支持从命令行访问接口,这里直接把爬虫的实现做到了controller里,可以从命令行来直接调用这个接口,在package.json的scripts加一个命令可就能通过npm来调用。

Node.js虽然在7.x以后都支持原生es6/7书写,但是还是需要harmony和谐模式来运行才可以,要不然一样报语法错误。

而TinkJS运行前是先将src的代码用babel编译到app文件夹内再跑服务,实际运行的是降级编译后的js代码,所以es6/7语法可以随心所欲的写,而不用担心兼容问题。

用ThinkJS命令行工具初始化了一个项目,并加入一个Npm命令:

 "spider""npm run compile && node www/production.js spider/index"

controller:

'use strict';

/**
 * spider controller
 */


import Base from './base.js';

import rp from 'request-promise';
import cheerio from 'cheerio';
import books from './spider/book';
import {sleep, log} from './spider/tool';

export default class extends Base {
    indexAction (){
        if (this.isCli()){
            this.checked = false;
            this.spiderModel = this.model('book');
            this.chapterModel = this.model('chapter');
            this.crawlBook();
        } else {
            this.fail('该接口只支持在命令行调用~~~');
        }
    }

    async crawlBook (isCheck){
        log('小说目录插入开始...');
        // 小说先存入书籍表
        var boookArr = [];
        for (var x in books){
            boookArr.push(books[x]);
        }
        await this.spiderModel.addBookMany(boookArr);
        log('小说目录插入完成...');
        log('小说内容抓取开始...');
        // 循环抓取小说目录
        for (var key in books){
            var {id, name, uri} = books[key];
            var bookId = id;
            log(name + ' [章节条目抓取开始...]');
            try {
                var $ = await rp({
                    uri,
                    transformbody => cheerio.load(body)
                });
                var $chapters = $('.container .excerpts .excerpt a'); // 所有章节的dom节点
                var chapterArr = []; // 存储章节信息
                log(name + ' [章节条目如下...]');
                $chapters.each((i, el) => {
                    var index = i + 1;
                    var $chapter = $(el); // 每个章节的dom
                    let name = $chapter.text().trim();
                    var uri = $chapter.attr('href');
                    log(name + ' ' + uri);
                    chapterArr.push({bookId, index, name, uri});
                });
            } catch (e){
                return log(e.message, 1);
            }

            log(name + ' [章节条目抓取完毕,开始章节内容抓取...]');

            // 循环抓取章节内容
            for (var i = 0,len = chapterArr.length;i < len;i ++){
                var chapter = chapterArr[i];
                // 先查询该章节是否已存在
                // 爬取的途中断掉或者卡住了,再次启动蜘蛛的时候已存在的章节就不必再爬了
                var res = await this.chapterModel.findChapter(chapter.name);
                if (!think.isEmpty(res)){
                    log(name + ' [章节已存在,忽略...]');
                    continue;
                }
                await sleep(500);
                await this.crawlChapter(chapter);
            }
        }
        this.checked = isCheck;

        // 再检测一遍是否有遗漏
        !this.checked && this.crawlBook(1);
    }

    async crawlChapter ({bookId, index, name, uri}){
        try {
            log(name + ' [章节内容抓取开始...]');
            var $ = await rp({
                uri,
                transformbody => cheerio.load(body)
            });
            var $content = $('.article-content'); // 只取正文内容
            $('.article-content span').remove(); // 干掉翻页提示
            var content = '   ' + $content.text().trim(); // 提取纯文本(不需要html标签,但保留换行和空格)
            await this.chapterModel.addChapter({bookId, index, name, content, uri});
            log(name + ' [章节内容抓取完毕,已写入数据库...]');
        } catch (e){
            log(e.message, 1);
        }
    }
}

爬虫很简单,就是根据book.js的小说条目,先在数据库的小说条目表写入所有小说数据。

然后遍历条目,先爬到某小说的章节条目数据,再爬每个章节的内容数据写入到数据的章节表中,完成后继续爬下一本小说的数据。

由于这些小说都是出版定稿了的,也不需要定时器来定时爬,和爬新闻等数据还是有区别。

这个爬虫基本是一次性的,数据完整地爬完一次就没意义了。

章节表中的bookId和小说表中的id关联,表示该章节属于哪本小说。

章节表中的index表示该章节是它所在小说的第几章节(序列)。

给APP端提供的小说章节目录接口,只需要小说id就行。

章节内容接口,需要小说id和章节的序列index参数就能取到数据。

model:

'use strict';

/**
 * book model
 */


export default class extends think.model.base {
    /**
     * 删除某个书籍
     * @param id 要删除的书籍id
     * @return {promise}
     */

    removeBookById (id){
        return this.where({id}).delete();
    }

    /**
     * 删除所有书籍
     * @param null
     * @return {promise}
     */

    removeAllBooks (){
        return this.where({id: ['>'0]}).delete();
    }

    /**
     * 单个增加书籍
     * @param book 书籍对象
     * @return {promise}
     */

    addBook (book){
        return this.add(book);
    }

    /**
     * 批量增加书籍
     * @param books 书籍对象数组
     * @return {promise}
     */

    async addBookMany (books){
        await this.removeAllBooks();
        return this.addMany(books);
    }
}


'use strict';

/**
 * chapter model
 */


export default class extends think.model.base {
    /**
     * 查询某个章节
     * @param name 章节名称
     * @return {promise}
     */

    findChapter (name){
        return this.where({name}).find()
    }

    /**
     * 单个增加章节
     * @param chapter 章节对象
     * @return {promise}
     */

    addChapter (chapter){
        return this.add(chapter);
    }

    /**
     * 批量增加章节
     * @param chapters 章节对象数组
     * @return {promise}
     */

    addChapter (chapters){
        return this.add(chapters);
    }
}

model里面提供了操作数据库的方法,传入的数据对象的key需要和表中的cloumn一致,其他的就不用管了,很方便。

命令行cd进工程目录,执行 npm run spider ,就开始写入小说条目数据,然后爬章节数据了:

一个用来爬小说的简单的NODE.JS爬虫

 一个用来爬小说的简单的NODE.JS爬虫   

因为怕封IP,每个章节之间设置了500毫秒间隔时间,再加上网络延迟等原因,28本小说全部爬完再查漏一遍还是需要一些时间的。

爬完后,总章节数有2157章,总字数就没统计了。 

https://www.cnblogs.com/rock-roll/p/6608105.html


设置星标不迷路一个用来爬小说的简单的NODE.JS爬虫


一个用来爬小说的简单的NODE.JS爬虫




有你想看的精彩一个用来爬小说的简单的NODE.JS爬虫





一个用来爬小说的简单的NODE.JS爬虫

前端大牛爱好者:每天一篇前端技术文章,不定时前端干货发送

欢迎长按(扫描)二维码关注:前端大牛爱好者

   


万水千山总是情,点个 “ 在看” 行不行


以上是关于一个用来爬小说的简单的NODE.JS爬虫的主要内容,如果未能解决你的问题,请参考以下文章

实用前端Node.js写爬虫系列之第3章:编写小说阅读器

用爬虫爬取笔趣阁小说

node.js做一个简单的爬虫,专爬网站接口

node.js做一个简单的爬虫,专爬网站接口

一个简单的小说爬虫

Java爬虫框架 | 爬小说