小说就准备点天下霸唱和南派三叔的系列,本人喜欢看,而且数据也好爬。貌似因为树大招风的原因,这两作者的的书被盗版的很多,乱改的也多。然后作者就直接在网上开放免费阅读了,还提供了官网,猜想作者应该是允许爬虫来爬内容的。《盗墓笔记》和《鬼吹灯》系列这两官网从第一眼的界面风格来看还差不多,最后发现还真是一个队伍开发的,服务器都是一个。因为最开始爬数据的时候两次请求之间没有间隔时间,请求太频繁了,然后突然就没法访问了。立马反映过来是不是因为服务器端的保护措施,导致被封IP了。然后在别的电脑上和手机上都还能继续访问,发现还真是被封IP了,大约要等30分钟才能解封。而且要是在爬《盗墓》数据的时候被封IP,访问《鬼吹灯》的站点也是被封了的,哈哈。后来每次爬章节内容的时候都间隔500毫秒,就没有被封过了,这个间隔感觉还可以更短些,只要不影响其他读者的正常访问都是允许的吧。爬数据的同时可以ping站点,若一直有返回,IP就没被封。

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

章节内容的结构:

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

下面说下这个爬虫:

最开始准备直接用node.js + cheerio + request就搞定,爬数据不需要提供接口访问,甚至连express都不需要,直接做成一个命令行工具。最后想了想,还是不太行,因为后面数据爬下来以后,还要给APP端提供小说接口,所以还是需要一个完整的server端,而且还要和数据库交互,能有ORM最好,免得直接写SQL。于是想起了两年前曾经使用过得ThinkJS,现已经更新到2.2.x版本了。这是国内的一个基于Node.js的MVC框架。相比于Express或Koa来说提供了更强大和完善的功能,应该把它和Sails一起比较。多的就不介绍了,官网文档很全面。

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

   // book.js 1 export default {
 2     /**
 3      * 天下霸唱 19本
 4      */
 6     guichuideng_1: {
 7         id: 1,
 8         name: '鬼吹灯1之精绝古城',
 9         uri: 'http://www.guichuideng.org/jing-jue-gu-cheng',
10         author: '天下霸唱',
11         publish_date: '2006-09',
12         publisher: '安徽文艺出版社',
13         cover: 'guichuideng_1',
14     },
16     guichuideng_2: {
17         id: 2,
18         name: '鬼吹灯2之龙岭迷窟',
19         uri: 'http://www.guichuideng.org/long-ling-mi-ku',
20         author: '天下霸唱',
21         publish_date: '2006-11',
22         publisher: '安徽文艺出版社',
23         cover: 'guichuideng_2',
24     },
26     guichuideng_3: {
27         id: 3,
28         name: '鬼吹灯3之云南虫谷',
29         uri: 'http://www.guichuideng.org/yun-nan-chong-gu',
30         author: '天下霸唱',
31         publish_date: '2006-11',
32         publisher: '安徽文艺出版社',
33         cover: 'guichuideng_3',
34     },
36     guichuideng_4: {
37         id: 4,
38         name: '鬼吹灯4之昆仑神宫',
39         uri: 'http://www.guichuideng.org/kun-lun-shen-gong',
40         author: '天下霸唱',
41         publish_date: '2006-12',
42         publisher: '安徽文艺出版社',
43         cover: 'guichuideng_4',
44     },
45     // ......  省略
46 }

ThinkJS支持从命令行访问接口,这里直接把爬虫的实现做到了controller里,可以从命令行来直接调用这个接口,在package.json的scripts加一个命令可就能通过npm来调用。Node.js虽然在7.x以后都支持原生es6/7书写,但是还是需要harmony和谐模式来运行才可以,要不然一样报语法错误。而TinkJS运行前是先将src的代码用babel编译到app文件夹内再跑服务,实际运行的是降级编译后的js代码,所以es6/7语法可以随心所欲的写,而不用担心兼容问题。用ThinkJS命令行工具初始化了一个项目,并加入一个Npm命令:

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

controller:

 1 'use strict';
 2
 3 /**
 4  * spider controller
 5  */
 6
 7 import Base from './base.js';
 8
 9 import rp from 'request-promise';
