这几天使用手机看玄幻小说《斗罗大陆3-龙王传说》,页面总是弹出色色的广告,不但浪费了流量延迟了加载时间,而且严重影响心情,决定写一个爬虫爬取该网站的小说部分的内容,把它保存成txt格式,直接使用手机阅读器阅读,告别烦人的广告,爽得飞起!我所爬取得小说的下载地址在本文末尾给出,可以免费下载。

  • 使用语言:Scala
  • 代码使用:下面所有的代码都是在一个.scala文件中的,复制粘贴点击运行就可以了。从第一个给定的url开始爬取符合条件的小说内容。
  • 优化:使用线程池,多个线程异步的方式获取远端的网页内容。虽然这样子代码复制度相对于同步的方式复杂一些,但是下载速度很快。
  • 总体思路:
    1、从第一个网页开始,提取所有符合条件的url,再从这些新的url对应的页面中提取新的url,直到提取完成为止。上面这个过程我们保存了所有符合条件的页面内容和对应的url。(所以我把它们保存在一个treemap中,key是url值,value是页面内容,treemap自带排序功能)。
    2、从页面内容中提取出我想要的小说内容。
    3、保存小说内容到文件系统中。
  • 其他说明:本代码先把所爬取的内容先全部保存到内存中,再存储成txt文件。(因为需要排序,所以采用这样的方式)

