數據大做戰

      連續一個星期了,一直是在抓數據。我的水平從小初班到了小中班。好歹是積累了些乾貨。感受有必要總結總結了。php

          在上乾貨以前,我先說明下提綱。不感興趣前部分就能夠跳過了。html

            第一節,普及下網絡數據請求過程vue

            第二節,幾種語言抓取數據的方式python

            第三節,Python抓取數據的方法和常見問題,包括中文亂碼,post傳值,域名登陸,接口獲取,數據解析處理等jquery

一,      網絡數據請求過程linux

我能夠作一個這樣的比喻,瀏覽網頁就像是在跟另外一我的用對講機通話。是的。雖然咱們一提到上網衝浪好像是在看電影似的,徹底是不勞而獲的感覺。可是實際上這是一種錯誤的認識。上網就是在通話,用戶在一頭說話,服務器在另外一頭回應。若是用戶沒有任何操做,你想要的網頁是不會跳出來的。而瀏覽器就至關於對講機,展現數據的載體。http協議就至關於通話雙方的頻段。git

再提到域名就不能用對講機舉例了,就要拿電話機來比喻了。咱們全部訪問的網站,都表明了一個徹底獨立惟一的個體,就像電話號碼。可是電話號碼太長了,就須要用另外一種容易記憶的方式來標示,這就出現了各類www域名,就像咱們手機通信錄存儲的姓名。試想一下每次使用百度都輸入’61.135.169.121’這樣一串數字,誰能吃得消啊。實際上在我們往瀏覽器輸入簡單的www.baidu.com的時候,瀏覽器仍然在須要在龐大的域名數據庫裏查詢對應的ip地址才能訪問到目標網站。好比說你想找北京水利局長,即便你很明確你要找的人是誰也沒法和他通話,由於你首先要經過查詢找到他的電話號碼才能撥通。若是你找不到他的號碼,就會獲得提示‘查無此人’,這種事情發生在網絡上就會出現最經典的404報錯。程序員

   假設網站能夠正常訪問了,至關於電話撥通了。可是並不表明能夠正常通話了。由於對方是水利局長天天受到的騷擾多了去了,首先你得代表身份是什麼地方的誰誰誰,當電話那頭容許了。你這邊再說一句‘下面我將代表個人訴求‘,再接下來纔是進入網絡訪問過程。這就是傳說中的三次握手。github

   既然通話網路已經創建,就進入到了通話過程。一頭說,「我想讓某某給我唱一首傷心太平洋「」我想讓某某某號給我講一個笑話「,而後對講機那一頭就按你的要求進行回覆。即便這個時候也是不能保證有求必應的,好比403錯誤,你想看看對方老婆照片,對方確定是不容許的;再好比500錯誤,對方臨時上廁所開小差抽風了無法正常通話了;還有一種屬於正常300提示,對方轉接到其它線路。ajax

      我所認識的網絡請求就是這個樣子了。但願能知足諸位某些疑惑,也歡迎提出在下認識中的不足之處。

二,幾中編程語言抓取數據的方式

      1)

