以前也用過爬蟲,比如使用nutch爬取指定種子,基于爬到的數(shù)據(jù)做搜索,還大致看過一些源碼。當(dāng)然,nutch對(duì)于爬蟲考慮的是十分全面和細(xì)致的。每當(dāng)看到屏幕上唰唰過去的爬取到的網(wǎng)頁信息以及處理信息的時(shí)候,總感覺這很黑科技。正好這次借助梳理Spring MVC的機(jī)會(huì),想自己弄個(gè)小爬蟲,簡(jiǎn)單沒關(guān)系,有些小bug也無所謂,我需要的只是一個(gè)能針對(duì)某個(gè)種子網(wǎng)站能爬取我想要的信息就可以了。有Exception就去解決,可能是一些API使用不當(dāng),也可能是遇到了http請(qǐng)求狀態(tài)異常,又或是數(shù)據(jù)庫讀寫有問題,就是在這個(gè)報(bào)exception和解決exception的過程中,JewelCrawler(兒子的小名)已經(jīng)可以能夠獨(dú)立的爬取數(shù)據(jù),并且還有一項(xiàng)基于Word2Vec算法做個(gè)情感分析的小技能。
后面可能還會(huì)有未知的Exception等著解決,也有一些性能需要優(yōu)化,比如和數(shù)據(jù)庫的交互,數(shù)據(jù)的讀寫等等。但是目測(cè)年內(nèi)沒有太多精力放這上面了,所以今天做一個(gè)簡(jiǎn)單的總結(jié),而且前兩篇主要側(cè)重的是功能和結(jié)果,這篇來說說JewelCrawler是如何誕生的,并將代碼放到Github上(源碼地址在文章最后),有興趣的可以關(guān)注下(僅供交流學(xué)習(xí),請(qǐng)勿他用,考慮下douban君。多一點(diǎn)真誠(chéng),少一點(diǎn)傷害)
環(huán)境介紹
開發(fā)工具:Intellij idea 14
數(shù)據(jù)庫: Mysql 5.5 + 數(shù)據(jù)庫管理工具Navicat(可用來連接查詢數(shù)據(jù)庫)
語言:Java
Jar包管理:Maven
版本管理:Git
目錄結(jié)構(gòu)
其中
com.ansj.vec是Word2Vec算法的Java版本實(shí)現(xiàn)
com.jackie.crawler.doubanmovie是爬蟲實(shí)現(xiàn)模塊,其中又包括
有些包是空的,因?yàn)檫@些模塊還沒有用上,其中
constants包是存放常量類
crawl包存放爬蟲入口程序
entity包映射數(shù)據(jù)庫表的實(shí)體類
test包存放測(cè)試類
utils包存放工具類
resource模塊存放的是配置文件和資源文件,比如
beans.xml:Spring上下文的配置文件
seed.properties:種子文件
stopwords.dic:停用詞庫
comment12031715.txt:爬取的短評(píng)數(shù)據(jù)
tokenizerResult.txt:使用IKAnalyzer分詞后的結(jié)果文件
vector.mod:基于Word2Vec算法訓(xùn)練的模型數(shù)據(jù)
test模塊是測(cè)試模塊,用于編寫UT.
數(shù)據(jù)庫配置
1. 添加依賴的包
JewelCrawler使用的maven管理,所以只需要在pom.xml中添加相應(yīng)的依賴就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-jdbc</ artifactId > < version >4.1.1.RELEASE</ version > </ dependency > < dependency > < groupId >commons-pool</ groupId > < artifactId >commons-pool</ artifactId > < version >1.6</ version > </ dependency > < dependency > < groupId >commons-dbcp</ groupId > < artifactId >commons-dbcp</ artifactId > < version >1.4</ version > </ dependency > < dependency > < groupId >mysql</ groupId > < artifactId >mysql-connector-java</ artifactId > < version >5.1.38</ version > </ dependency > < dependency > < groupId >mysql</ groupId > < artifactId >mysql-connector-java</ artifactId > < version >5.1.38</ version > </ dependency > |
2. 聲明數(shù)據(jù)源bean
我們需要在beans.xml中聲明數(shù)據(jù)源的bean
1 2 3 4 5 6 7 | < context:property-placeholder location="classpath*:*.properties"/> < bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> < property name="driverClassName" value="${jdbc.driver}"/> < property name="url" value="${jdbc.url}"/> < property name="username" value="${jdbc.username}"/> < property name="password" value="${jdbc.password}"/> </ bean > |
注意: 這里是綁定了外部配置文件jdbc.properties,具體數(shù)據(jù)源的參數(shù)從該文件讀取。
如果遇到問題“SQL [insert into user(id) values(?)]; Field 'name' doesn't have a default value;”解決方法是設(shè)置表的相應(yīng)字段為自增長(zhǎng)字段。
解析頁面遇到的問題
對(duì)于爬到的網(wǎng)頁數(shù)據(jù)需要解析dom結(jié)構(gòu),拿到自己想要的數(shù)據(jù),期間遇到如下錯(cuò)誤
org.htmlparser.Node不識(shí)別
解決方法:添加jar包依賴
1 2 3 4 5 | < dependency > < groupId >org.htmlparser</ groupId > < artifactId >htmlparser</ artifactId > < version >1.6</ version > </ dependency > |
org.apache.http.HttpEntity不識(shí)別
解決方法:添加jar包依賴
1 2 3 4 5 | < dependency > < groupId >org.apache.httpcomponents</ groupId > < artifactId >httpclient</ artifactId > < version >4.5.2</ version > </ dependency > |
當(dāng)然這是期間遇到的問題,最后用的是Jsoup做的頁面解析。
maven倉庫下載速度慢
之前使用的是默認(rèn)的maven中央倉庫,下載jar包的速度很慢,不知道是我的網(wǎng)絡(luò)問題還是其他原因,后來在網(wǎng)上找到了阿里云的maven倉庫,更新后,相比之前簡(jiǎn)直是秒下,吐血推薦。
1 2 3 4 5 6 7 8 | < mirrors > < mirror > < id >alimaven</ id > < name >aliyun maven</ name > < url >http://maven.aliyun.com/nexus/content/groups/public/</ url > < mirrorOf >central</ mirrorOf > </ mirror > </ mirrors > |
找到maven的settings.xml文件,添加這個(gè)鏡像即可。
讀取resource模塊下文件的一種方法
比如讀取seed.properties文件
1 2 3 4 5 | @Test public void testFile(){ File seedFile = new File( this .getClass().getResource( "/seed.properties" ).getPath()); System.out.print( "===========" + seedFile.length() + "===========" ); } |
有關(guān)正則表達(dá)式
使用regrex正則表達(dá)式的時(shí)候,如果匹配上了定義的Pattern,則需要先調(diào)用matcher的find方法然后才能使用group方法找到子串。直接調(diào)用group方法是沒有辦法找到你想要的結(jié)果的。
我看了下上面Matcher類的源碼
原因是這樣的:這里如果不先調(diào)用find方法,直接調(diào)用group,可以發(fā)現(xiàn)group方法調(diào)用group(int group),該方法的方法體中有if first<0,顯然這里這個(gè)條件是成立的,因?yàn)閒irst的初始值就是-1,所以這里會(huì)拋異常。但是如果調(diào)用find方法,可以發(fā)現(xiàn),最終會(huì)調(diào)用search(nextSearchIndex),注意這里的nextSearchIndex已被last賦值,而last的值為0,再跳轉(zhuǎn)到search方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | boolean search( int from) { this .hitEnd = false ; this .requireEnd = false ; from = from < 0 ? 0 : from; this .first = from; this .oldLast = oldLast < 0 ? from : oldLast; for ( int i = 0 ; i < groups.length; i++) groups[i] = - 1 ; acceptMode = NOANCHOR; boolean result = parentPattern.root.match( this , from, text); if (!result) this .first = - 1 ; this .oldLast = this .last; return result; } |
這個(gè)nextSearchIndex傳給了from,而from在方法體中被賦值給了first,所以,調(diào)用了find方法之后,這個(gè)的first就不在是-1,也就不是拋異常了。
源碼已經(jīng)上傳至Github:https://github.com/DMinerJackie/JewelCrawler
以上說的問題比較碎,都是在遇到問題和解決問題的時(shí)候的一些總結(jié)。在具體操作的時(shí)候還會(huì)遇到其他問題,有問題或者建議的話歡迎提出來^^。
最后放幾張截止目前爬取的數(shù)據(jù)
Record表
其中存儲(chǔ)的是79032條,爬取過的網(wǎng)頁有48471條
movie表
目前爬取了2964部影視作品
comments表
爬取了29711條記錄
如果您覺得閱讀本文對(duì)您有幫助,請(qǐng)點(diǎn)一下“推薦”按鈕,您的“推薦”將是我最大的寫作動(dòng)力!如果您想持續(xù)關(guān)注我的文章,請(qǐng)掃描二維碼,關(guān)注JackieZheng的微信公眾號(hào),我會(huì)將我的文章推送給您,并和您一起分享我日常閱讀過的優(yōu)質(zhì)文章。