週末沒事幹,無聊,使用php作了個博客抓取系統,我常常訪問的是cnblogs,固然從博客園(看看我仍是很喜歡博客園的)開始入手了,個人抓取比較簡易,獲取網頁內容,而後經過正則匹配,獲取到想要的東西,而後保存數據庫,固然了,在實際過程當中會遇到一些問題。作這個以前已經想好了,要作成可擴充的,之後要是哪天想添加csdn、51cto、新浪博客這些內容了能夠很容易的擴展。php
首先要說些,這個是個簡易的抓取,不是全部網頁中看到的東西均可以抓取,有些東西是抓取不到的,就像下面這些css
其中圈紅的閱讀次數、評論次數、推薦次數、反對次數、評論……,這些是經過js調用ajax動態獲取的,因此是獲取不到的,其實就一句話,你打開一個網頁,而後右鍵點擊查看源代碼,在源代碼中直接看不到的,這種簡易抓取可能就有問題,要抓取那些ajax填充的內容,要想一想其餘辦法,以前看見過一篇文章,有人先經過瀏覽器加載完網頁,而後對整個dom就行篩選(那篇文章也說了,這樣效率很低),固然了,拼接這些js請求也是能夠的,估計會比較麻煩。html
首先說下爬取深度depthjava
好比從連接a開始爬,若是depth是1,獲取玩當前連接的內容就完事,若是depth是2的話,就從a連接的內容中再去按指定的規則匹配連接,對匹配到的連接也作depth爲1的處理,以此類推,depth是獲取連接的深度、層級。這樣爬蟲才能夠」爬動起來「。git
固然了,用一個連接去爬特定的內容,這個爬到的東西是頗有限的,或者有可能還沒爬起來就死掉了(日後的層級沒有匹配到內容),因此在爬取的時候能夠設置多個起始連接。固然了,在爬取的時候極可能會遇到不少重複的連接,因此還得給抓取到的連接作記號,防止重複獲取相同的內容,形成冗餘。有幾個變量來緩存這些信息,格式以下github
第一,就是一個hash數組,鍵值是url的md5值,狀態是0,維護一個不重複的url數組,形以下面的形式ajax
Array ( [bc790cda87745fa78a2ebeffd8b48145] => 0 [9868e03f81179419d5b74b5ee709cdc2] => 0 [4a9506d20915a511a561be80986544be] => 0 [818bcdd76aaa0d41ca88491812559585] => 0 [9433c3f38fca129e46372282f1569757] => 0 [f005698a0706284d4308f7b9cf2a9d35] => 0 [e463afcf13948f0a36bf68b30d2e9091] => 0 [23ce4775bd2ce9c75379890e84fadd8e] => 0 ...... )
第二個就是要獲取的url數組,這個地方還能夠優化,我是將全部的連接連接所有獲取到數組中,再去循環數組獲取內容,就等因而說,全部最大深度減1的內容都獲取了兩次,這裏能夠直接在獲取下一級內容的時候順便把內容獲取了,而後上面的數組中狀態修改成1(已經獲取),這樣能夠提升效率。先看看保存連接的數組內容:正則表達式
Array ( [0] => Array ( [0] => http://zzk.cnblogs.com/s?t=b&w=php&p=1 ) [1] => Array ( [0] => http://www.cnblogs.com/baochuan/archive/2012/03/12/2391135.html [1] => http://www.cnblogs.com/ohmygirl/p/internal-variable-1.html [2] => http://www.cnblogs.com/zuoxiaolong/p/java1.html ...... ) [2] => Array ( [0] => http://www.cnblogs.com/ohmygirl/category/623392.html [1] => http://www.cnblogs.com/ohmygirl/category/619019.html [2] => http://www.cnblogs.com/ohmygirl/category/619020.html ...... ) )
最後將全部的連接拼爲一個數組返回,讓程序循環獲取鏈接中的內容。就像上面的獲取層級是2,0級的鏈內容接獲取過了,僅僅用來獲取1級中的連接,1級中的全部連接內容也獲取過了,僅僅用來保存2級中的連接,等到真正獲取內容的時候又會對上面的內容進行一次獲取,並且上面的hash數組中的狀態都沒有用到。。。(有待優化)。sql
還有一個獲取文章的正則,經過分析博客園中的文章內容,發現文章標題、正文部分基本均可以很規則的獲取到數據庫
標題,標題html代碼的形式都是下圖的那種格式,能夠很輕鬆的用下面的正則匹配到
#<a\s*?id=\"cb_post_title_url\"[^>]*?>(.*?)<\/a>#is
正文,正文部分是能夠經過正則表達式的高級特性平衡組很容易獲取到的,但弄了半天發現php好像對平衡組支持的不是很好,因此放棄額平衡組,在html源碼中發現經過下面的正則也能夠很容易匹配到文章正文的內容,每篇文章基本都有下圖中的內容
#(<div\s*?id=\"cnblogs_post_body\"[^>]*?>.*)<div\s*id=\"blog_post_info_block\">#is
開始:
結束:
博客的發佈時間也是能夠獲取到的,但有些文章在獲取發佈時間的時候可能會找不到,這個就不列在這裏了,有了這些東西就能夠爬取內容了。
開始爬取內容了,最初我設置的爬取深度是2級,初始頁面是博客園首頁,發現爬取不了多少內容,後來發現博客園首頁有個頁碼導航
就試圖拼接成頁碼格式http://www.cnblogs.com/#p2,循環200次,以每頁爲起始頁面,深度爲2去抓取。但我高興的太早了,開了幾個進程跑了很久程序,抓了幾十萬條,後來發現徹底在重複,都是從第一頁中抓取的,由於博客園首頁點擊導航的時候(除了第一頁),都是ajax請求獲取到的。。。。看來博客園仍是考慮到這個問題,由於大多數人都是隻打開首頁,不會去點擊後面的內容(我可能偶爾會去點擊下一頁),因此爲了在防止初級抓取者去抓取和性能發麪作權衡,將第一頁設置爲靜態網頁的方式,緩存有效期是幾分鐘(或者是根據跟新頻率,當更新多少篇的時候去更新緩存,或者二者的結合),這也是爲何有時候發佈的文章,過一下子纔會顯示出來的緣由(我猜的^_^)。
難道不能一次性抓取不少內容嗎?後來我發現這個地方使用的所有是靜態網頁
從找找看這個地方獲取到的內容都是靜態的,包括最下面的導航連接中的全部頁面都是靜態的,並且,這個搜索右邊還有篩選條件,能夠更好的提升抓取的質量。好了有了這個入口,就能夠獲取到好多高質量的文章了,下面是循環抓取100頁的代碼
for($i=1;$i<=100;$i++){ echo "PAGE{$i}*************************[begin]***************************\r"; $spidercnblogs = new C\Spidercnblogs("http://zzk.cnblogs.com/s?t=b&w=php&p={$i}"); $urls = $spidercnblogs->spiderUrls(); die(); foreach ($urls as $key => $value) { $cnblogs->grap($value); $cnblogs->save(); } }
至此,就能夠去抓去本身喜歡的東西了,抓取速度不是很快,我在一臺普通pc上面開了10個進程,抓了好幾個小時,才獲取到了40多萬條數據,好了看看抓取到的內容稍微優化以後的顯示效果,這裏面加上了博客園的基礎css代碼,能夠看出效果和
抓取到的內容稍做修改:
原始內容
再看下文件目錄結構,也是用上篇的自制目錄生成工具生成的:
+myBlogs-master
+controller
|_Blog.php
|_Blogcnblogs.php
|_Spider.php
|_Spidercnblogs.php
+core
|_Autoload.php
+interface
|_Blog.php
+lib
|_Mysql.php
+model
|_Blog.php
|_App.php
今天又想到,抓取的時候用隊列應該比較合適,新抓取的連接放在隊頭,從隊尾獲取連接的內容並匹配生成新的連接插入隊頭,這樣思路應該更清晰,更簡單。
效果仍是很不錯的,這裏再猜下推酷這種專門爬取的網站的工做方式,一個常駐進程,隔一段時間去獲取一次內容(好比說首頁),若是有新鮮的內容入庫,沒有的話放棄此次獲取的內容,等待下次獲取,當時間很小的時候就能夠一篇不漏的抓取的」新鮮「的內容。
這是github地址:
本文版權歸做者iforever(luluyrt@163.com)全部,未經做者本人贊成禁止任何形式的轉載,轉載文章以後必須在文章頁面明顯位置給出做者和原文鏈接,不然保留追究法律責任的權利。