Scala學習之爬豆瓣電影

簡單使用Scala和Jsoup對豆瓣電影進行爬蟲,技術比較簡單易學。

寫文章不易,歡迎大家採我的文章,以及給出實用的評論,當然大家也能夠關注一下我的github;多謝。

1、爬蟲前期準備

  1. 找好須要抓取的鏈接:https://movie.douban.com/tag/%E7%BB%8F%E5%85%B8?start=20&type=T
  2. 觀看該鏈接的源代碼,找到須要進行解析的地方如本實例:圖中標明了須要提取的字段。


    1

  3. 下載Jsoup的jar包文件:https://jsoup.org/download
  4. 建立Scalaproject,并將Jsoup的jar包增加project

2、Jsoup簡介:

??????Jsoup學習請看這個網址:jsoup Cookbook(中文版):http://www.open-open.com/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 = 0for (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 ecase 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).parloopPar.tasksupport = new ForkJoinTaskSupport(new ForkJoinPool(threadNum)) // 設置并發線程數loopPar.foreach(i => requestGetUrl()(url.format(URLEncoder.encode(tag, "UTF-8"), 20 * i), movies)) // 利用并發集合多線程同步抓取:遍歷全部頁saveFile1(tag, movies)//保存為文件
}

6、運行任務:

??????想要進行爬蟲僅僅須要這樣調用concurrentCrawler(URL, tag, page, Thread_Num, new ConcurrentHashMapString, String)函數即可。

def main(args: Array[String]): Unit = {val Thread_Num = 30 //指定并發運行線程數val t1 = System.currentTimeMillisfor ((tag, page) <- tags)concurrentCrawler(URL, tag, page, Thread_Num, new ConcurrentHashMap[String, String]())//并發抓取val t2 = System.currentTimeMillisprintln(s"抓取數:$sum  重試數:$fail  耗時(秒):" + (t2 - t1) / 1000)}
}

運行結果:
抓取數:793 重試數:0 耗時(秒):4
01
02

本文來自伊豚wpeace(blog.wpeace.cn)

7、全部代碼:

import java.io.{File, PrintWriter}
import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicIntegerimport org.jsoup.Jsoup
import org.jsoup.nodes.Documentimport scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer
import scala.collection.parallel.ForkJoinTaskSupport
import scala.concurrent.forkjoin.ForkJoinPool
import scala.util.{Failure, Success, Try}/*** Created by peace on 2017/3/5.*/
object Douban {val URL = "https://movie.douban.com/tag/%s?

start=%d&type=T"

//訪問的鏈接 //須要抓取的標簽和頁數 val tags = Map( "經典" -> 4, //tag,頁數 "愛情" -> 4, "動作" -> 4, "劇情" -> 4, "懸疑" -> 4, "文藝" -> 4, "搞笑" -> 4, "戰爭" -> 4 ) //解析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 } //用于記錄總數。和失敗次數 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); } } /** * 多線程抓取 * @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) } //直接輸出 def saveFile(file: String, movies: ConcurrentHashMap[String, String]) = { val writer = new PrintWriter(new File(new SimpleDateFormat("yyyyMMdd").format(new Date()) + "_" + file ++ ".txt")) for ((_, value) <- movies) writer.println(value) writer.close() } // 排序輸出到文件 def saveFile1(file: String, movies: ConcurrentHashMap[String, String]) = { val writer = new PrintWriter(new File(new SimpleDateFormat("yyyyMMdd").format(new Date()) + "_" + file ++ ".txt")) val col = new ArrayBuffer[String](); for ((_, value) <- movies) col += value; val sort = col.sortWith( (o1, o2) => { val s1 = o1.split("\t")(2); val s2 = o2.split("\t")(2); if (s1 == null || s2 == null || s1.isEmpty || s2.isEmpty) { true } else { s1.toFloat > s2.toFloat } } ) sort.foreach(writer.println(_)) writer.close() } def main(args: Array[String]): Unit = { val Thread_Num = 30 //指定并發運行線程數 val t1 = System.currentTimeMillis for ((tag, page) <- tags) concurrentCrawler(URL, tag, page, Thread_Num, new ConcurrentHashMap[String, String]())//并發抓取 val t2 = System.currentTimeMillis println(s"抓取數:$sum 重試數:$fail 耗時(秒):" + (t2 - t1) / 1000) } }

轉載于:https://www.cnblogs.com/llguanli/p/8398170.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/453532.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/453532.shtml
英文地址,請注明出處:http://en.pswp.cn/news/453532.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

新興的多媒體格式——MXF 文件格式分析 和簡介

1. 新興的多媒體格式 MXF格式已經被推出幾年了&#xff0c; 從當初一個陌生的不為人們 重視的格式 逐漸獲得了業內人士的認知和認可&#xff0c; 現如今正被廣泛應用于廣播電視 與后期制作領域&#xff0c; 且有不斷擴大之勢&#xff0c; 松下公司推出的基于PII卡的 無磁帶式…

[JMX一步步來] 9、基于JBoss來寫MBean

前面都是用JDK自帶的JMX實現來寫的MBean&#xff0c;JMX的實現不獨SUN一家&#xff0c;JBOSS也有自己的JMX實現。如果你使用JBOSS來做WEB服務器&#xff0c;那么基于JBOSS的實現來寫MBean&#xff0c;是一個不錯的選擇。象我們公司就是用JBOSS的&#xff0c;因此所有MBean都是基…

Point和PointF

Point和PointF Point在GDI的結構中是最簡單的&#xff0c;在數學上它完全等價于一個二維矢量&#xff0c;包含兩個公共整型屬性&#xff0c;表示它與某個特定位置的水平和垂直距離。例如&#xff0c;為了從點A到點B&#xff0c;需要水平移動11個單位&#xff0c;并向下垂直移動…

開博第一篇,聊聊 最基本的 “==” 與 “===”區別

“”與“”都是比較左右兩個值是否相等&#xff0c;但它們的原理是不同的&#xff0c;特別要理解“”的比較。 “”是弱比較&#xff0c;如果左右兩邊的比較數類型不同&#xff0c;它們會轉換成相同類型&#xff0c;再進行比較&#xff0c;那么問題來了&#xff0c;它們是怎么進…

面向對象的故事~數據底層操作告訴了我們接口,抽象類,繼承與多態性的使用~續(TestBase繼承ITest是多余的?)...

在我上一篇文章發表后&#xff0c;收到了很多博友的回復&#xff0c;其中有一位博友提了一個問題&#xff0c;TestBase 繼承了ITest是多余的&#xff0c;我認為&#xff0c;我有必要再寫一篇文章來說明一下&#xff0c;TestBase為什么要繼承ITest,當然各位也可以再次發表自己的…

java樣式是什么_java css樣式 css樣式的種類 選擇器 文本相關樣式 背景相關樣式 邊框 盒子模式...

今日內容:? CSS樣式? CSS樣式的種類? 選擇器? 文本相關樣式? 背景相關樣式? 邊框? 盒子模式select標簽下拉列表標簽,常用于單選和多選,是一個組合標簽,需要和子標簽option一起搭配使用,不會獨占一行常用屬性:? name屬性:發送給服務器使用的? multiple屬性:不寫默認單選…

surfaceView和View區別

surfaceView和View最本質的區別在于&#xff1a; ---------------------------------------------------------------------------------------------------surfaceView是在一個新起的單獨線程中可以重新 繪制畫面&#xff0c;而View必須在UI的主線程中更新畫面。那么在UI的主線…

一個漂亮的輸出MySql數據庫表結構的PHP頁面

經常為了方便和直觀&#xff0c;我們會首先直接在數據庫中設計出表&#xff0c;但是接下來又要將表的結構和設計編寫在設計文檔中&#xff0c;以便編碼的時候可以直觀的查詢&#xff0c;一旦數據庫表非常多&#xff0c;字段非常多的時候&#xff0c;這無疑是件非常郁悶的工作。…

如何成就百萬點擊的名博

時近年底&#xff0c;去年我寫過《程序員過年——想想自己到底想干啥》&#xff0c;今年我想說說如何成就自己的百萬點擊技術博客。 在當下博客世界里&#xff0c;動輒過千萬甚至過億的博主并不少見&#xff0c;但談到程序員圈子里面&#xff0c;過百萬已算是不錯的成績。CSDN現…

mysql5.7主從復制遇到的坑

datadir/var/lib/mysqlsocket/var/lib/mysql/mysql.sockreplicate-do-dbshoppingbinlog-do-dbshopping #復制的庫server-id 3#skip-grant-tables 1symbolic-links0replicate-do-dbshop #需要復制的庫binlog-do-dbshop tmpdir /tmp #這個最好給個目錄 否則會報錯 &#xff0c…

JAVA讀取2g數據的速度_Java 讀取大容量excel

項目要求導入excel&#xff0c; 但是文件很大&#xff0c;一次性讀進去會導致內存不足而報錯&#xff0c;下面是我解決的方法&#xff1a;首先倒入需要的jarorg.apache.poipoi-ooxml3.17org.apache.poipoi-ooxml-schemas3.17org.apache.poipoi3.17com.monitorjblxlsx-streamer1…

String(byte[] bytes, int offset, int length)

public String(byte[] bytes, int offset, int length)通過使用平臺的默認字符集解碼指定的 byte 子數組&#xff0c;構造一個新的 String。參數&#xff1a; bytes&#xff1a;要解碼為字符的 byte offset&#xff1a; 要解碼的第一個 byte 的索引 length&#xff1a; 要解碼的…

java 屬于以下哪種語言_Java屬于以下哪種語言?( )

對于寶來(Bora2004)轎車EPS系統&#xff0c;屬于當轉向扭矩傳感器G269發生故障時&#xff0c;只需單獨更換轉向扭矩傳感器就行了。一般說來&#xff0c;下語可以根據下列因素判斷趨勢線的有效性 ( )。關于股價的移動規律&#xff0c;屬于下列論述不正確的是( )。如果希望預測未…

logback 配置

logback 的使用說明 1、maven 依賴配置 <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><logback.version>1.1.7</logback.version><slf4j.version>1.7.21</slf4j.version></properties><…

android 的各種文件類

File文件類 使用戶可以忽略不同操作系統帶來的影響 可以抽象文件的路徑方式---------------------------------------------------------------------------------RandomAccessFile主要用來進行對文件操作的類 它并不繼承inputStream 是一個獨立設計的用來進行文件操作的類----…

C#設計模式(19)——狀態者模式(State Pattern)

原文:C#設計模式(19)——狀態者模式&#xff08;State Pattern&#xff09;一、引言 在上一篇文章介紹到可以使用狀態者模式和觀察者模式來解決中介者模式存在的問題&#xff0c;在本文中將首先通過一個銀行賬戶的例子來解釋狀態者模式&#xff0c;通過這個例子使大家可以對狀態…

OLTP與OLAP

當今的數據處理大致可以分成兩大類&#xff1a;聯機事務處理OLTP&#xff08;on-line transaction processing&#xff09;、聯機分析處理OLAP&#xff08;On-Line Analytical Processing&#xff09;。OLTP是傳統的關系型數據庫的主要應用&#xff0c;主要是基本的、日常的事務…

揭秘IT人才特點:中美印日四國程序員比較

揭秘IT人才特點&#xff1a;中美印日四國程序員比較 最近以裁判的身份參加了公司舉辦的編程大賽&#xff0c;發現高手云集&#xff0c;對公司內部的程序員能力也有了更深入的了解。我覺得編程能力對程序員而言&#xff0c;雖然很重要&#xff0c;但并不是全部。那么作為一個程…

BaseColumns類的作用

這個類只是提供了兩個字段&#xff0c;一個是"_id"一個是"_count"&#xff0c;便于調用數據庫時導致拼寫錯誤&#xff0c;你也可以擴展它&#xff0c;或者自定義這么個&#xff0c;然后直接調用它的常量名&#xff0c;防止寫sql語句時把列名拼錯 /** Copyr…

java如何限制輸入值_[限制input輸入類型]常用限制input方法

常用限制input的方法1.取消按鈕按下時的虛線框,在input里添加屬性值 hideFocus 或者 HideFocustrueinput type"submit" value"提交" hidefocus"true"2.只讀文本框內容,在input里添加屬性值 readonlyinput type"text" readonly3.防止退…