爬蟲學習(01)
爬蟲第一步:獲取頁面
一、資訊在網路連線層的傳遞
(Bob 從Alice那裡獲得資訊)
- Bob的電腦傳送一個位元組流,由資訊組成,包含header和body。header包含一個本地路由器MAC地址的直接目的地,和Alice的IP地址的最終目的地。body包含他對接受Alice伺服器應用程式的請求。
- Bob的本地路由器接收所有這些1和0,並將它們解釋為一個包,來自Bob自己的MAC地址,目的地是Alice的IP地址。他的路由器把自己的IP地址作為“來自”IP地址印在包上,然後通過網際網路傳送出去。
- Bob的包穿過幾箇中間伺服器,這些伺服器將他的包指向Alice伺服器上正確的物理/有線路徑。
- Alice的伺服器在她的IP地址接收資料包。
- Alice的伺服器讀取報頭中的包埠目的地,並將其傳遞給適當的應用程式——web伺服器應用程式。(對於web應用程式,包埠目的地幾乎總是埠80;這可以看作是包資料的公寓號,而IP地址就像街道地址一樣。)
- eb伺服器應用程式從伺服器處理器接收資料流。這些資料說明了如下內容:
- 這是一個GET請求。
- 請求以下檔案:index.html。
- web伺服器定位正確的HTML檔案,將其打包成一個新的包傳送給Bob,並將其傳送到本地路由器,以便通過相同的過程將其傳輸回Bob的機器。
二、python 虛擬環境
如果您打算處理多個Python專案,或者您需要一種方法來輕鬆地將專案與所有關聯的庫捆綁在一起,或者您擔心已安裝的庫之間可能存在衝突,那麼您可以安裝一個Python虛擬環境來保持所有內容分離並易於管理。
$ virtualenv scrapingEnv
建立一個環境scrapingEnv,產生了一個單獨的資料夾,包含一套獨立的python執行環境,安裝的任何庫或執行的指令碼都將只在該虛擬環境下執行。執行虛擬環境:
$ cd scrapingEnv/ $ source bin/activate (scrapingEnv)ryan$ pip install beautifulsoup4 (scrapingEnv)ryan$ python > from bs4 import BeautifulSoup > (scrapingEnv)ryan$ deactivate ryan$ python > from bs4 import BeautifulSoup Traceback (most recent call last): File "<stdin>", line 1, in <module>
三、urllib.request.urlopen 從一個網路地址獲取html檔案
from urllib.request import urlopen html = urlopen('http://pythonscraping.com/pages/page1.html') print(html.read())
urllib是一個標準的Python庫(意味著您不需要安裝任何額外的程式來執行這個示例),它包含用於跨web請求資料、處理cookie、甚至更改元資料(如標題和使用者代理)的函式。
urlopen用於跨網路開啟遠端物件並讀取它。它是一個相當通用的函式(它可以輕鬆地讀取HTML檔案、影象檔案或任何其他檔案流)
四、BeautifulSoup 處理html頁面
BeautifulSoup 通過修復糟糕的HTML和向我們提供表示XML結構的易於遍歷的Python物件來幫助格式化和組織混亂的web。
安裝:
$sudoapt-get install python-bs4 or: $pip install beautifulsoup4
執行:
from urllib.request import urlopen from bs4 import BeautifulSoup html = urlopen('http://www.pythonscraping.com/pages/page1.html') bs = BeautifulSoup(html.read(), 'html.parser')# 無需.read() print(bs.h1) ----------------------Output-------------------------- <h1>An Interesting Title</h1>
bs = BeautifulSoup(html.read(), 'html.parser')將html檔案進行解析,成為一個BeautifulSoup 物件,具有良好的結構層次:
html → <html><head>...</head><body>...</body></html> head → <head><title>A Useful Page<title></head> title → <title>A Useful Page</title> body → <body><h1>An Int...</h1><div>Loremip...</div></body> h1 → <h1>An Interesting Title</h1> div → <div>Lorem Ipsum dolor...</div>
然後可以訪問不同層次結構:比如獲得h1 tag:
bs.h1
;bs.html.h1
;bs.body.h1
;bs.html.body.h1
都可以
其中引數'html.parser'表示將其以html良好結構進行解析,無需安裝直接使用。其他還有'lxml'和'html5lib'兩者都是解析方式,不過都需要先下載安裝pip install lxml
及pip install html5lib
.
lxml格式可以處理更糟糕的html程式碼,可以進行修復,而且比html.parser更快。缺點在於需要安裝,依賴於第三方C庫,可能出現易使用型和移植性問題。html5lib是一個非常寬容的解析器,它甚至可以主動更正損壞的HTML。它還依賴於外部依賴,並且比lxml和html.parser都要慢。
五、可靠地連線並處理異常
獲取頁面出錯
- 伺服器上找不到頁面HTMLError
- 找不到伺服器URLError
from urllib.request import urlopen from urllib.error import HTTPError from urllib.error import URLError try: html_1 = urlopen('http://www.pythonscraping.com/pages/pagenofound.html') html_2 = urlopen('https://pythonscrapingthisurldoesnotexist.com') except HTTPError as e: print(e) except URLError as e: print('The server could not be found!') else: print('It Worked!')
tag獲取出錯
如果從伺服器成功檢索到頁面,仍然存在頁面上的內容與預期不完全一致的問題。每次在BeautifulSoup物件中訪問標記時,明智的做法是新增一個檢查來確保標記確實存在。如果您試圖訪問一個不存在的標記,BeautifulSoup將返回一個None物件。如果要繼續訪問深層tag,則出現None物件的AttributeError錯誤。
try: badContent = bs.nonExistingTag.anotherTag except AttributeError as e: print('Tag was not found') else: if badContent == None: print ('Tag was not found') else: print(badContent)
爬蟲第二步:HTML 解析和資訊提取
注意:一般不要去通過使用多行語句層層深入來找到所要的資訊。可能因為頁面的變化而出錯
bs.find_all('table')[4].find_all('tr')[2].find('td').find_all('div')[1].find('a')
接下來的beautifulsoup物件的資訊提取都建立在獲取頁面內容之後:
from urllib.request import urlopen from bs4 import BeautifulSoup html = urlopen('http://www.pythonscraping.com/pages/page1.html') bs = BeautifulSoup(html.read(), 'html.parser')
一、通過屬性匹配獲得特定tag
nameList = bs.findAll('span', {'class':'green'}) for name in nameList: print(name.get_text())
- bs.find_all(tagName, tagAttributes)來獲取頁面上所有標記的列表,而不僅僅是第一個標記。
- tagName.get_text(),以便將內容從標記中分離出來。
find() 和 find_all()
BeautifulSoup的find()和find_all()是您可能最常用的兩個函式。有了它們,您可以很容易地過濾HTML頁面,根據它們的各種屬性找到所需標記的列表,或者單個標記。
find_all(tag, attributes, recursive, text, limit, keywords) find(tag, attributes, recursive, text, keywords)
tag: 可以傳遞標記的字串名稱,甚至Python字串標記名稱列表
.find_all(['h1','h2','h3','h4','h5','h6'])
attributes:獲取屬性的Python字典,並匹配包含這些屬性之一的標記
.find_all('span', {'class':{'green', 'red'}})
recursive:布林值。您希望文件深入到什麼程度?如果recursive被設定為True, find_all函式將查詢與引數匹配的標記的子標記和子標記的子標記。如果它是假的,它將只檢視文件中的頂級標記。預設情況下,find_all遞迴工作(recursive設定為True)
text:根據標記的文字內容進行匹配,而不是根據標記本身的屬性進行匹配
nameList = bs.find_all(text='the prince') print(len(nameList))
keywords:允許您選擇包含特定屬性或屬性集的標記
title = bs.find_all(id='title', class_='text')
但keywords可以用attributes引數替代:
bs.find_all('', {'id':'text','class':'text'})
二、通過html組織結構獲取tag
children and descendants 直接子代和所有後輩
在BeautifulSoup庫和許多其他庫中,在子庫和後代庫之間有一個區別:就像在人類族譜中一樣,子庫總是恰好比父庫低一個標記,而後代庫可以在父庫下面的樹中的任何級別。
一般來說,BeautifulSoup函式總是處理選中的當前標記的後代。例如,bs.body.h1
選擇第一個h1標記,它是body標記的後代。它不會找到位於主體外部的標記。類似地,bs.div.find_all('img')
將在文件中找到第一個div標記,然後檢索該div標記的後代的所有img標記的列表.
如果您希望只找到子代,可以使用.children標記
for child in bs.find('table',{'id':'giftList'}).children: print(child)
所有後輩則使用.descendants
for all_child in bs.find('table',{'id':'giftList'}).descendants: print(all_child)
siblings同層次兄弟姐妹
for sibling in bs.find('table',{'id':'giftList'}).tr.next_siblings: print(sibling)
當你得到一個物件的兄弟siblings物件時,該物件本身將不會包含在列表中。正如函式名所暗示的,它只調用下面同層次的所有兄弟姐妹。例如,如果在列表中間選擇一行,並對其呼叫next_siblings,則只返回後續的兄弟姐妹。因此,通過選擇標題行並呼叫next_siblings,可以選擇表中的所有行.
作為對next_siblings的補充,previous_siblings函式通常很有用,如果您希望得到的同胞標記列表的末尾有一個易於選擇的標記。previous_siblings返回同層次的所有兄弟姐妹
也有next_sibling和previous_sibling函式,它們執行的函式幾乎與next_sibling和previous_sibling相同,只不過它們返回的是單個標記,而不是它們的列表
parent 父代標記
bs.find('img',{'src':'../img/gifts/img1.jpg'}).parent.previous_sibling.get_text()
三、正則表示式
import re images = bs.find_all('img', {'src':re.compile('\.\.\/img\/gifts/img.*\.jpg')}) for image in images: print(image['src'])
只打印以../img/gifts/img開頭並且以.jpg結尾的相對影象路徑。 輸出結果如下:
../img/gifts/img1.jpg ../img/gifts/img2.jpg ../img/gifts/img3.jpg ../img/gifts/img4.jpg ../img/gifts/img6.jpg
四、獲取屬性
使用標記物件,Python屬性列表可以通過呼叫以下命令自動訪問:
myTag.attrs
這實際上返回了Python dictionary物件,可以使用以下行找到影象的源位置:
myTag.attrs['src']
五、Lambda 表示式
BeautifulSoup允許您將某些型別的函式作為引數傳遞到find_all函式中。
唯一的限制是這些函式必須以標記物件作為引數並返回布林值。beautifulsoup遇到的每個標記物件都在這個函式中求值,值為True的標記將被返回,其餘的將被丟棄。
bs.find_all(lambda tag: len(tag.attrs) == 2)
返回所有具有兩個屬性的標記
Lambda函式非常有用,您甚至可以使用它們來替換現有的BeautifulSoup函式
bs.find_all(lambda tag: tag.get_text() =='Or maybe he\'s only resting?')
等同於:
bs.find_all('', text='Or maybe he\'s only resting?')