天天看點

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)//儲存為檔案
}      

​ ​