10 import cheerio from 'cheerio';
11 import books from './spider/book';
12 import {sleep, log} from './spider/tool';
13
14 export default class extends Base {
15     indexAction (){
16         if (this.isCli()){
17             this.checked = false;
18             this.spiderModel = this.model('book');
19             this.chapterModel = this.model('chapter');
20             this.crawlBook();
21         } else {
22             this.fail('该接口只支持在命令行调用~~~');
23         }
24     }
25
26     async crawlBook (isCheck){
27         log('小说目录插入开始...');
28         // 小说先存入书籍表
29         var boookArr = [];
30         for (var x in books){
31             boookArr.push(books[x]);
32         }
33         await this.spiderModel.addBookMany(boookArr);
34         log('小说目录插入完成...');
35         log('小说内容抓取开始...');
36         // 循环抓取小说目录
37         for (var key in books){
38             var {id, name, uri} = books[key];
39             var bookId = id;
40             log(name + ' [章节条目抓取开始...]');
41             try {
42                 var $ = await rp({
43                     uri,
44                     transform: body => cheerio.load(body)
45                 });
46                 var $chapters = $('.container .excerpts .excerpt a'); // 所有章节的dom节点
47                 var chapterArr = []; // 存储章节信息
48                 log(name + ' [章节条目如下...]');
49                 $chapters.each((i, el) => {
50                     var index = i + 1;
51                     var $chapter = $(el); // 每个章节的dom
52                     let name = $chapter.text().trim();
53                     var uri = $chapter.attr('href');
54                     log(name + ' ' + uri);
55                     chapterArr.push({bookId, index, name, uri});
56                 });
57             } catch (e){
58                 return log(e.message, 1);
59             }
60
61             log(name + ' [章节条目抓取完毕,开始章节内容抓取...]');
62
63             // 循环抓取章节内容
64             for (var i = 0,len = chapterArr.length;i < len;i ++){
65                 var chapter = chapterArr[i];
66                 // 先查询该章节是否已存在
67                 // 爬取的途中断掉或者卡住了,再次启动蜘蛛的时候已存在的章节就不必再爬了
68                 var res = await this.chapterModel.findChapter(chapter.name);
69                 if (!think.isEmpty(res)){
70                     log(name + ' [章节已存在,忽略...]');
71                     continue;
72                 }
73                 await sleep(500);
74                 await this.crawlChapter(chapter);
75             }
76         }
77         this.checked = isCheck;
78
79         // 再检测一遍是否有遗漏
80         !this.checked && this.crawlBook(1);
81     }
82
83     async crawlChapter ({bookId, index, name, uri}){
84         try {
85             log(name + ' [章节内容抓取开始...]');
86             var $ = await rp({
87                 uri,
88                 transform: body => cheerio.load(body)
89             });
90             var $content = $('.article-content'); // 只取正文内容
91             $('.article-content span').remove(); // 干掉翻页提示
92             var content = '   ' + $content.text().trim(); // 提取纯文本(不需要html标签,但保留换行和空格)
93             await this.chapterModel.addChapter({bookId, index, name, content, uri});
94             log(name + ' [章节内容抓取完毕,已写入数据库...]');
95         } catch (e){
96             log(e.message, 1);
97         }
98     }
99 }

爬虫很简单,就是根据book.js的小说条目,先在数据库的小说条目表写入所有小说数据。然后遍历条目,先爬到某小说的章节条目数据,再爬每个章节的内容数据写入到数据的章节表中,完成后继续爬下一本小说的数据。由于这些小说都是出版定稿了的,也不需要定时器来定时爬,和爬新闻等数据还是有区别。这个爬虫基本是一次性的,数据完整地爬完一次就没意义了。

章节表中的bookId和小说表中的id关联,表示该章节属于哪本小说。章节表中的index表示该章节是它所在小说的第几章节(序列)。

给APP端提供的小说章节目录接口,只需要小说id就行。章节内容接口,需要小说id和章节的序列index参数就能取到数据。

model:

 1 'use strict';
 2
 3 /**
 4  * book model
 5  */
 6
 7 export default class extends think.model.base {
 8     /**
 9      * 删除某个书籍
10      * @param id 要删除的书籍id
11      * @return {promise}
12      */
13     removeBookById (id){
14         return this.where({id}).delete();
15     }
16
17     /**
18      * 删除所有书籍
19      * @param null
20      * @return {promise}
21      */
22     removeAllBooks (){
23         return this.where({id: ['>', 0]}).delete();
24     }
25
26     /**
27      * 单个增加书籍
28      * @param book 书籍对象
29      * @return {promise}
30      */
31     addBook (book){
32         return this.add(book);
33     }
34
35     /**
36      * 批量增加书籍
37      * @param books 书籍对象数组
38      * @return {promise}
39      */
40     async addBookMany (books){
41         await this.removeAllBooks();
42         return this.addMany(books);
43     }
44 }

 1 'use strict';
 2
 3 /**
 4  * chapter model
 5  */
 6
 7 export default class extends think.model.base {
 8     /**
 9      * 查询某个章节
10      * @param name 章节名称
11      * @return {promise}
12      */
13     findChapter (name){
14         return this.where({name}).find()
15     }
16
17     /**
18      * 单个增加章节
19      * @param chapter 章节对象
20      * @return {promise}
21      */
22     addChapter (chapter){
23         return this.add(chapter);
24     }
25
26     /**
27      * 批量增加章节
28      * @param chapters 章节对象数组
29      * @return {promise}
30      */
31     addChapter (chapters){
32         return this.add(chapters);
33     }
34 }

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

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

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

有了小说数据以后,就可以为自用小说阅读APP提供内容了。当然了,侵犯著作权的事情是不能干的哦。

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