import java.net.{HttpURLConnection, SocketTimeoutException, URL}import scala.collection.JavaConversions._
import java.io._
import java.util.concurrent._
import java.util.HashSet
import java.util.regex.Patternimport scala.collection.mutable/**** @param startPage* @param outputPath 所爬取小说的存储路径,默认为当前目录下的crawl.txt文件.* @param filter url的过滤条件, 默认为true*/
class Crawler2(startPage: String, outputPath: String = "./crawl.txt", filter: (String => Boolean) = (url: String) => true) {/*** 获取链接的正则表达式*/private val linkRegex = """ (src|href)="([^"]+)"|(src|href)='([^']+)' """.trim.r/*** 文件类型正则*/private val htmlTypeRegex = "\btext/html\b"/*** 存储符合条件的链接*/private val crawledPool = new HashSet[String]/*** 连接超时时间*/private val CONN_TIME_OUT = 10*1000/*** 读取超时时间超时时间*/private val READ_TIME_OUT = 15*1000def crawl():Unit ={//爬取原始html页面val linksAndContent = doCrawlPages(startPage)//解析和提取有价值的内容linksAndContent.foreach(entry => {linksAndContent += (entry._1 -> extractTitleAndContent(entry._2))})//保存数据到文件系统中storeContent(linksAndContent, outputPath)}/*** 这个函数负责主要的爬取任务。它调用线程池中的一条线程来爬取一个页面,返回所爬取的页面内容和对应的url。* 该方法没有采用递归的方式来爬取网页内容,取而代之的是自定义一个栈结构,从而避免了大量的link造成的栈溢出和速度慢的问题。* 主线程需要等待其他爬取工作线程结束之后再进行下一步动作,又因为这里的爬取工作线程的数量是不确定的,这里的解决方法是* 让主线程循环等待,直到所有的爬取工作线程结束(正常完成任务或者超时).** @param pageUrl* @return  存储链接和对应的页面数据(HTML). key:url, value:原始的html页面数据。*/private def doCrawlPages(pageUrl: String):mutable.TreeMap[String,String] ={//创建线程池val threadPool: ThreadPoolExecutor = new ThreadPoolExecutor(10, 200, 3, TimeUnit.SECONDS,new LinkedBlockingDeque[Runnable](),new ThreadPoolExecutor.CallerRunsPolicy())//设置线程池相关属性threadPool.allowCoreThreadTimeOut(true)threadPool.setKeepAliveTime(6, TimeUnit.SECONDS)//存储该函数的返回值val result = new collection.mutable.TreeMap[String, String]()//用于存储每个页面符合条件的url,该栈共享于多个线程val LinksStack = mutable.Stack[String]()LinksStack.push(pageUrl)try{do{//线程池中还有任务在进行while(!LinksStack.isEmpty){//link栈不空val link = LinksStack.pop()val future = new FutureTask[(Int, String, Map[String, String])](() => getPageFromRemote(link))threadPool.execute(future)val pageContent = future.get(this.READ_TIME_OUT, TimeUnit.SECONDS)._2val tempLinks = parseCrawlLinks(link, pageContent)tempLinks.filter(!crawledPool.contains(_)).foreach(LinksStack.push(_))result += (link -> pageContent)}Thread.sleep(200)}while(threadPool.getActiveCount != 0)}finally {threadPool.shutdown()}result}/*** 连接将要爬取得网站,并下载该url的内容** @param url* @return ResponseCode, page内容, headers*/def getPageFromRemote(url: String):(Int, String, Map[String, String]) = {val uri = new URL(url);var conn:HttpURLConnection = nullvar status:Int = 0var data:String = ""var headers:Map[String,String] = nulltry{conn = uri.openConnection().asInstanceOf[HttpURLConnection];conn.setConnectTimeout(CONN_TIME_OUT)conn.setReadTimeout(this.READ_TIME_OUT)val stream = conn.getInputStream()val bufferedReader = new BufferedReader(new InputStreamReader(stream, "utf-8"))val strBuf = new StringBuilder()var line = bufferedReader.readLine()while (line != null) {strBuf.append(line)line = bufferedReader.readLine()}data = strBuf.toString()status = conn.getResponseCode()//根据status code判断页面是否被重定向了,从而进一步处理。这里略掉此步骤。headers = conn.getHeaderFields().toMap.map {head => (head._1, head._2.mkString(","))}}catch{case e:SocketTimeoutException => println(e.getStackTrace)case e2:Exception => println(e2.getStackTrace)}finally {if(conn != null) conn.disconnectcrawledPool.add(url)}return (status, data, headers)}/*** 从HTML文件中提取符合条件的URL** @param parentUrl* @param html* @return*/private def parseCrawlLinks(parentUrl: String, html: String) = {val baseHost = getHostBase(parentUrl)val links = fetchLinks(html).map {link =>link match {case link if link.startsWith("/") => baseHost + linkcase link if link.startsWith("http:") || link.startsWith("https:") => linkcase _ =>val index = parentUrl.lastIndexOf("/")parentUrl.substring(0, index) + "/" + link}}.filter {link => !crawledPool.contains(link) && this.filter(link)}println("find " + links.size + " links at page " + parentUrl)links}/*** 通过正则表达式从页面中提取出所有的url,包含不符合条件的** @param html* @return*/private def fetchLinks(html: String):Set[String] = {val list = for (m <- linkRegex.findAllIn(html).matchData if (m.group(1) != null || m.group(3) != null)) yield {if (m.group(1) != null) m.group(2) else m.group(4)}list.filter {link => !link.startsWith("#") && !link.startsWith("javascript:") && link != "" && !link.startsWith("mailto:")}.toSet}/*** 根据第一个url得到该网站的基本url** @param url* @return*/private def getHostBase(url: String) = {val uri = new URL(url)val portPart = if (uri.getPort() == -1 || uri.getPort() == 80) "" else ":" + uri.getPort()uri.getProtocol() + "://" + uri.getHost() + portPart}/*** 判断所爬取的网页是不是文本类型** @param headers* @return*/private def isTextPage(headers: Map[String, String]) = {val contentType = if (headers contains "Content-Type") headers("Content-Type") else nullcontentType match {case null => falsecase contentType if contentType isEmpty => falsecase contentType if Pattern.compile(htmlTypeRegex).matcher(contentType).find => truecase _ => false}}/*** 从原始的html文件中提取出自己想要的内容。所以需要修改这个函数来适应不同的网站页面。** @param html* @return*/private def extractTitleAndContent(html:String):String ={val h1StartIndex = html.indexOf("<h1>")val h1EndIndex = html.indexOf("</h1>", h1StartIndex)val contentStartIndex = html.indexOf("<div>", h1EndIndex)val contentEndIndex = html.indexOf("</div>", contentStartIndex)if(h1StartIndex < 0 || h1EndIndex < 0 || contentStartIndex < 0 || contentEndIndex < 0)return ""val title = html.substring(h1StartIndex+4, h1EndIndex)val content = html.substring(contentStartIndex+5, contentEndIndex).replaceAll("<br />|&nbsp;+|\t+", "")s"${title}\n${content}\n\n"}/*** 保存所爬取得数据到文件系统中。**/private def storeContent(linksAndContent:mutable.TreeMap[String, String], outputPath:String):Unit = {val writer = new BufferedWriter(new FileWriter(new File(outputPath)))val values = linksAndContent.valuesIteratorwhile(values.hasNext){writer.write(values.next())}writer.close()}
}object CrawlTest{def main(args:Array[String]): Unit ={new Crawler2("http://www.7caimi.com/xiaoshuo/2/","crawl.txt",filter = (url:String) => url.contains("http://www.7caimi.com/xiaoshuo/2/")).crawl()}
}

小说下载地址:http://pan.baidu.com/s/1mhWyZ8k

Scala语言编写的爬虫应用-爬取一部小说相关推荐