本人是搞PHP的,其它領域就業餘不少了。因此先拿php說吧,

      最簡單的就是fopen /file_get_contents函數了,這就特別像拿來主意了,至關於網頁右鍵查看源代碼再複製下來。可是實際上他的簡單也說明了它的侷限性,只能抓取現成的頁面代碼,對於post傳值和須要登陸的就無能爲力了,並且它不能keeplive,就像每次通話都要從新撥號,獲得需求後再掛斷。有些影響效率,比較適用於文本內容比較簡單並且不做處理的頁面。

      另外一種就是curl模擬提交了,屬於僞造瀏覽器訪問。能夠設置post參數,甚至能夠傳輸文件,應該範圍很廣,尤爲是公衆號開發,第三方接口獲取數據。缺點就是配置特別多,因此我猜大部分跟我同樣使用複製大法進行套用。另外,curl對於登陸驗證碼這種高難度的操做就不太靈了。

      2)

      再接下來講一說js,jquery,vue.js,jsonp,ajax,這幾種不一樣的名字在獲取數據方法其實都是一種方式,就是ajax異步獲取。固然其中確定是有差異的,後面再細說。

      它的優勢是沒必要整個頁面從新獲取,而是隻獲取某一個數字或圖片或單獨一頁內容再進行填充,用戶體驗效果好,而且節省網絡資源。缺點是它並不是真正意義上的抓取數據,而是相似於接收數據。由於ajax獲取的數據及數據格式都是預先定義好的,只須要按照預約參數去獲取就行了。

      其中jsonp是較特殊的一種了,優勢是能夠跨域,就是能夠獲取不一樣域名站點的內容。缺點是隻能使用get提交。至於使用非jsonp跨域獲取數據的ajax相似的在網上還有大把方法可尋。世上無難事,只怕有心人。

      3)

      Node.js,聽說這是近兩年特別火爆的語言。我就感受作程序員就是個無底洞,三天兩頭出來個新語言,新升級,新類庫。不學不行,學了也感受就那樣。因此我就看了兩個小時的教程,get 了點皮毛,貌似是功能挺強大,操做也挺簡單。能夠完成各類高難度的數據抓取。下面是一段簡單的抓取數據的源碼,包含抓取到解析的過程,自行體會吧。

  1 var http = require('http')
  2 
  3 var cheerio = require('./cheerio')
  4 
  5 var url  = 'http://www.imooc.com/learn/348'
  6 
  7 function filterChapter(html)
  8 
  9 {
 10 
 11       var $ = cheerio.load(html)
 12 
 13       chapters = $('.chapter')
 14 
 15       var courseData = []
 16 
 17       chapters.each(function(item){
 18 
 19            var chapter = $(this)
 20 
 21            var chapterTitle = chapter.find('strong').text()
 22 
 23            var videos = chapter.find('.video').children('li')
 24 
 25            var chapterData = {
 26 
 27                  chapterTitle:chapterTitle,
 28 
 29                  videos:[]
 30 
 31            }
 32 
 33            videos.each(function(item){
 34 
 35                  var video = $(this).find('.J-media-item')
 36 
 37                  var videoTitle = video.text()
 38 
 39                  var id = video.attr('href').split('video/')[1]
 40 
 41                  chapterData.videos.push({
 42 
 43                       title:videoTitle,
 44 
 45                       id:id
 46 
 47                  })
 48 
 49            })
 50 
 51            courseData.push(chapterData)
 52 
 53       })
 54 
 55       return courseData
 56 
 57 }
 58 
 59 function printCourseInfo(courseData){
 60 
 61       courseData.forEach(function(item){
 62 
 63            var chapterTitle = item.chapterTitle
 64 
 65            var chapterVideos = item.videos
 66 
 67            console.log(chapterTitle+'\n')
 68 
 69            chapterVideos.forEach(function(video){
 70 
 71                  var videoTitle = video.title
 72 
 73                  var videoId    = video.id
 74 
 75                  console.log('['+videoId+']'+videoTitle)
 76 
 77            })
 78 
 79       })
 80 
 81 }
 82 
 83 http.get(url,function(res){
 84 
 85       var html = ''
 86 
 87       res.on('data',function(data){
 88 
 89            html+=data
 90 
 91       })
 92 
 93       res.on('end',function(){
 94 
 95            courseData = filterChapter(html)
 96 
 97            //console.log(courseData)
 98 
 99            printCourseInfo(courseData)
100 
101       })
102 
103 }).on('error',function(){
104 
105       console.log('獲取數據出錯')
106 
107 })

 

4)

最後再說一說python了,這門語言算是抓取數據的老牌專業戶了,一提到它的名字就會聯想到’爬蟲’二字。我對它接觸了有一個來月吧,就感受python是個啥也不是,可是啥也能幹的玩意。若是不加載模塊好像啥也幹不了了,可是全部想到的需求只要配合相關模塊全都能作。好比能作網站,能作桌面應用程序,也能寫用來執行爬蟲的頁面。

 1 user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
 2 
 3 headers = { 'User-Agent' : user_agent }
 4 
 5 response = urllib2.urlopen(request)
 6 
 7 if response.getcode()!=200:
 8 
 9 return None
