1. 什么是網絡爬蟲?
在大數據時代,信息的采集是一項重要的工作,而互聯網中的數據是海量的,如果單純靠人力進行信息采集,不僅低效繁瑣,搜集的成本也會提高。如何自動高效地獲取互聯網中我們感興趣的信息並為我們所用是一個重要的問題,而爬蟲技術就是為了解決這些問題而生的。
網絡爬蟲(Web crawler)也叫做網絡機器人,可以代替人們自動地在互聯網中進行數據信息的采集與整理。它是一種按照一定的規則,自動地抓取萬維網信息的程序或者腳本,可以自動采集所有其能夠訪問到的頁面內容,以獲取相關數據。
從功能上來講,爬蟲一般分為數據采集,處理,儲存三個部分。爬蟲從一個或若干初始網頁的URL開始,獲得初始網頁上的URL,在抓取網頁的過程中,不斷從當前頁面上抽取新的URL放入隊列,直到滿足系統的一定停止條件。
2. 網絡爬蟲的作用
1.可以實現搜索引擎
我們學會了爬蟲編寫之后,就可以利用爬蟲自動地采集互聯網中的信息,采集回來后進行相應的存儲或處理,在需要檢索某些信息的時候,只需在采集回來的信息中進行檢索,即實現了私人的搜索引擎。
2.大數據時代,可以讓我們獲取更多的數據源
在進行大數據分析或者進行數據挖掘的時候,需要有數據源進行分析。我們可以從某些提供數據統計的網站獲得,也可以從某些文獻或內部資料中獲得,但是這些獲得數據的方式,有時很難滿足我們對數據的需求,而手動從互聯網中去尋找這些數據,則耗費的精力過大。此時就可以利用爬蟲技術,自動地從互聯網中獲取我們感興趣的數據內容,並將這些數據內容爬取回來,作為我們的數據源,再進行更深層次的數據分析,並獲得更多有價值的信息。
3. 可以更好地進行搜索引擎優化(SEO)
對於很多SEO從業者來說,為了更好的完成工作,那么就必須要對搜索引擎的工作原理非常清楚,同時也需要掌握搜索引擎爬蟲的工作原理。而學習爬蟲,可以更深層次地理解搜索引擎爬蟲的工作原理,這樣在進行搜索引擎優化時,才能知己知彼,百戰不殆。
3.網絡爬蟲如何使用?
爬蟲底層兩大核心:
(1).HttpClient:網絡爬蟲就是用程序幫助我們訪問網絡上的資源,我們一直以來都是使用HTTP協議訪問互聯網的網頁,網絡爬蟲需要編寫程序,在這里使用同樣的HTTP協議訪問網頁,這里我們使用Java的HTTP協議客戶端 HttpClient這個技術,來實現抓取網頁數據。(在Java程序中通過HttpClient技術進行遠程訪問,抓取網頁數據.)
注:如果每次請求都要創建HttpClient, 會有頻繁創建和銷毀的問題, 可以使用HttpClient連接池來解決這個問題。
(2). Jsoup:我們抓取到頁面之后,還需要對頁面進行解析。可以使用字符串處理工具解析頁面,也可以使用正則表達式,但是這些方法都會帶來很大的開發成本,所以我們需要使用一款專門解析html頁面的技術。(將獲取到的頁面數據轉化為Dom對象進行解析)
Jsoup介紹:Jsoup 是一款Java 的HTML、XML解析器,可直接解析某個URL地址、HTML文本、文件解析為DOM對象, 同時它提供了一套非常省力的API,可通過DOM,CSS以及類似於jQuery的操作方法來取出和操作數
- 設定抓取目標(種子頁面)並獲取網頁.
- 當服務器無法訪問時, 設置重試次數.
- 在需要的時候設置用戶代理(否則無法訪問頁面)
- 對獲取的頁面進行必要的解碼操作
- 通過正則表達式獲取頁面中的鏈接
- 對鏈接進行進一步的處理(獲取頁面並重復上面的操作)
- 將有用的信息進行持久化(以備后續的處理)
5. WebMagic介紹
WebMagic是一款爬蟲框架,其底層用到了上文所介紹使用的HttpClient和Jsoup,讓我們能夠更方便的開發爬蟲。WebMagic的設計目標是盡量的模塊化,並體現爬蟲的功能特點。這部分提供非常簡單、靈活的API,在基本不改變開發模式的情況下,編寫一個爬蟲。
WebMagic項目代碼分為核心和擴展兩部分。核心部分(webmagic-core)是一個精簡的、模塊化的爬蟲實現,而擴展部分(webmagic-extension)則包括一些便利的、實用性的功能, 例如注解模式編寫爬蟲等,同時內置了一些常用的組件,便於爬蟲開發。
1).架構介紹
WebMagic的結構分為Downloader、PageProcessor、Scheduler、Pipeline四大組件,並由Spider將它們彼此組織起來。這四大組件對應爬蟲生命周期中的下載、處理、管理和持久化等功能.
Spider將這幾個組件組織起來,讓它們可以互相交互,流程化的執行,可以認為Spider是一個大的容器,它也是WebMagic邏輯的核心。
WebMagic總體架構圖如下:
2).WebMagic的四個組件
①.Downloader
Downloader負責從互聯網上下載頁面,以便后續處理。WebMagic默認使用了Apache HttpClient作為下載工具。
②.PageProcessor
PageProcessor負責解析頁面,抽取有用信息,以及發現新的鏈接。WebMagic使用Jsoup作為HTML解析工具,並基於其開發了解析XPath的工具Xsoup。
在這四個組件中,PageProcessor對於每個站點每個頁面都不一樣,是需要使用者定制的部分。
③.Scheduler
Scheduler負責管理待抓取的URL,以及一些去重的工作。WebMagic默認提供了JDK的內存隊列來管理URL,並用集合來進行去重。也支持使用Redis進行分布式管理。
④.Pipeline
Pipeline負責抽取結果的處理,包括計算、持久化到文件、數據庫等。WebMagic默認提供了“輸出到控制台”和“保存到文件”兩種結果處理方案。
Pipeline定義了結果保存的方式,如果你要保存到指定數據庫,則需要編寫對應的Pipeline。對於一類需求一般只需編寫一個Pipeline。
3).用於數據流轉的對象
①. Request
Request是對URL地址的一層封裝,一個Request對應一個URL地址。它是PageProcessor與Downloader交互的載體,也是PageProcessor控制Downloader唯一方式。除了URL本身外,它還包含一個Key-Value結構的字段extra。你可以在extra中保存一些特殊的屬性,然后在其他地方讀取,以完成不同的功能。例如附加上一個頁面的一些信息等。
②. Page
Page代表了從Downloader下載到的一個頁面——可能是HTML,也可能是JSON或者其他文本格式的內容。Page是WebMagic抽取過程的核心對象,它提供一些方法可供抽取、結果保存等。
③. ResultItems
ResultItems相當於一個Map,它保存PageProcessor處理的結果,供Pipeline使用。它的API與Map很類似,值得注意的是它有一個字段skip,若設置為true,則不應被Pipeline處理。
6.WebMagic如何使用
1). PageProcessor組件的功能
①.抽取元素Selectable
Selectable相關的抽取元素鏈式API是WebMagic的一個核心功能。使用Selectable接口,可以直接完成頁面元素的鏈式抽取,也無需去關心抽取的細節。page.getHtml()返回的是一個Html對象,它實現了Selectable接口。這部分抽取API返回的都是一個Selectable接口,意思是說,是支持鏈式調用的。這個接口包含的方法分為兩類:抽取部分和獲取結果部分。
方法 |
說明 |
示例 |
xpath(String xpath) |
使用XPath選擇 |
html.xpath("//div[@class='title']") |
$(String selector) |
使用Css選擇器選擇 |
html.$("div.title") |
$(String selector,String attr) |
使用Css選擇器選擇 |
html.$("div.title","text") |
css(String selector) |
功能同$(),使用Css選擇器選擇 |
html.css("div.title") |
links() |
選擇所有鏈接 |
html.links() |
regex(String regex) |
使用正則表達式抽取 |
html.regex("\(.\*?)\") |
PageProcessor里主要使用了三種抽取技術:XPath、CSS選擇器和正則表達式。對於JSON格式的內容,可使用JsonPath進行解析.
1. XPath
以上是獲取屬性class=mt的div標簽,里面的h1標簽的內容
2.CSS選擇器
CSS選擇器是與XPath類似的語言。
div.mt>h1表示class為mt的div標簽下的直接子元素h1標簽
可是使用:nth-child(n)選擇第幾個元素,如下選擇第一個元素
注意:需要使用 > 就是直接子元素才可以選擇第幾個元素
3.正則表達式
正則表達式則是一種通用的文本抽取語言。在這里一般用於獲取url地址。
②.獲取結果
當鏈式調用結束時,我們一般都想要拿到一個字符串類型的結果。這時候就需要用到獲取結果的API了。
我們知道,一條抽取規則,無論是XPath、CSS選擇器或者正則表達式,總有可能抽取到多條元素。WebMagic對這些進行了統一,可以通過不同的API獲取到一個或者多個元素。
方法 |
說明 |
示例 |
get() |
返回一條String類型的結果 |
String link= html.links().get() |
toString() |
同get(),返回一條String類型的結果 |
String link= html.links().toString() |
all() |
返回所有抽取結果 |
List links= html.links().all() |
當有多條數據的時候,使用get()和toString()都是獲取第一個url地址。
測試結果:
selectable.toString()在輸出以及和一些框架結合的時候,更加方便。因為一般情況下,我們都只采用此方法獲取一個元素!
③. 獲取鏈接
一個站點的頁面是很多的,一開始我們不可能全部列舉出來,於是如何發現后續的鏈接,是一個爬蟲不可缺少的一部分。
下面的例子就是獲取https://www.jd.com/moreSubject.aspx這個頁面中
所有符合https://www.jd.com/news.\\w+?.*正則表達式的url地址
並將這些鏈接加入到待抓取的隊列中去。
2). Scheduler組件的使用
在解析頁面的時候,很可能會解析出相同的url地址(例如商品標題和商品圖片超鏈接,而且url一樣),如果不進行處理,同樣的url會解析處理多次,浪費資源。所以我們需要有一個url去重的功能。
Scheduler可以幫助我們解決以上問題。Scheduler是WebMagic中進行URL管理的組件。一般來說,Scheduler包括兩個作用:
❶對待抓取的URL隊列進行管理。
❷對已抓取的URL進行去重。
WebMagic內置了幾個常用的Scheduler。如果只是在本地執行規模比較小的爬蟲,那么基本無需定制Scheduler,但是了解一下已經提供的幾個Scheduler還是有意義的。
類 |
說明 |
備注 |
DuplicateRemovedScheduler |
抽象基類,提供一些模板方法 |
繼承它可以實現自己的功能 |
QueueScheduler |
使用內存隊列保存待抓取URL |
|
PriorityScheduler |
使用帶有優先級的內存隊列保存待抓取URL |
耗費內存較QueueScheduler更大,但是當設置了request.priority之后,只能使用PriorityScheduler才可使優先級生效 |
FileCacheQueueScheduler |
使用文件保存抓取URL,可以在關閉程序並下次啟動時,從之前抓取到的URL繼續抓取 |
需指定路徑,會建立.urls.txt和.cursor.txt兩個文件 |
RedisScheduler |
使用Redis保存抓取隊列,可進行多台機器同時合作抓取 |
需要安裝並啟動redis |
去除重復鏈接部分被單獨抽象成了一個接口:DuplicateRemover,從而可以為同一個Scheduler選擇不同的去重方式,以適應不同的需要,目前提供了兩種去重方式。
類 |
說明 |
HashSetDuplicateRemover |
使用HashSet來進行去重,占用內存較大 |
BloomFilterDuplicateRemover |
使用BloomFilter來進行去重,占用內存較小,但是可能漏抓頁面 |
RedisScheduler是使用Redis的set進行去重,其他的Scheduler默認都使用HashSetDuplicateRemover來進行去重。
如果要使用BloomFilter,必須要加入以下依賴:
修改代碼,添加布隆過濾器
三種去重方式的對比
❶ HashSetDuplicateRemover
使用java中的HashSet不能重復的特點去重。優點是容易理解。使用方便。
缺點:占用內存大,性能較低。
❷.RedisScheduler的set進行去重。
優點是速度快(Redis本身速度就很快),而且去重不會占用爬蟲服務器的資源,可以處理更大數據量的數據爬取。
缺點:需要准備Redis服務器,增加開發和使用成本。
❸.BloomFilterDuplicateRemover
使用布隆過濾器也可以實現去重。優點是占用的內存要比使用HashSet要小的多,也適合大量數據的去重操作。
缺點:有誤判的可能。沒有重復可能會判定重復,但是重復數據一定會判定重復。
*網頁內容去重
上文我們研究了對下載的url地址進行了去重的解決方案,避免同樣的url下載多次。其實不只是url需要去重,我們對下載的網頁內容也需要去重。在網上我們可以找到許多內容相似的文章。但是實際我們只需要其中一個即可,同樣的內容沒有必要下載多次,那么如何進行去重就需要進行處理了
去重方案介紹
❶.指紋碼對比
最常見的去重方案是生成文檔的指紋門。例如對一篇文章進行MD5加密生成一個字符串,我們可以認為這是文章的指紋碼,再和其他的文章指紋碼對比,一致則說明文章重復。但是這種方式是完全一致則是重復的,如果文章只是多了幾個標點符號,那仍舊被認為是重復的,這種方式並不合理。
❷.BloomFilter
這種方式就是我們之前對url進行去重的方式,使用在這里的話,也是對文章進行計算得到一個數,再進行對比,缺點和方法1是一樣的,如果只有一點點不一樣,也會認為不重復,這種方式不合理。
❸.KMP算法
KMP算法是一種改進的字符串匹配算法。KMP算法的關鍵是利用匹配失敗后的信息,盡量減少模式串與主串的匹配次數以達到快速匹配的目的。能夠找到兩個文章有哪些是一樣的,哪些不一樣。這種方式能夠解決前面兩個方式的“只要一點不一樣就是不重復”的問題。但是它的時空復雜度太高了,不適合大數據量的重復比對。
❹. Simhash簽名
Google 的 simhash 算法產生的簽名,可以滿足上述要求。這個算法並不深奧,比較容易理解。這種算法也是目前Google搜索引擎所目前所使用的網頁去重算法。
(1).SimHash流程介紹
simhash是由 Charikar 在2002年提出來的,為了便於理解盡量不使用數學公式,分為這幾步:
1、分詞,把需要判斷文本分詞形成這個文章的特征單詞。
2、hash,通過hash算法把每個詞變成hash值,比如“美國”通過hash算法計算為 100101,“51區”通過hash算法計算為 101011。這樣我們的字符串就變成了一串串數字。
3、加權,通過 2步驟的hash生成結果,需要按照單詞的權重形成加權數字串,“美國”的hash值為“100101”,通過加權計算為“4 -4 -4 4 -4 4”
“51區”計算為 “ 5 -5 5 -5 5 5”。
4、合並,把上面各個單詞算出來的序列值累加,變成只有一個序列串。
“美國”的 “4 -4 -4 4 -4 4”,“51區”的 “ 5 -5 5 -5 5 5”
把每一位進行累加, “4+5 -4+-5 -4+5 4+-5 -4+5 4+5”à“9 -9 1 -1 1 9”
5、降維,把算出來的 “9 -9 1 -1 1 9”變成 0 1 串,形成最終的simhash簽名。
(2).簽名距離計算
我們把庫里的文本都轉換為simhash簽名,並轉換為long類型存儲,空間大大減少。現在我們雖然解決了空間,但是如何計算兩個simhash的相似度呢?
我們通過海明距離(Hamming distance)就可以計算出兩個simhash到底相似不相似。兩個simhash對應二進制(01串)取值不同的數量稱為這兩個simhash的海明距離。
(3).測試simhash
這個項目不能直接使用,因為jar包的問題,需要導入工程simhash,並進行install。
導入對應的依賴:
測試用例:
測試結果:
3). Pipeline組件的使用
Pipeline組件的作用用於保存結果。我們現在通過“控制台輸出結果”這件事也是通過一個內置的Pipeline完成的,它叫做ConsolePipeline
。
那么,我現在想要把結果用保存到文件中,怎么做呢?只將Pipeline的實現換成"FilePipeline"就可以了。
7.網絡爬蟲的配置、啟動和終止
1).Spider
Spider是網絡爬蟲啟動的入口。在啟動爬蟲之前,我們需要使用一個PageProcessor創建一個Spider對象,然后使用run()進行啟動。
Spider的其他組件(Downloader、Scheduler)可以通過set方法來進行設置,
Pipeline組件通過add方法進行設置。
方法 |
說明 |
示例 |
create(PageProcessor) |
創建Spider |
Spider.create(new GithubRepoProcessor()) |
addUrl(String…) |
添加初始的URL |
spider .addUrl("http://webmagic.io/docs/") |
thread(n) |
開啟n個線程 |
spider.thread(5) |
run() |
啟動,會阻塞當前線程執行 |
spider.run() |
start()/runAsync() |
異步啟動,當前線程繼續執行 |
spider.start() |
stop() |
停止爬蟲 |
spider.stop() |
addPipeline(Pipeline) |
添加一個Pipeline,一個Spider可以有多個Pipeline |
spider .addPipeline(new ConsolePipeline()) |
setScheduler(Scheduler) |
設置Scheduler,一個Spider只能有個一個Scheduler |
spider.setScheduler(new RedisScheduler()) |
setDownloader(Downloader) |
設置Downloader,一個Spider只能有個一個Downloader |
spider .setDownloader( new SeleniumDownloader()) |
get(String) |
同步調用,並直接取得結果 |
ResultItems result = spider .get("http://webmagic.io/docs/") |
getAll(String…) |
同步調用,並直接取得一堆結果 |
List<ResultItems> results = spider .getAll("http://webmagic.io/docs/", "http://webmagic.io/xxx") |
2). 爬蟲配置Site
Site.me()可以對爬蟲進行一些配置配置,包括編碼、抓取間隔、超時時間、重試次數等。在這里我們先簡單設置一下:重試次數為3次,抓取間隔為一秒。
站點本身的一些配置信息,例如編碼、HTTP頭、超時時間、重試策略等、代理等,都可以通過設置Site對象來進行配置。
方法 |
說明 |
示例 |
setCharset(String) |
設置編碼 |
site.setCharset("utf-8") |
setUserAgent(String) |
設置UserAgent |
site.setUserAgent("Spider") |
setTimeOut(int) |
設置超時時間, 單位是毫秒 |
site.setTimeOut(3000) |
setRetryTimes(int) |
設置重試次數 |
site.setRetryTimes(3) |
setCycleRetryTimes(int) |
設置循環重試次數 |
site.setCycleRetryTimes(3) |
addCookie(String,String) |
添加一條cookie |
site.addCookie("dotcomt_user","code4craft") |
setDomain(String) |
設置域名,需設置域名后,addCookie才可生效 |
site.setDomain("github.com") |
addHeader(String,String) |
添加一條addHeader |
site.addHeader("Referer","https://github.com") |
setHttpProxy(HttpHost) |
設置Http代理 |
site.setHttpProxy(new HttpHost("127.0.0.1",8080)) |
8.代理服務器
有些網站不允許網絡爬蟲進行數據爬取,因為會加大服務器的壓力。解決此障礙其中一種最有效的方式是通過ip+訪問時間進行鑒別,因為正常人不可能短時間開啟太多的頁面,發起太多的請求。
我們使用的WebMagic可以很方便的設置爬取數據的時間,但是這樣會大大降低我們爬取數據的效率,如果不小心ip被禁了,會讓我們無法爬去數據,那么我們就有必要使用代理服務器來爬取數據。
1).代理服務器介紹
網絡代理(internet Proxy)是一種特殊的網絡服務,允許一個網絡終端(一般為客戶端)通過這個服務與另一個網絡終端(一般為服務器)進行非直接的連接。提供代理服務的電腦系統或其它類型的網絡終端稱為代理服務器(英文:Proxy Server)。一個完整的代理請求過程為:客戶端首先與代理服務器創建連接,接着根據代理服務器所使用的代理協議,請求對目標服務器創建連接、或者獲得目標服務器的指定資源。
注: 網上有很多代理服務器的提供商,但是大多是免費的不好用,付費的效果還不錯。
2).代理服務器的使用
WebMagic使用的代理APIProxyProvider。因為相對於Site的“配置”,ProxyProvider定位更多是一個“組件”,所以代理不再從Site設置,而是由HttpClientDownloader設置。
API |
說明 |
HttpClientDownloader.setProxyProvider(ProxyProvider proxyProvider) |
設置代理 |
ProxyProvider有一個默認實現:SimpleProxyProvider。它是一個基於簡單Round-Robin的、沒有失敗檢查的ProxyProvider。可以配置任意個候選代理,每次會按順序挑選一個代理使用。它適合用在自己搭建的比較穩定的代理的場景。
如果需要根據實際使用情況對代理服務器進行管理(例如校驗是否可用,定期清理、添加代理服務器等),只需要自己實現APIProxyProvider即可。
使用代理服務器之前,通過查詢可以獲取ip信息的網站得到如下結果:
未使用代理服務器之前,利用爬蟲程序抓取此頁面獲得的結果為本地ip地址:
使用代理服務器:
使用代理服務器之后,用爬蟲程序獲取的結果為代理服務器ip地址:
由此看出使用代理服務成功,並且訪問的ip改為代理服務器的ip.