整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          手把手教你用Python網絡爬蟲爬網頁(附代碼)

          讀:本文主要分為兩個部分:一部分是網絡爬蟲的概述,幫助大家詳細了解網絡爬蟲;另一部分是HTTP請求的Python實現,幫助大家了解Python中實現HTTP請求的各種方式,以便具備編寫HTTP網絡程序的能力。

          作者:范傳輝

          如需轉載請聯系華章科技

          01 網絡爬蟲概述

          接下來從網絡爬蟲的概念、用處與價值和結構等三個方面,讓大家對網絡爬蟲有一個基本的了解。

          1. 網絡爬蟲及其應用

          隨著網絡的迅速發展,萬維網成為大量信息的載體,如何有效地提取并利用這些信息成為一個巨大的挑戰,網絡爬蟲應運而生。網絡爬蟲(又被稱為網頁蜘蛛、網絡機器人),是一種按照一定的規則,自動地抓取萬維網信息的程序或者腳本。下面通過圖3-1展示一下網絡爬蟲在互聯網中起到的作用:

          ▲圖3-1 網絡爬蟲

          網絡爬蟲按照系統結構和實現技術,大致可以分為以下幾種類型:通用網絡爬蟲、聚焦網絡爬蟲、增量式網絡爬蟲、深層網絡爬蟲。實際的網絡爬蟲系統通常是幾種爬蟲技術相結合實現的。

          搜索引擎(Search Engine),例如傳統的通用搜索引擎baidu、Yahoo和Google等,是一種大型復雜的網絡爬蟲,屬于通用性網絡爬蟲的范疇。但是通用性搜索引擎存在著一定的局限性:

          1. 不同領域、不同背景的用戶往往具有不同的檢索目的和需求,通用搜索引擎所返回的結果包含大量用戶不關心的網頁。
          2. 通用搜索引擎的目標是盡可能大的網絡覆蓋率,有限的搜索引擎服務器資源與無限的網絡數據資源之間的矛盾將進一步加深。
          3. 萬維網數據形式的豐富和網絡技術的不斷發展,圖片、數據庫、音頻、視頻多媒體等不同數據大量出現,通用搜索引擎往往對這些信息含量密集且具有一定結構的數據無能為力,不能很好地發現和獲取。
          4. 通用搜索引擎大多提供基于關鍵字的檢索,難以支持根據語義信息提出的查詢。

          為了解決上述問題,定向抓取相關網頁資源的聚焦爬蟲應運而生。

          聚焦爬蟲是一個自動下載網頁的程序,它根據既定的抓取目標,有選擇地訪問萬維網上的網頁與相關的鏈接,獲取所需要的信息。與通用爬蟲不同,聚焦爬蟲并不追求大的覆蓋,而將目標定為抓取與某一特定主題內容相關的網頁,為面向主題的用戶查詢準備數據資源。

          說完了聚焦爬蟲,接下來再說一下增量式網絡爬蟲。增量式網絡爬蟲是指對已下載網頁采取增量式更新和只爬行新產生的或者已經發生變化網頁的爬蟲,它能夠在一定程度上保證所爬行的頁面是盡可能新的頁面。

          和周期性爬行和刷新頁面的網絡爬蟲相比,增量式爬蟲只會在需要的時候爬行新產生或發生更新的頁面,并不重新下載沒有發生變化的頁面,可有效減少數據下載量,及時更新已爬行的網頁,減小時間和空間上的耗費,但是增加了爬行算法的復雜度和實現難度。

          例如:想獲取趕集網的招聘信息,以前爬取過的數據沒有必要重復爬取,只需要獲取更新的招聘數據,這時候就要用到增量式爬蟲。

          最后說一下深層網絡爬蟲。Web頁面按存在方式可以分為表層網頁和深層網頁。表層網頁是指傳統搜索引擎可以索引的頁面,以超鏈接可以到達的靜態網頁為主構成的Web頁面。深層網絡是那些大部分內容不能通過靜態鏈接獲取的、隱藏在搜索表單后的,只有用戶提交一些關鍵詞才能獲得的Web頁面。

          例如用戶登錄或者注冊才能訪問的頁面。可以想象這樣一個場景:爬取貼吧或者論壇中的數據,必須在用戶登錄后,有權限的情況下才能獲取完整的數據。

          2. 網絡爬蟲結構

          下面用一個通用的網絡爬蟲結構來說明網絡爬蟲的基本工作流程,如圖3-4所示。

          ▲圖3-4 網絡爬蟲結構

          網絡爬蟲的基本工作流程如下:

          1. 首先選取一部分精心挑選的種子URL。
          2. 將這些URL放入待抓取URL隊列。
          3. 從待抓取URL隊列中讀取待抓取隊列的URL,解析DNS,并且得到主機的IP,并將URL對應的網頁下載下來,存儲進已下載網頁庫中。此外,將這些URL放進已抓取URL隊列。
          4. 分析已抓取URL隊列中的URL,從已下載的網頁數據中分析出其他URL,并和已抓取的URL進行比較去重,最后將去重過的URL放入待抓取URL隊列,從而進入下一個循環。

          02 HTTP請求的Python實現

          通過上面的網絡爬蟲結構,我們可以看到讀取URL、下載網頁是每一個爬蟲必備而且關鍵的功能,這就需要和HTTP請求打交道。接下來講解Python中實現HTTP請求的三種方式:urllib2/urllib、httplib/urllib以及Requests。

          1. urllib2/urllib實現

          urllib2和urllib是Python中的兩個內置模塊,要實現HTTP功能,實現方式是以urllib2為主,urllib為輔。

          1.1 首先實現一個完整的請求與響應模型

          urllib2提供一個基礎函數urlopen,通過向指定的URL發出請求來獲取數據。最簡單的形式是:

          import urllib2
          response=urllib2.urlopen('http://www.zhihu.com')
          html=response.read()
          print html
          

          其實可以將上面對http://www.zhihu.com的請求響應分為兩步,一步是請求,一步是響應,形式如下:

          import urllib2
          # 請求
          request=urllib2.Request('http://www.zhihu.com')
          # 響應
          response = urllib2.urlopen(request)
          html=response.read()
          print html
          

          上面這兩種形式都是GET請求,接下來演示一下POST請求,其實大同小異,只是增加了請求數據,這時候用到了urllib。示例如下:

          import urllib
          import urllib2
          url = 'http://www.xxxxxx.com/login'
          postdata = {'username' : 'qiye',
           'password' : 'qiye_pass'}
          # info 需要被編碼為urllib2能理解的格式,這里用到的是urllib
          data = urllib.urlencode(postdata)
          req = urllib2.Request(url, data)
          response = urllib2.urlopen(req)
          html = response.read()
          

          但是有時會出現這種情況:即使POST請求的數據是對的,但是服務器拒絕你的訪問。這是為什么呢?問題出在請求中的頭信息,服務器會檢驗請求頭,來判斷是否是來自瀏覽器的訪問,這也是反爬蟲的常用手段。

          1.2 請求頭headers處理

          將上面的例子改寫一下,加上請求頭信息,設置一下請求頭中的User-Agent域和Referer域信息。

          import urllib
          import urllib2
          url = 'http://www.xxxxxx.com/login'
          user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
          referer='http://www.xxxxxx.com/'
          postdata = {'username' : 'qiye',
           'password' : 'qiye_pass'}
          # 將user_agent,referer寫入頭信息
          headers={'User-Agent':user_agent,'Referer':referer}
          data = urllib.urlencode(postdata)
          req = urllib2.Request(url, data,headers)
          response = urllib2.urlopen(req)
          html = response.read()
          

          也可以這樣寫,使用add_header來添加請求頭信息,修改如下:

          import urllib
          import urllib2
          url = 'http://www.xxxxxx.com/login'
          user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
          referer='http://www.xxxxxx.com/'
          postdata = {'username' : 'qiye',
           'password' : 'qiye_pass'}
          data = urllib.urlencode(postdata)
          req = urllib2.Request(url)
          # 將user_agent,referer寫入頭信息
          req.add_header('User-Agent',user_agent)
          req.add_header('Referer',referer)
          req.add_data(data)
          response = urllib2.urlopen(req)
          html = response.read()
          

          對有些header要特別留意,服務器會針對這些header做檢查,例如:

          • User-Agent:有些服務器或Proxy會通過該值來判斷是否是瀏覽器發出的請求。
          • Content-Type:在使用REST接口時,服務器會檢查該值,用來確定HTTP Body中的內容該怎樣解析。在使用服務器提供的RESTful或SOAP服務時,Content-Type設置錯誤會導致服務器拒絕服務。常見的取值有:application/xml(在XML RPC,如RESTful/SOAP調用時使用)、application/json(在JSON RPC調用時使用)、application/x-www-form-urlencoded(瀏覽器提交Web表單時使用)。
          • Referer:服務器有時候會檢查防盜鏈。

          1.3 Cookie處理

          urllib2對Cookie的處理也是自動的,使用CookieJar函數進行Cookie的管理。如果需要得到某個Cookie項的值,可以這么做:

          import urllib2
          import cookielib
          cookie = cookielib.CookieJar()
          opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
          response = opener.open('http://www.zhihu.com')
          for item in cookie:
           print item.name+':'+item.value
          

          但是有時候會遇到這種情況,我們不想讓urllib2自動處理,我們想自己添加Cookie的內容,可以通過設置請求頭中的Cookie域來做:

          import urllib2
          opener = urllib2.build_opener()
          opener.addheaders.append( ( 'Cookie', 'email=' + "xxxxxxx@163.com" ) )
          req = urllib2.Request( "http://www.zhihu.com/" )
          response = opener.open(req)
          print response.headers
          retdata = response.read()
          

          1.4 Timeout設置超時

          在Python2.6之前的版本,urllib2的API并沒有暴露Timeout的設置,要設置Timeout值,只能更改Socket的全局Timeout值。示例如下:

          import urllib2
          import socket
          socket.setdefaulttimeout(10) # 10 秒鐘后超時
          urllib2.socket.setdefaulttimeout(10) # 另一種方式
          

          在Python2.6及新的版本中,urlopen函數提供了對Timeout的設置,示例如下:

          import urllib2
          request=urllib2.Request('http://www.zhihu.com')
          response = urllib2.urlopen(request,timeout=2)
          html=response.read()
          print html
          

          1.5 獲取HTTP響應碼

          對于200 OK來說,只要使用urlopen返回的response對象的getcode()方法就可以得到HTTP的返回碼。但對其他返回碼來說,urlopen會拋出異常。這時候,就要檢查異常對象的code屬性了,示例如下:

          import urllib2
          try:
           response = urllib2.urlopen('http://www.google.com')
           print response
          except urllib2.HTTPError as e:
           if hasattr(e, 'code'):
           print 'Error code:',e.code
          

          1.6 重定向

          urllib2默認情況下會針對HTTP 3XX返回碼自動進行重定向動作。要檢測是否發生了重定向動作,只要檢查一下Response的URL和Request的URL是否一致就可以了,示例如下:

          import urllib2
          response = urllib2.urlopen('http://www.zhihu.cn')
          isRedirected = response.geturl() == 'http://www.zhihu.cn'
          

          如果不想自動重定向,可以自定義HTTPRedirectHandler類,示例如下:

          import urllib2
          class RedirectHandler(urllib2.HTTPRedirectHandler):
           def http_error_301(self, req, fp, code, msg, headers):
           pass
           def http_error_302(self, req, fp, code, msg, headers):
           result = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, 
           msg, headers)
           result.status = code
           result.newurl = result.geturl()
           return result
          opener = urllib2.build_opener(RedirectHandler)
          opener.open('http://www.zhihu.cn')
          

          1.7 Proxy的設置

          在做爬蟲開發中,必不可少地會用到代理。urllib2默認會使用環境變量http_proxy來設置HTTP Proxy。但是我們一般不采用這種方式,而是使用ProxyHandler在程序中動態設置代理,示例代碼如下:

          import urllib2
          proxy = urllib2.ProxyHandler({'http': '127.0.0.1:8087'})
          opener = urllib2.build_opener([proxy,])
          urllib2.install_opener(opener)
          response = urllib2.urlopen('http://www.zhihu.com/')
          print response.read()
          

          這里要注意的一個細節,使用urllib2.install_opener()會設置urllib2的全局opener,之后所有的HTTP訪問都會使用這個代理。這樣使用會很方便,但不能做更細粒度的控制,比如想在程序中使用兩個不同的Proxy設置,這種場景在爬蟲中很常見。比較好的做法是不使用install_opener去更改全局的設置,而只是直接調用opener的open方法代替全局的urlopen方法,修改如下:

          import urllib2
          proxy = urllib2.ProxyHandler({'http': '127.0.0.1:8087'})
          opener = urllib2.build_opener(proxy,)
          response = opener.open("http://www.zhihu.com/")
          print response.read()
          

          2. httplib/urllib實現

          httplib模塊是一個底層基礎模塊,可以看到建立HTTP請求的每一步,但是實現的功能比較少,正常情況下比較少用到。在Python爬蟲開發中基本上用不到,所以在此只是進行一下知識普及。下面介紹一下常用的對象和函數:

          • 創建HTTPConnection對象:
          • class httplib.HTTPConnection(host[, port[, strict[, timeout[, source_address]]]])。
          • 發送請求:
          • HTTPConnection.request(method, url[, body[, headers]])。
          • 獲得響應:
          • HTTPConnection.getresponse()。
          • 讀取響應信息:
          • HTTPResponse.read([amt])。
          • 獲得指定頭信息:
          • HTTPResponse.getheader(name[, default])。
          • 獲得響應頭(header, value)元組的列表:
          • HTTPResponse.getheaders()。
          • 獲得底層socket文件描述符:
          • HTTPResponse.fileno()。
          • 獲得頭內容:
          • HTTPResponse.msg。
          • 獲得頭http版本:
          • HTTPResponse.version。
          • 獲得返回狀態碼:
          • HTTPResponse.status。
          • 獲得返回說明:
          • HTTPResponse.reason。

          接下來演示一下GET請求和POST請求的發送,首先是GET請求的示例,如下所示:

          import httplib
          conn =None
          try:
           conn = httplib.HTTPConnection("www.zhihu.com")
           conn.request("GET", "/")
           response = conn.getresponse()
           print response.status, response.reason
           print '-' * 40
           headers = response.getheaders()
           for h in headers:
           print h
           print '-' * 40
           print response.msg
          except Exception,e:
           print e
          finally:
           if conn:
           conn.close()
          

          POST請求的示例如下:

          import httplib, urllib
          conn = None
          try:
           params = urllib.urlencode({'name': 'qiye', 'age': 22})
           headers = {"Content-type": "application/x-www-form-urlencoded"
           , "Accept": "text/plain"}
           conn = httplib.HTTPConnection("www.zhihu.com", 80, timeout=3)
           conn.request("POST", "/login", params, headers)
           response = conn.getresponse()
           print response.getheaders() # 獲取頭信息
           print response.status
           print response.read()
          except Exception, e:
           print e
           finally:
           if conn:
           conn.close()
          

          3. 更人性化的Requests

          Python中Requests實現HTTP請求的方式,是本人極力推薦的,也是在Python爬蟲開發中最為常用的方式。Requests實現HTTP請求非常簡單,操作更加人性化。

          Requests庫是第三方模塊,需要額外進行安裝。Requests是一個開源庫,源碼位于:

          GitHub: https://github.com/kennethreitz/requests

          希望大家多多支持作者。

          使用Requests庫需要先進行安裝,一般有兩種安裝方式:

          • 使用pip進行安裝,安裝命令為:pip install requests,不過可能不是最新版。
          • 直接到GitHub上下載Requests的源代碼,下載鏈接為:
          • https://github.com/kennethreitz/requests/releases
          • 將源代碼壓縮包進行解壓,然后進入解壓后的文件夾,運行setup.py文件即可。

          如何驗證Requests模塊安裝是否成功呢?在Python的shell中輸入import requests,如果不報錯,則是安裝成功。如圖3-5所示。

          ▲圖3-5 驗證Requests安裝

          3.1 首先還是實現一個完整的請求與響應模型

          以GET請求為例,最簡單的形式如下:

          import requests
          r = requests.get('http://www.baidu.com')
          print r.content
          

          大家可以看到比urllib2實現方式的代碼量少。接下來演示一下POST請求,同樣是非常簡短,更加具有Python風格。示例如下:

          import requests
          postdata={'key':'value'}
          r = requests.post('http://www.xxxxxx.com/login',data=postdata)
          print r.content
          

          HTTP中的其他請求方式也可以用Requests來實現,示例如下:

          r = requests.put('http://www.xxxxxx.com/put', data = {'key':'value'})
          r = requests.delete('http://www.xxxxxx.com/delete')
          r = requests.head('http://www.xxxxxx.com/get')
          r = requests.options('http://www.xxxxxx.com/get')
          

          接著講解一下稍微復雜的方式,大家肯定見過類似這樣的URL:

          http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1

          就是在網址后面緊跟著“?”,“?”后面還有參數。那么這樣的GET請求該如何發送呢?肯定有人會說,直接將完整的URL帶入即可,不過Requests還提供了其他方式,示例如下:

          import requests
           payload = {'Keywords': 'blog:qiyeboy','pageindex':1}
          r = requests.get('http://zzk.cnblogs.com/s/blogpost', params=payload)
          print r.url
          

          通過打印結果,我們看到最終的URL變成了:

          http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1

          3.2 響應與編碼

          還是從代碼入手,示例如下:

          import requests
          r = requests.get('http://www.baidu.com')
          print 'content-->'+r.content
          print 'text-->'+r.text
          print 'encoding-->'+r.encoding
          r.encoding='utf-8'
          print 'new text-->'+r.text
          

          其中r.content返回的是字節形式,r.text返回的是文本形式,r.encoding返回的是根據HTTP頭猜測的網頁編碼格式。

          輸出結果中:“text-->”之后的內容在控制臺看到的是亂碼,“encoding-->”之后的內容是ISO-8859-1(實際上的編碼格式是UTF-8),由于Requests猜測編碼錯誤,導致解析文本出現了亂碼。Requests提供了解決方案,可以自行設置編碼格式,r.encoding='utf-8'設置成UTF-8之后,“new text-->”的內容就不會出現亂碼。

          但是這種手動的方式略顯笨拙,下面提供一種更加簡便的方式:chardet,這是一個非常優秀的字符串/文件編碼檢測模塊。安裝方式如下:

          pip install chardet
          

          安裝完成后,使用chardet.detect()返回字典,其中confidence是檢測精確度,encoding是編碼形式。示例如下:

          import requests
          r = requests.get('http://www.baidu.com')
          print chardet.detect(r.content)
          r.encoding = chardet.detect(r.content)['encoding']
          print r.text
          

          直接將chardet探測到的編碼,賦給r.encoding實現解碼,r.text輸出就不會有亂碼了。

          除了上面那種直接獲取全部響應的方式,還有一種流模式,示例如下:

          import requests
          r = requests.get('http://www.baidu.com',stream=True)
          print r.raw.read(10)
          

          設置stream=True標志位,使響應以字節流方式進行讀取,r.raw.read函數指定讀取的字節數。

          3.3 請求頭headers處理

          Requests對headers的處理和urllib2非常相似,在Requests的get函數中添加headers參數即可。示例如下:

          import requests
          user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
          headers={'User-Agent':user_agent}
          r = requests.get('http://www.baidu.com',headers=headers)
          print r.content
          

          3.4 響應碼code和響應頭headers處理

          獲取響應碼是使用Requests中的status_code字段,獲取響應頭使用Requests中的headers字段。示例如下:

          import requests
          r = requests.get('http://www.baidu.com')
          if r.status_code == requests.codes.ok:
           print r.status_code# 響應碼
           print r.headers# 響應頭
           print r.headers.get('content-type')# 推薦使用這種獲取方式,獲取其中的某個字段
           print r.headers['content-type']# 不推薦使用這種獲取方式
          else:
           r.raise_for_status()
          

          上述程序中,r.headers包含所有的響應頭信息,可以通過get函數獲取其中的某一個字段,也可以通過字典引用的方式獲取字典值,但是不推薦,因為如果字段中沒有這個字段,第二種方式會拋出異常,第一種方式會返回None。

          r.raise_for_status()是用來主動地產生一個異常,當響應碼是4XX或5XX時,raise_for_status()函數會拋出異常,而響應碼為200時,raise_for_status()函數返回None。

          3.5 Cookie處理

          如果響應中包含Cookie的值,可以如下方式獲取Cookie字段的值,示例如下:

          import requests
          user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
          headers={'User-Agent':user_agent}
          r = requests.get('http://www.baidu.com',headers=headers)
          # 遍歷出所有的cookie字段的值
          for cookie in r.cookies.keys():
           print cookie+':'+r.cookies.get(cookie)
          

          如果想自定義Cookie值發送出去,可以使用以下方式,示例如下:

          import requests
          user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
          headers={'User-Agent':user_agent}
          cookies = dict(name='qiye',age='10')
          r = requests.get('http://www.baidu.com',headers=headers,cookies=cookies)
          print r.text
          

          還有一種更加高級,且能自動處理Cookie的方式,有時候我們不需要關心Cookie值是多少,只是希望每次訪問的時候,程序自動把Cookie的值帶上,像瀏覽器一樣。Requests提供了一個session的概念,在連續訪問網頁,處理登錄跳轉時特別方便,不需要關注具體細節。使用方法示例如下:

          import Requests
          oginUrl = 'http://www.xxxxxxx.com/login'
          s = requests.Session()
          #首先訪問登錄界面,作為游客,服務器會先分配一個cookie
          r = s.get(loginUrl,allow_redirects=True)
          datas={'name':'qiye','passwd':'qiye'}
          #向登錄鏈接發送post請求,驗證成功,游客權限轉為會員權限
          r = s.post(loginUrl, data=datas,allow_redirects= True)
          print r.text
          

          上面的這段程序,其實是正式做Python開發中遇到的問題,如果沒有第一步訪問登錄的頁面,而是直接向登錄鏈接發送Post請求,系統會把你當做非法用戶,因為訪問登錄界面時會分配一個Cookie,需要將這個Cookie在發送Post請求時帶上,這種使用Session函數處理Cookie的方式之后會很常用。

          3.6 重定向與歷史信息

          處理重定向只是需要設置一下allow_redirects字段即可,例如:

          r=requests.get('http://www.baidu.com',allow_redirects=True)

          將allow_redirects設置為True,則是允許重定向;設置為False,則是禁止重定向。如果是允許重定向,可以通過r.history字段查看歷史信息,即訪問成功之前的所有請求跳轉信息。示例如下:

          import requests
          r = requests.get('http://github.com')
          print r.url
          print r.status_code
          print r.history
          

          打印結果如下:

          https://github.com/
          200
          (<Response [301]>,)
          

          上面的示例代碼顯示的效果是訪問GitHub網址時,會將所有的HTTP請求全部重定向為HTTPS。

          3.7 超時設置

          超時選項是通過參數timeout來進行設置的,示例如下:

          requests.get('http://github.com', timeout=2)
          

          3.8 代理設置

          使用代理Proxy,你可以為任意請求方法通過設置proxies參數來配置單個請求:

          import requests
          proxies = {
           "http": "http://0.10.1.10:3128",
           "https": "http://10.10.1.10:1080",
          }
          requests.get("http://example.org", proxies=proxies)
          

          也可以通過環境變量HTTP_PROXY和HTTPS_PROXY?來配置代理,但是在爬蟲開發中不常用。你的代理需要使用HTTP Basic Auth,可以使用http://user:password@host/語法:

          proxies = {
           "http": "http://user:pass@10.10.1.10:3128/",
          }
          

          03 小結

          本文主要講解了網絡爬蟲的結構和應用,以及Python實現HTTP請求的幾種方法。希望大家對本文中的網絡爬蟲工作流程和Requests實現HTTP請求的方式重點吸收消化。

          關于作者:范傳輝,資深網蟲,Python開發者,參與開發了多項網絡應用,在實際開發中積累了豐富的實戰經驗,并善于總結,貢獻了多篇技術文章廣受好評。研究興趣是網絡安全、爬蟲技術、數據分析、驅動開發等技術。

          本文摘編自《Python爬蟲開發與項目實戰》,經出版方授權發布。

          延伸閱讀《Python爬蟲開發與項目實戰》

          推薦語:零基礎學習爬蟲技術,從Python和Web前端基礎開始講起,由淺入深,包含大量案例,實用性強。

          文適合有 Java 基礎知識的人群

          本文作者:HelloGitHub-秦人

          HelloGitHub 推出的《講解開源項目》系列,今天給大家帶來一款開源 Java 版一款網頁元素解析框架——jsoup,通過程序自動獲取網頁數據。

          項目源碼地址:https://github.com/jhy/jsoup

          一、項目介紹

          jsoup 是一款 Java 的 HTML 解析器。可直接解析某個 URL 地址的 HTML 文本內容。它提供了一套很省力的 API,可通過 DOM、CSS 以及類似于 jQuery 選擇器的操作方法來取出和操作數據。

          jsoup 主要功能:

          1. 從一個 URL、文件或字符串中解析 HTML。
          2. 使用 DOM 或 CSS 選擇器來查找、取出數據。
          3. 可操作 HTML 元素、屬性、文本。

          二、使用框架

          2.1 準備工作

          • 掌握 HTML 語法
          • Chrome 瀏覽器調試技巧
          • 掌握開發工具 idea 的基本操作

          2.2 學習源碼

          將項目導入 idea 開發工具,會自動下載 maven 項目需要的依賴。源碼的項目結構如下:

          快速學習源碼是每個程序員必備的技能,我總結了以下幾點:

          1. 閱讀項目 ReadMe 文件,可以快速知道項目是做什么的。
          2. 概覽項目 pom.xml 文件,了解項目引用了哪些依賴。
          3. 查看項目結構、源碼目錄、測試用例目錄,好的項目結構清晰,層次明確。
          4. 運行測試用例,快速體驗項目。

          2.3 下載項目

          git clone https://github.com/jhy/jsoup
          

          2.4 運行項目測試代碼

          通過上面的方法,我們很快可知 example 目錄是測試代碼,那我們直接來運行。注:有些測試代碼需要稍微改造一下才可以運行。

          例如,jsoup 的 Wikipedia 測試代碼:

          public class Wikipedia {
              public static void main(String[] args) throws IOException {
                  Document doc = Jsoup.connect("http://en.wikipedia.org/").get();
                  log(doc.title());
          
                  Elements newsHeadlines = doc.select("#mp-itn b a");
                  for (Element headline : newsHeadlines) {
                      log("%s\n\t%s", headline.attr("title"), headline.absUrl("href"));
                  }
              }
          
              private static void log(String msg, String... vals) {
                  System.out.println(String.format(msg, vals));
              }
          }
          

          說明:上面代碼是獲取頁面(http://en.wikipedia.org/)包含(#mp-itn b a)選擇器的所有元素,并打印這些元素的 title , herf 屬性。維基百科 國內無法訪問,所以上面這段代碼運行會報錯。

          改造后可運行的代碼如下:

          public static void main(String[] args) throws IOException {
              Document doc = Jsoup.connect("https://www.baidu.com/").get();
              Elements newsHeadlines = doc.select("a[href]");
              for (Element headline : newsHeadlines) {
                  System.out.println("href: " +headline.absUrl("href") );
              }
          }
          

          三、工作原理

          Jsoup 的工作原理,首先需要指定一個 URL,框架發送 HTTP 請求,然后獲取響應頁面內容,然后通過各種選擇器獲取頁面數據。整個工作流程如下圖:

          以上面為例:

          3.1 發請求

          Document doc = Jsoup.connect("https://www.baidu.com/").get();
          

          這行代碼就是發送 HTTP 請求,并獲取頁面響應數據。

          3.2 數據篩選

          Elements newsHeadlines = doc.select("a[href]");
          

          定義選擇器,獲取匹配選擇器的數據。

          3.3 數據處理

          for (Element headline : newsHeadlines) {
                  System.out.println("href: " +headline.absUrl("href") );
              }
          

          這里對數據只做了一個簡單的數據打印,當然這些數據可寫入文件或數據的。

          四、實戰

          獲取豆瓣讀書 -> 新書速遞中每本新書的基本信息。包括:書名、書圖片鏈接、作者、內容簡介(詳情頁面)、作者簡介(詳情頁面)、當當網書的價格(詳情頁面),最后將獲取的數據保存到 Excel 文件。

          目標鏈接:https://book.douban.com/latest?icn=index-latestbook-all

          4.1 項目 pom.xml 文件

          項目引入 jsoup、lombok、easyexcel 三個庫。

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
          
              <groupId>org.example</groupId>
              <artifactId>JsoupTest</artifactId>
              <version>1.0-SNAPSHOT</version>
              <properties>
                  <maven.compiler.target>1.8</maven.compiler.target>
                  <maven.compiler.source>1.8</maven.compiler.source>
              </properties>
              <dependencies>
                  <dependency>
                      <groupId>org.jsoup</groupId>
                      <artifactId>jsoup</artifactId>
                      <version>1.13.1</version>
                  </dependency>
                  <dependency>
                      <groupId>org.projectlombok</groupId>
                      <artifactId>lombok</artifactId>
                      <version>1.18.12</version>
                  </dependency>
                  <dependency>
                      <groupId>com.alibaba</groupId>
                      <artifactId>easyexcel</artifactId>
                      <version>2.2.6</version>
                  </dependency>
              </dependencies>
          </project>
          

          4.2 解析頁面數據

          public class BookInfoUtils {
          
              public static List<BookEntity> getBookInfoList(String url) throws IOException {
                  List<BookEntity>  bookEntities=new ArrayList<>();
                  Document doc = Jsoup.connect(url).get();
                  Elements liDiv = doc.select("#content > div > div.article > ul > li");
                  for (Element li : liDiv) {
                      Elements urls = li.select("a[href]");
                      Elements imgUrl = li.select("a > img");
                      Elements bookName = li.select(" div > h2 > a");
                      Elements starsCount = li.select(" div > p.rating > span.font-small.color-lightgray");
                      Elements author = li.select("div > p.color-gray");
                      Elements description = li.select(" div > p.detail");
          
                      String bookDetailUrl = urls.get(0).attr("href");
                      BookDetailInfo detailInfo = getDetailInfo(bookDetailUrl);
                      BookEntity bookEntity = BookEntity.builder()
                              .detailPageUrl(bookDetailUrl)
                              .bookImgUrl(imgUrl.attr("src"))
                              .bookName(bookName.html())
                              .starsCount(starsCount.html())
                              .author(author.text())
                              .bookDetailInfo(detailInfo)
                              .description(description.html())
                              .build();
          //            System.out.println(bookEntity);
                      bookEntities.add(bookEntity);
                  }
                  return bookEntities;
              }
              /**
               *
               * @param detailUrl
               * @return
               * @throws IOException
               */
              public static BookDetailInfo getDetailInfo(String detailUrl)throws IOException{
          
                  Document doc = Jsoup.connect(detailUrl).get();
                  Elements content = doc.select("body");
          
                  Elements price = content.select("#buyinfo-printed > ul.bs.current-version-list > li:nth-child(2) > div.cell.price-btn-wrapper > div.cell.impression_track_mod_buyinfo > div.cell.price-wrapper > a > span");
                  Elements author = content.select("#info > span:nth-child(1) > a");
                  BookDetailInfo bookDetailInfo = BookDetailInfo.builder()
                          .author(author.html())
                          .authorUrl(author.attr("href"))
                          .price(price.html())
                          .build();
                  return bookDetailInfo;
              }
          }
          

          這里的重點是要獲取網頁對應元素的選擇器

          例如:獲取 li.select("div > p.color-gray") 中 div > p.color-gray 是怎么知道的。

          使用 chrome 的小伙伴應該都猜到了。打開 chrome 瀏覽器 Debug 模式,Ctrl + Shift +C 選擇一個元素,然后在 html 右鍵選擇 Copy ->Copy selector,這樣就可以獲取當前元素的選擇器。如下圖:

          4.3 存儲數據到 Excel

          為了數據更好查看,我將通過 jsoup 抓取的數據存儲的 Excel 文件,這里我使用的 easyexcel 快速生成 Excel 文件。

          Excel 表頭信息

          @Data
          @Builder
          public class ColumnData {
          
              @ExcelProperty("書名稱")
              private String bookName;
          
              @ExcelProperty("評分")
              private String starsCount;
          
              @ExcelProperty("作者")
              private String author;
          
              @ExcelProperty("封面圖片")
              private String bookImgUrl;
          
              @ExcelProperty("簡介")
              private String description;
          
              @ExcelProperty("單價")
              private String price;
          }
          

          生成 Excel 文件

          public class EasyExcelUtils {
          
              public static void simpleWrite(List<BookEntity> bookEntityList) {
                  String fileName = "D:\\devEnv\\JsoupTest\\bookList" + System.currentTimeMillis() + ".xlsx";
                  EasyExcel.write(fileName, ColumnData.class).sheet("書本詳情").doWrite(data(bookEntityList));
                  System.out.println("excel文件生成完畢...");
              }
              private static List<ColumnData> data(List<BookEntity> bookEntityList) {
                  List<ColumnData> list = new ArrayList<>();
                  bookEntityList.forEach(b -> {
                      ColumnData data = ColumnData.builder()
                              .bookName(b.getBookName())
                              .starsCount(b.getStarsCount())
                              .author(b.getBookDetailInfo().getAuthor())
                              .bookImgUrl(b.getBookImgUrl())
                              .description(b.getDescription())
                              .price(b.getBookDetailInfo().getPrice())
                              .build();
                      list.add(data);
                  });
                  return list;
              }
          }
          

          4.4 最終展示效果

          最終的效果如下圖:

          以上就是從想法到實踐,我們就在實戰中使用了 jsoup 的基本操作。

          完整代碼地址:https://github.com/hellowHuaairen/JsoupTest

          五、最后

          Java HTML Parser 庫:jsoup,把它當成簡單的爬蟲用起來還是很方便的吧?

          為什么會講爬蟲?大數據,人工智能時代玩的就是數據,數據很重要。作為懂點技術的我們,也需要掌握一種獲取網絡數據的技能。當然也有一些工具 Fiddler、webscraper 等也可以抓取你想要的數據。

          教程至此,你應該也能對 jsoup 有一些感覺了吧。編程是不是也特別有意思呢?參考我上面的實戰案例,有好多網站可以實踐一下啦~歡迎在評論區曬你的實戰。

          言:

          今天為大家帶來的內容是4個詳細步驟講解Python爬取網頁數據操作過程!(含實例代碼)本文具有不錯的參考意義,希望在此能夠幫助到大家!

          提示:由于涉及代碼較多,大部分代碼用圖片的方式呈現出來!

          一、利用webbrowser.open()打開一個網站:

          實例:使用腳本打開一個網頁。

          所有Python程序的第一行都應以#!python開頭,它告訴計算機想讓Python來執行這個程序。(我沒帶這行試了試,也可以,可能這是一種規范吧)

          • 1.從sys.argv讀取命令行參數:打開一個新的文件編輯器窗口,輸入下面的代碼,將其保存為map.py。
          • 2.讀取剪貼板內容:
          • 3.調用webbrowser.open()函數打開外部瀏覽:

          注:不清楚sys.argv用法的,請參考這里;不清楚.join()用法的,請參考這里。sys.argv是字符串的列表,所以將它傳遞給join()方法返回一個字符串。

          好了,現在選中'天安門廣場'這幾個字并復制,然后到桌面雙擊你的程序。當然你也可以在命令行找到你的程序,然后輸入地點。

          二、用requests模塊從Web下載文件:requests模塊不是Python自帶的,通過命令行運行pip install request安裝。沒翻墻是很難安裝成功的,手動安裝可以參考這里。

          requests中查看網上下載的文件內容的方法還有很多,如果以后的博客用的到,會做說明,在此不再一一介紹。在下載文件的過程中,用raise_for_status()方法可以確保下載確實成功,然后再讓程序繼續做其他事情。

          三、將下載的文件保存到本地:

          四、用BeautifulSoup模塊解析HTML:在命令行中用pip install beautifulsoup4安裝它。

          1.bs4.BeautifulSoup()函數可以解析HTML網站鏈接requests.get(),也可以解析本地保存的HTML文件,直接open()一個本地HTML頁面。

          我這里有錯誤提示,所以加了第二個參數。

          2.用select()方法尋找元素:需傳入一個字符串作為CSS“選擇器”來取得Web頁面相應元素,例如:

          1. soup.select('div'):所有名為<div>的元素;
          2. soup.select('#author'):帶有id屬性為author的元素;
          3. soup.select('.notice'):所有使用CSS class屬性名為notice的元素;
          4. soup.select('div span'):所有在<div>元素之內的<span>元素;
          5. soup.select('input[name]'):所有名為<input>并有一個name屬性,其值無所謂的元素;
          6. soup.select('input[type="button"]'):所有名為<input>并有一個type屬性,其值為button的元素。

          想查看更多的解析器,請參看這里。

          3.通過元素的屬性獲取數據:接著上面的代碼寫。

          >>> link[0].get('href') 
          'css/mozMainStyle-min.css?v=20170705
          

          以上就是本文的全部內容啦,同時這些代碼實例也算是對“網絡爬蟲”的一些初探。

          最后多說一句,小編是一名python開發工程師,這里有我自己整理了一套最新的python系統學習教程,包括從基礎的python腳本到web開發、爬蟲、數據分析、數據可視化、機器學習等。想要這些資料的可以關注小編,并在后臺私信小編:“07”即可領取。


          主站蜘蛛池模板: 无码少妇一区二区浪潮av| 亚洲日本中文字幕一区二区三区| 国精无码欧精品亚洲一区| V一区无码内射国产| 亚洲一区二区在线视频| 在线精品视频一区二区| 一区二区高清在线| 国产成人AV一区二区三区无码| 97精品一区二区视频在线观看| 精品一区二区三区在线播放| 精品无人区一区二区三区在线| 一区二区三区波多野结衣| 亚洲国产精品无码第一区二区三区| 相泽亚洲一区中文字幕| 成人h动漫精品一区二区无码| 精品成人乱色一区二区| 无码一区二区三区| 搡老熟女老女人一区二区| 中文字幕在线一区| 国产AV一区二区三区无码野战| 中文字幕一区二区免费| 久久精品中文字幕一区| 精品国产一区二区三区2021| av无码一区二区三区| 国产一区二区三区久久精品| 国产波霸爆乳一区二区| 日韩精品一区二区三区视频| 国产一区二区三区免费观在线 | 日韩人妻不卡一区二区三区| 亚洲无线码一区二区三区| 无码乱人伦一区二区亚洲一| 一区二区三区四区精品视频| 亚洲日韩国产欧美一区二区三区| 免费无码一区二区三区蜜桃大| 波多野结衣一区二区三区88 | 亚洲国产情侣一区二区三区| 亚洲国产成人久久综合一区 | 一区二区三区视频免费观看| 国产一区二区免费| 国产在线观看91精品一区| 亚洲一区二区三区无码中文字幕|