10 
11 return response.read()

 

      這一段是基本的抓網頁內容的代碼,是否是比較牛逼,它的優勢是徹底假裝成瀏覽器的樣子,連什麼樣的瀏覽器,什麼樣的系統,用什麼方式去訪問,需不須要ip代理,徹底照辦。因此我仍是推薦使用python來抓取數據,代碼簡單,模塊豐富,功能強悍,linux還自帶python,只要花兩三個小時就能get到一套受用的抓取方法。每次寫好程序看着終端窗口不斷跳動抓取過程,爽得不要不要的。

      以上是我所知領域的抓取方式,畢竟也是小白一枚,未見過大世面。大牛大咖們莫要恥笑。

三,python爬蟲的常見方法和問題總結

      總算是進入乾貨階段了,方法一說出來就很簡單了。可是這些方法和問題倒是實例開發中遇到且花長時間才找到解決方法的,好比說中文亂碼的問題,我在網上查詢解決方案,五花八門感受都說得有理,折騰了三個多小時就是不成功。最終是本身嘗試才找到方法,後面會詳細說明。這裏就很少說了。

      首先說說爬取數據的方法,前面已經貼了一部分。在這裏我們再系統的羅列一遍。基本上用得都是urllib2和urllib模塊。

a)最簡單的不帶參數的爬取。

 f = urllib2.urlopen(url, timeout=5).read()  

b)帶header參數的

 1 user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
 2 
 3 headers = { 'User-Agent' : user_agent }
 4 
 5 
 6 
 7 request = urllib2.Request(url, headers = headers)
 8 
 9 response = urllib2.urlopen(request)
10 
11 if response.getcode()!=200:
12 
13 return None
14 
15 return response.read()

 

c)帶post參數的

 1 user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
 2 
 3 headers = { 'User-Agent' : user_agent }
 4 
 5 value={
 6 
 7 'name':'BUPT',
 8 
 9 'age':'60',
10 
11 'location':'Beijing'#字典中的內容隨意,不影響#
12 
13 }
14 
15 postdata=urllib.urlencode(value)#對value進行編碼,轉換爲標準編碼#
16 
17 request = urllib2.Request(url,data=postdata,headers = headers)
18 
19 response = urllib2.urlopen(request)
20 
21 if response.getcode()!=200:
22 
23 return None
24 
25 return response.read()

 

d)須要登陸和驗證碼的頁面,利用本地cookie文件爬取,須要使用cookielib模塊

 1 cookie=cookielib.MozillaCookieJar()
 2 
 3 cookie.load('cookies.txt',ignore_expires=True,ignore_discard=True)
 4 
 5 req=urllib2.Request('http://bbs.fobshanghai.com/qun.php?subcatid=576')
 6 
 7 opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
 8 
 9 urllib2.install_opener(opener)
10 
11 response=urllib2.urlopen(req)
12 
13 print response.read()

 

PS:cookie文件能夠利用firebug工具導出,要特別注意兩點,1是在文本第一行加上‘# Netscape HTTP Cookie File’,這樣一句話聲明這是一個cookie文件,否則會報錯,http.cookiejar.LoadError: 'cookies.txt' does not look like a Netscape format cookies file  2.檢查一下每行的項目是否是齊全,裏面包含了每條cookie的域名,布爾值,適用途徑,是否使用安全協議,過時時間,名稱,值共7個屬性,缺一個就會報錯。若是少的話,隨便從其它行復制過來就能夠。若是再報錯,就用一個笨方法。複製一行正確的cookie,而後每一個屬性值一一複製粘貼過來。關鍵時刻還就得這樣整。

e)獲取json數據

按說獲取接口的jsono數據是最簡單的了,可是習慣使用beautiful模塊去解析頁面的DOM元素,遇到json數據反而傻眼了。因此在這裏特別說明下json數據解析使用到的模塊和方法。

解析json數據,我使用的是demjson模塊,操做也是很簡單的。使用datas = demjson.decode(soup),就能夠像使用字典那樣去取值了。

f)涉及用戶登陸及驗證碼的網頁

針對驗證碼的爬取方法,網上一樣有不少教程。可是本人沒有成功完成的經驗因此就不敢妄言了,目前來看掌握這幾種方法就已經夠用了。

