《简单爬虫》——抓取一部小说

优采云 发布时间: 2022-06-05 02:52

  《简单爬虫》——抓取一部小说

  

  例子是基于nodejs的,也不是很复杂,有兴趣的可以自己也来敲敲代码。

  情不知从何起

  很多人喜欢手机阅读,当然我也是,不过我比较喜欢看一些盗墓偏玄幻类的小说,比如《鬼吹灯》、《盗墓笔记》这样的。现在用手机来阅读也是很方便的事情,下个app就好了,什么起点、追书神器、QQ阅读之类的,但是这些多数都是要收费的,特别是那种还未完结的。收费还贵,1章少一点的5毛,多一点的那种要收1块,这类小说不写个1000多章都不好意思出来混。

  对于我这样的免(dao)费(ban)程序受益者,怎么可能花那么大的代(jin)价(qian)来支持他。所以,第一原则:百度找免费的。百度一下果然有免费的,而且还是txt的,下载完成。开始看书。

  当然,不可能所有事情都是那么顺利就解决的。比如下载的txt有时候会少章节,有时候少内容,看的不够尽兴。然后我又开始找网站了,一搜一大把,然后问题又来了,一章一个页面,每次看好一章就要去刷一下页面,每次还会加载很多小广告,这里的小广告就厉害了,不点掉会挡住部分文字,点一下就不知道跳转到哪里去了,有时候点击下一章也会是这样的。这流量刷刷刷的就没有了,对于我这样的程(qiong)序(diao)员(si),一个月300M流量不够用啊。

  怎么办呢?毕竟咱们是个程序员啊,想点办法吧。那么我们用爬虫把页面里面的文字爬下来就好了嘛,于是就又了今天的内容了《简单爬虫》——抓取一部小说。

  而一往情深

  选角

  爬虫当然有很多可以选择的,各种后端语言(java、.net)、脚本语言(php、nodejs、python),很多都可以的,但是作为一名前端工程师,在现在这个大环境下,多少需要知道一些nodejs,就算不会,但是至少还是需要知道的,不然怎么能成为一名合格的前端工程师呢?所以这次试着用用nodejs。

  开搞

  首先,我们要找到一个我们需要抓取的文章页面。这里抓一下天蚕土豆的《大主宰》。天蚕土豆应该都知道吧,当年的《斗破苍穹》就是这家伙写的,火的不行。

  思路

  我们需要抓取每一章小说内容,然后将每一章按照顺序排列好之后,写入文件——大主宰.txt中。好了思路大致就这样,接下来开撸代码。

  var superagent = require("superagent")//用superagent去抓取

  var cheerio = require("cheerio");//分析网页结构

  //大主宰第一章页面

  var first_url = "";

  superagent.get(first_url).end(function(err,sres){

  var $ = cheerio.load(sres.text);

  var con = $("#content").text();

  consoe.log(con);

  })

  先说一下代码superagent是一个http的库,这边我们用来发起http请求。然后cheerio这东西就更好了,说白了就是一个node版的jquery,这里我通过页面的代码结构知道文章都在一个ID为content的div里面。

  

  运行一下居然是一堆乱码。。。一看就知道是编码问题了,看一下啊html编码是gbk的,一般我们的网站都是utf-8的,但是很多小说的网站都是gbk的编码,这是为什么呢?查看一下GBK编码:是指中国的中文字符,它包含了简体中文与繁体中文字符,另外还有一种字符“gb2312”,这种字符仅能存储简体中文字符。GBK编码格式,它的功能少,仅限于中文字符,当然它所占用的空间大小会随着它的功能而减少,打开网页的速度比较快。

  知道为什么用gbk了,现在就开始转码吧

  var superagent = require("superagent")//用superagent去抓取

  var cheerio = require("cheerio");//分析网页结构

  require('superagent-charset')(superagent);//引入gbk转换插件

  //大主宰第一章页面

  var first_url = "";

  superagent.get(first_url).charset('gbk').end(function(err,sres){

  var $ = cheerio.load(sres.text);

  var con = $("#content").text();

  console.log(con);

  })

  哈哈,搞定了,单个页面抓取成功了,那么就剩多页面抓取啦。

  思路:列表页抓取单个章节的文章链接,然后再去抓取单页的文章,最后拼接写入文件。恩,思路可行,那么我们来看一下列表页面...

  

  抓取list下的里面的a标签的href即可

  //大主宰列表页面

  var list_url = "";

  superagent.get(list_url).end(function(err,sres){

  var $ = cheerio.load(sres.text);

  var listurl = new Array();//存放单章的url

  $("#list dl dd a").each(function(idex,element){

  var $ele = $(element);

  listurl.push(list_url+$ele.attr("href").replace("/0_757/",""));

  });

  console.log(listurl);

  })

  我们这里打印一下列表页面的链接地址看一下。

  

  恩 打印的链接没问题

  superagent.get(list_url).end(function(err,sres){

  var $ = cheerio.load(sres.text);

  var listurl = new Array();//存放单章的url

  $("#list dl dd a").each(function(idex,element){

  var $ele = $(element);

  listurl.push(list_url+$ele.attr("href").replace("/0_757/",""));

  });

  var content = "";//最终要的文章内容

  var tmplisturl = listurl.slice(0,10);

  tmplisturl.forEach(function(item){

  superagent.get(item).charset('gbk').end(function(err,sres){

  var $ = cheerio.load(sres.text);

  var con = $("#content").text();

  content+=con;

  })

  });

  console.log(content)

  });

  那么来看一下代码多了一个循环,但是我打印出来的content居然是空...why?哦原来这个superagent抓取的时候是一个异步操作。所以我需要一个*敏*感*词*事件来告诉我,异步完成了才行。

  我们这里用eventproxy,用来处理异步协作是很有用的,有兴趣的可以去#%E9%87%8D%E5%A4%8D%E5%BC%82%E6%AD%A5%E5%8D%8F%E4%BD%9C 学习一下。

  superagent.get(list_url).end(function(err,sres){

  var $ = cheerio.load(sres.text);

  var listurl = new Array();//存放单章的url

  $("#list dl dd a").each(function(idex,element){

  var $ele = $(element);

  listurl.push(list_url+$ele.attr("href").replace("/0_757/",""));

  });

  var content = "";//最终要的文章内容

  var tmplisturl = listurl.slice(0,10);

  tmplisturl.forEach(function(item){

  superagent.get(item).charset('gbk').end(function(err,sres){

  var $ = cheerio.load(sres.text);

  var name = $(".bookname h1").text();

  var con = $("#content").text().replace(/\n/g,'');

  content+=name+"\n"+con+"\n";

  ep.emit("finish");

  })

  });

  ep.after("finish",tmplisturl.length,function(){

  console.log(content);

  });

  });

  ok啦!文章都能打印出来了,这里试了一下搞了前面10篇,加了点去除换行之类的小东西。但是还是有点问题,看下面的打印截图:

  

  因为是异步的原因,所以没有按照我们原来设想的那样按照章节的顺序输出。想想是不是得先用对象保存起来然后再来排序输出呢?

  tmplisturl.forEach(function(item){

  superagent.get(item.url).charset('gbk').end(function(err,sres){

  var $ = cheerio.load(sres.text);

  var name = $(".bookname h1").text();

  var con = $("#content").text().replace(/\n/g,'');

  item.con = name+"\n"+con+"\n";

  ep.emit("finish");

  })

  });

  ep.after("finish",tmplisturl.length,function(){ //模拟10次

  var content = "";

  for(var i=0;i

  content+=tmplisturl[i].con;

  }

  console.log(content);

  });

  试了一下果然ok啦。打完收工,领盒饭~

  哦,还有最后需要写入文件

  //'a+' - 以读取和追加模式打开文件。如果文件不存在,则会被创建

  fs.open("大主宰.txt",'a+',function(){

  fs.appendFile("大主宰.txt",content,function(){

  console.log("写完了");

  });

  })

  这里还有一点要提一下,别一次写入太多东西到文件中,内存不足会崩溃的。我这边做了一下分割循环。大概是30章一分割,也就是每次请求30章内容然后写入文件,待文件写入成功后继续请求30章,直到完成为止。但是我这边却只是写了600多章就没有了,这让我很惆怅...检查一下代码好像没什么问题,然后再次跑了一次,依然是600章不到的地方不动了..

  看看打印的log信息:

  

  想了想是不是我的内存不够用啊?看了一下,还真是这么一回事!

  

  然后开始清理一下自己开的东西。微信,chrome,qq,outlook,sourcetree,phpstorm关了,然后瞬间清爽了。

  

  剩余了10个G,继续跑一遍试试看,靠...还是不行,这次比之前的都少了,才200多章就停了...还不是内存占用的问题,那就是异步写入导致的I/O阻塞?

  fs.appendFile("大主宰.txt",content,function(){

  console.log("写入成功"+go)

  if(go){

  setTimeout(function(){

  write();

  },1000);

  }else{

  console.log('完成');

  }

  });

  改成这样试试看,为了测试猜想,我只是写入了文章的标题。看一下运行结果:

  

  还真的可以啊,当然这里的用的setTimeout,回头改成文件的关闭事件就好了。

  完整代码

  var superagent = require("superagent")//用superagent去抓取

  var cheerio = require("cheerio");//分析网页结构

  var eventproxy = require("eventproxy");//控制并发

  require('superagent-charset')(superagent);//引入gbk转换插件

  var list_url = "";//大主宰列表页面

  var ep = new eventproxy();//得到一个 eventproxy 的实例

  var fs = require("fs");//文件模块

  console.time("start")

  superagent.get(list_url).end(function(err,sres){

  var $ = cheerio.load(sres.text);

  var listurl = new Array();//存放单章的url

  $("#list dl dd a").each(function(idex,element){

  var $ele = $(element);

  var tmpurl = $ele.attr("href");

  var tmpid = tmpurl.replace("/0_757/","").replace(".html","")*1;

  var tmp = {

  url:list_url+tmpurl.replace("/0_757/",""),

  id:tmpid,

  con:""

  }

  listurl.push(tmp);

  });

  write();

  function write(){

  var tmplisturl = listurl.splice(0,30);

  var go = true;

  if(tmplisturl.length

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线