天天看点

Scala学习之爬豆瓣电影

1、爬虫前期准备

2、Jsoup简介:

      我这里仅仅介绍我用到了的四个函数:

1、第一个函数:Jsoup.connect(url)
val doc:Document=Jsoup.connect(url).get()//从一个站点获取和解析一个HTML文档,使用get方式。      

​ ​

​说的直白点这里获得的就是网页的源代码;//特殊使用:带有參数并使用Post方式 Document doc = Jsoup.connect("http://example.com") .data("query", "Java") .userAgent("Mozilla") .cookie("auth", "token") .timeout(3000) .post(); 2、第二个函数:Element.select(String selector) doc.select("a.nbg")//通过使用CSS(或Jquery)selector syntax 获得你想要操作元素,这里获得的是说有class=nbg的<a/>标签。​

​3、第三个函数:public String attr(String attributeKey) Elements中的attr函数是通过属性获得Element中第一个匹配该属性的值。如elem.select("a.nbg").attr("title"):获得a标签中的title。 4、第四个函数:public String html() 获得element中包括的Html内容​

3、解析Html:

      这里的Html内容比較简单。仅仅须要获得如图一中标记的四处。这里仅仅要用到第二章中的后面三个方法。

//解析Document,须要对比网页源代码进行解析
def parseDoc(doc: Document, movies: ConcurrentHashMap[String, String]) = {
  var count = 0
  for (elem <- doc.select("tr.item")) {//获得全部的电影条目
    movies.put(elem.select("a.nbg").attr("title"), elem.select("a.nbg").attr("title") + "\t" //标题
      + elem.select("a.nbg").attr("href") + "\t" //豆瓣链接
      // +elem.select("p.pl").html+"\t"//简介
      + elem.select("span.rating_nums").html + "\t" //评分
      + elem.select("span.pl").html //评论数
    )
    count += 1
  }
  count
}      

4、建立连接获得相应Url的Html

      这里使用了Scala中的Try语法,我这里仅仅简单说明,当​

​Jsoup.connect(url).get()​

​ 返回异常时模式匹配会匹配Failure(e)并将异常赋值给模板类中的e。当返回成功时将匹配Success(doc),并将获得的Html的Document赋值给doc。

//用于记录总数。和失败次数
val sum, fail: AtomicInteger = new AtomicInteger(0)
/**
  *  当出现异常时10s后重试,异常反复100次
  * @param delay:延时时间
  * @param url:抓取的Url
  * @param movies:存取抓到的内容
  */
def requestGetUrl(times: Int = 100, delay: Long = 10000)(url: String, movies: ConcurrentHashMap[String, String]): Unit = {
  Try(Jsoup.connect(url).get()) match {//使用try来推断是否成功和失败对网页进行抓取
    case Failure(e) =>
      if (times != 0) {
        println(e.getMessage)
        fail.addAndGet(1)
        Thread.sleep(delay)
        requestGetUrl(times - 1, delay)(url, movies)
      } else throw e
    case Success(doc) =>
      val count = parseDoc(doc, movies);
      if (count == 0) {
        Thread.sleep(delay);
        requestGetUrl(times - 1, delay)(url, movies)
      }
      sum.addAndGet(count);
  }
}      

5、使用并发集合

      为了加快住区速度使用了Scala中的并发集合:par。相似于java中的fork/join框架;

/**
  * 多线程抓取
  * @param url:原始的Url
  * @param tag:电影标签
  * @param maxPage:页数
  * @param threadNum:线程数
  * @param movies:并发集合存取抓到的内容
  */
def concurrentCrawler(url: String, tag: String, maxPage: Int, threadNum: Int, movies: ConcurrentHashMap[String, String]) = {
  val loopPar = (0 to maxPage).par
  loopPar.tasksupport = new ForkJoinTaskSupport(new ForkJoinPool(threadNum)) // 设置并发线程数
  loopPar.foreach(i => requestGetUrl()(url.format(URLEncoder.encode(tag, "UTF-8"), 20 * i), movies)) // 利用并发集合多线程同步抓取:遍历全部页
  saveFile1(tag, movies)//保存为文件
}      

​ ​