說完了獲取接下來就談一談解析了,畢竟拿到手的不是json數據而是一堆零亂的html文件,怎麼取本身想的數據還要有不少路要走。我常用的beautiful模塊,下面也是就這個模塊來進行講解。

a),獲取id,class,以及標籤的DOM元素

cont = soup.find('div',id="d_list")   #獲取id名稱爲‘d_list’的div元素

link.find('span',class_="c_tit")     #獲取class名稱爲’c_tit’的span元素,注意class_不一樣於’class’

b)獲取元素的文本內容和屬性值

soup.find('a').get_text()     #獲取a標籤的文本內容

soup.find('a')['href']          #獲取a標籤的連接地址

soup.find('img')['src']          #獲取圖片地址

c)去除DOM元素中的部分元素

cont = soup.find('div',id="d_list")

cont.find('div',class_="ppp").extract()    #去除id爲’d_list’元素中的class爲‘ppp’的div元素

d)去除a標籤的href屬性

del link.find('a')[‘href’]    #實際開發中須要遍歷操做

e)去除某DOM元素中的第N個某標籤元素,或倒數第N個某標籤元素

看似傷腦筋的題目,其實只要循環遍歷其中全部元素,而後按照下標作選擇判斷就能夠了。若是是去除倒數第N個元素,能夠循環兩次,第一次取到元素總數,第二次再相應操做就能夠了。對於這種問題關鍵是思路。

f)圖片地址問題

由於爬取數據的同時,也須要把圖片抓取到本地,這就面臨一個圖片地址的問題。頁面內容的圖片地址和獲取到本身服務器上的圖片地址必須相對應。我在這裏提供兩個思路,一是若是抓取的網站裏面的圖片都是放在一個服務器上,那麼就遠程獲取圖片下載到本身服務器的相同目錄下。這樣圖片抓到了,內容也抓到了,還不影響頁面展現。因此把頁面裏的全部圖片地址傳到一個列表裏,再一一遠程下載到本地服務器。二是面對要抓取的網站裏的圖片來自不一樣服務器,這就有些棘手。即便是抓取到本身服務器相同的目錄,也要使用正則表達把頁面裏圖片的地址替換掉。我估計大部門程序員對正則仍是有些怵的。因此我就採用了迂迴戰術,遍歷出頁面裏全部的圖片地址,分別下載到本地服務器後分別替換新地址。代碼比較長就不在這裏展現了,放個github庫地址給你們觀摩吧,https://github.com/zuoshoupai/code/tree/master/wxfunyparse

      常見的招數也就這些了,最後再說一說使人頭疼的中文亂碼問題。

      網上對於亂碼的說法有不少,大概也是因爲形成亂碼的緣由有不少吧。不能所有適用。我就先把通常出現的緣由羅列一下,a)頁面自己是非utf8編碼 ;b)程序開發環境也就是編輯器非utf8編碼  c)服務響應的Accept-Encoding採用了gzip壓縮 d)其它緣由,好比cmd不支持顯示中文或者設置不正確

      由於形成亂碼的緣由有不少,因此咱們首先要排查是哪一種問題形成的。個人建議是在獲取到頁面的內容後立刻輸出,並導出到本地文件,也就是response.read()後立刻打印。若是輸出顯示或是本地文件顯示正常,就表示頁面編碼是沒有問題的,問題出在解析上。因此使用response.read().encode('utf-8')解決問題。我上次遇到的問題非常奇葩,頁面獲取內容後打印輸入正常,而後解析處理後就亂碼了,我就使用各類decode,encode咋都很差使。最終沒轍了,使用chardet模塊把每一步的編碼格式都打印出來,發如今傳輸過程當中,gbk變成了utf8,也就是說明明是gbk的文本當成是utf8展現,並且還轉不過來。最後我把最開始獲取數據的地方轉換成utf8就OK了。

但願這個小例子能給你帶來一些啓發,遇到bug千萬不能亂,必須一步一步整理思路,肯定問題所在,才能一擊而中,成功解決。

      好了,關於爬數據的知識總結就到這了。如今忽然有種頓悟,所謂編程就是數據的交互,正確的傳給別人和正確的拿到別人的。說來簡單,中間的過程就是千辛萬苦了。

相關文章
相關標籤/搜索