用Node.js写一个爬虫来爬小说相关推荐

  1. 用Node+wechaty写一个爬虫脚本每天定时给女朋友发微信暖心话

    点击上方"前端小苑",选择"置顶公众号" 精品技术文章,热门资讯第一时间送达 wechatBot 微信每日说,每日自动发送微信消息给你心爱的人 项目介绍 灵感来 ...

  2. 用Node+wechaty写一个爬虫脚本每天定时给女(男)朋友发微信暖心话

    wechatBot 微信每日说,每日自动发送微信消息给你心爱的人 项目介绍 灵感来源 在掘金看到了一篇<用Node + EJS写一个爬虫脚本每天定时女朋友发一封暖心邮件>后, 在评论区偶然 ...

  3. 使用Node.js写一个简单的api接口

    引入Http模块 默认你已经安装了Node.js Node当中内置了Http模块: 可以使用 var http= require("http"); 复制代码 引入http模块: H ...

  4. 用 Node.js 写一个多人游戏服务器引擎

    英文原文:https://www.smashingmagazine.com/2018/12/multiplayer-text-adventure-engine-node-js/ 摘要 听说过文字冒险游 ...

  5. 用 Node.js 写一个多人游戏服务器引擎 1

    翻译:疯狂的技术宅 原文: https://www.smashingmagazine.... 本文首发微信公众号:jingchengyideng 欢迎关注,每天都给你推送新鲜的前端技术文章 摘要 听说 ...

  6. 用Python写一个爬虫,爬取双色球开奖记录

    好的,下面是一个简单的爬虫代码,它爬取了双色球开奖记录: import requests from bs4 import BeautifulSoupurl ="http://kaijiang ...

  7. python写一个爬虫、爬取网站漫画信息_python爬取漫画

    原博文 2017-05-31 00:56 − 抓取漫画的网址是:sf互动传媒 抓取漫画的由来也是看了知乎上有人说用爬取漫画,然后自己也玩玩 首页中每个漫画的url是类似这样存储的: 相关推荐 2019 ...

  8. node.js写一个json服务

    待续 转载于:https://www.cnblogs.com/progfun/p/4212099.html

  9. 用 Python + itchat 写一个爬虫脚本每天定时给女朋友发微信暖心话

    项目介绍: 灵感来源 在掘金看到了一篇<用Node+wechaty写一个爬虫脚本每天定时给女(男)朋友发微信暖心话>后,我就想为什么不用 Python 去实现这个功能呢. JUST TO ...

  10. python可抓取数据包括什么_Python对JS型数据抓取有什么特别好的方法吗,pythonjs型抓取,想写一个爬虫,但是需要抓...

    Python对JS型数据抓取有什么特别好的方法吗,pythonjs型抓取,想写一个爬虫,但是需要抓 想写一个爬虫,但是需要抓去的的数据是js生成的,在源代码里看不到,要怎么才能抓到呢? 最好是用pyt ...

最新文章

  1. AsSystemRum 系统提权工具 实现思路及其源码
  2. Kafka主题中的分区数越多吞吐量就越高?BULLSHIT!!!
  3. transformer中attention计算方式_Reformer: 局部敏感哈希、可逆残差和分块计算带来的高效...
  4. mysql-front5.1的注册码
  5. 《构建之法》阅读笔记(三)
  6. q 与 blockquote 的区别
  7. linux 更换 镜像源
  8. 汇编语言典型例子详解_单片机汇编语言经典一百例
  9. 高校智慧教室建设方案
  10. 奋斗5年,从月薪3500到700万(不止谈赚钱,故事也很感人)
  11. android 地球仪源码,android OpenGL ES 地球仪绘制——球体绘制及纹理映射
  12. html 显示 16进制 颜色,16进制颜色(html颜色值)
  13. WindowManagerService详述
  14. 极大似然估计——简述
  15. DotProject的安装(1)
  16. python中的美元符号_Python学习笔模式匹配与正则表达式之插入字符和美元字符
  17. python pika 消费mq basic_get方法
  18. FM FFM:深入理解FM与FFM
  19. 计算机学院可以举办活动,计算机学院举办2019年家长开放日系列活动
  20. jvm虚拟机规范 紧接上文的

热门文章

  1. UniAPP 使用高德地图,打包后定位api不执行
  2. no identity-based policy allows the cloudformation:CreateStack action
  3. ddl是什么意思网络语_ddl是什么
  4. 关于ASCII码的理解
  5. 领导力21法则-- 要点总结
  6. html手机打不开是什么,手机打不开微信的网页怎么办?手机打不开微信网页的原因和解决方法...
  7. WorldPress出现Briefly unavailable for scheduled maintenance. Check back in a minute.的解决方法
  8. Android作业分组与选题
  9. Android开源库:手把手教你实现一个简单好用的搜索框(含历史搜索记录)
  10. 联想笔记本电脑开机无法修复计算机,联想笔记本开机没反应怎么办 笔记本无法开机的解决方法...