  1. python爬虫下载小说_用PYTHON爬虫简单爬取网络小说

    用PYTHON爬虫简单爬取网络小说. 这里是17K小说网上,随便找了一本小说,名字是<千万大奖>. 里面主要是三个函数: 1.get_download_url() 用于获取该小说的所有章节 ...

  2. Python爬虫之爬取网络小说并在本地保存为txt文件

    Python爬虫之爬取网络小说并在本地保存为txt文件 注:本文使用软件为Sublime Text,浏览器为谷歌浏览器 (新手小白第一次写,写得不好请见谅) **1.**首先找到想要爬取的小说章节目录 ...

  3. 用PYTHON爬虫简单爬取网络小说

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取 python免费学习资 ...

  4. python爬虫之类的方法爬取一部小说

    面向对象编程,爬取一部小说 ''' 面向对象编程 爬取17K小说网一部小说 ''' # 导入第三方库 import os import requests from lxml import etree ...

  5. 实现一个go语言的简单爬虫来爬取CSDN博文(一)

    http://blog.csdn.net/tyBaoErGe/article/details/50375802?hmsr=studygolang.com&utm_medium=studygol ...

  6. python爬虫爬取歌曲_python爬虫实战:爬取全站小说排行榜

    喜欢看小说的骚年们都知道,总是有一些小说让人耳目一新,不管是仙侠还是玄幻,前面更了几十章就成功圈了一大波粉丝,成功攀上飙升榜,热门榜等各种榜,扔几个栗子出来: 新笔趣阁是广大书友最值得收藏的网络小说阅 ...

  7. Python爬虫练习 爬取网络小说保存到txt

    原文链接:https://yetingyun.blog.csdn.net/article/details/107916769 创作不易,未经作者允许,禁止转载,更勿做其他用途,违者必究. 利用 Pyt ...

  8. scrapy-redis分布式爬虫全站爬取顶点小说网

    scrapy-redis是一个基于redis的scrapy组件,通过它可以快速实现简单分布式爬虫程序,该组件本质上提供了三大功能: scheduler - 调度器 dupefilter - URL去重 ...

  9. 【网络爬虫】爬取网络小说并保存为txt

    爬虫爬取网络小说并保存为txt文件 最近突然想看小说,但是苦于无法下载为txt,于是秉持着"自己动手,丰衣足食"的原则,自己写了一个爬虫,仅供参考~ 这里就以火星引力的<逆天 ...

最新文章

  1. 同步和串行的区别_[深度思考]·为什么CNN是同步(并行)而RNN是异步(串行)的呢?...
  2. mysql list列表批量更新数据,Mybatis传入List实现批量更新的示例代码
  3. Web渗透测试中常见逻辑漏洞解析与实战
  4. 物理机安装ESXI6.7提示No Network Adapters的解决方案
  5. linux shell数组参数传递参数,在bash中将数组作为参数传递
  6. Servlet之间的跳转(MVC模式)
  7. 模拟定位工具gps mock
  8. Notepad++ 替代品开源了!
  9. mysql中获取时间的年月日_Mysql获取系统时间,年,月,日
  10. 免费,好用的天气预报API
  11. 使用插件实现ecplise js/jquery智能提示
  12. spring boot 源码解析23-actuate使用及EndPoint解析
  13. UVa 1616 - Caravan Robbers
  14. chtMultiRegionSimpleFoam求解器的热源不在边界上【翻译】
  15. 计算机函数sun怎么用,手把手演示sumif函数怎么用【处置步骤】
  16. 免备案CDN推荐+教程
  17. zw量化交易·实盘操作·系列培训班
  18. Grafana的基本使用
  19. VOL指标-成交量指标
  20. 停简单电子优惠系统_宝马320车辆停在某地遥控上锁过一段时间车身会突然报警案例...

热门文章

  1. input file文件上传_微服务间的文件上传与下载-Feign
  2. 华为鸿蒙去哪里更新,华为鸿蒙OS正式尝鲜版名单更新,升级?还是不升级?
  3. (转) 6 ways of mean-centering data in R
  4. mysql 运算符 =,:=,@,@@的含义
  5. master线程的主循环,后台循环,刷新循环,暂停循环
  6. js 事件函数中的参数带换行符或换行标签都不能起作用的解决方法
  7. tomcat配置文件server.xml具体解释
  8. TableLayout(表格布局)
  9. J-Focus动画应用框架使用教程
  10. linux下的CPU频率管理器