這是《Web Scraping with Python》一書的閱讀筆記。該筆記跳過了一些不必要的描述,對書的代碼也做了核實,也引入了一些我自己對爬蟲腳本實現的理解。
第一章 你的第一個網絡爬蟲程序
為了幫助理解,作者用了一個例子。假設Alice有一個網絡服務器。而Bob想用一個臺式機通過瀏覽器訪問Alice的服務器上運行的某個網站。整個訪問過程歸納如下:
1. Bob輸入訪問網站的地址后,Bob的電腦傳輸一段二進制的數據,這些數據包含數據頭和數據內容。數據頭包含發送方的mac地址和目的地的ip地址,而數據內容包含了針對Alice網絡服務器的請求,例如,獲得某個網頁頁面。
2. Bob的本地網絡路由器將數據打包傳輸到Alice的ip地址。
3. Bob的數據最后通過物理電纜進行傳輸。
4. Alice的服務器接受到了Bob的數據包。
5. Alice的服務器識別存于數據頭的端口號,發現是80,意味著這是一個網頁請求,于是調用網頁服務器相關的程序。
6. 網頁服務器程序接受到如下信息::
- This is a GET request
- The following file is requested: index.html
7. 網頁服務器程序載入正確的HTML 文件,并打包通過本地路由發送給Bob的電腦.
而Python的庫包含了模擬瀏覽器訪問某個頁面的功能,如下:
from urllib.request import urlopen
html = urlopen("http://pythonscraping.com/pages/page1.html")
print(html.read())
這是一段Python3的程序,請用Python3.X的版本運行它。執行后,該網頁的HTML內容會被打印出來,其實就是Chrome瀏覽器右鍵查看網頁源代碼可以看到的網頁內容。
urllib 還是 urllib2?
Python2中用urllib2,而Python3中用urllib。urllib是Python的標準庫,用于網頁數據請求,處理Cookies,甚至更改請求者的數據頭信息。因為正本書的代碼都會涉及urllib的使用,所以有空的時候可以看看Python的官方文檔:https://docs.python.org/3/library/urllib.html
BeautifulSoup的介紹和安裝
一句話來概括,BeautifulSoup將不可能變成了可能,它將HTML的內容組織成了Python可以識別的對象格式。因為BeautifulSoup不是Python默認的庫,我們要先安裝它。本書用BeautifulSoup的第四個版本。下載地址:https://pypi.python.org/pypi/beautifulsoup4。可下載安裝包:beautifulsoup4-4.5.3.tar.gz(md5)。解壓后使用命令:"python.exe setup.py install" 進行安裝,這種安裝方式在Windows下也可行。當然也可以使用pip命令安裝,省去下載安裝包的過程:"pip install beautifulsoup4",但Windows下,要另外裝pip工具。
BeautifulSoup初體驗
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/exercises/exercise1.html")
bsObj = BeautifulSoup(html.read(), "html.parser");
print(bsObj.h1)
這段代碼解析了exercise1.html這個HTML文件,并輸出了h1這個字段的內容:
<h1>An Interesting Title<h1>
上面這個圖顯示的是HTML的常用結構。bsObj.h1是一個快捷的訪問h1數據的方法,實際上類似這樣的訪問也是有效的:bsObj.html.body.h1,bsObj.body.h1,bsObj.html.h1
通過這個例子,我們應該可以體會到BeautifulSoup的方便。第三章將對BeautifulSoup做更深入的討論,例如:使用正則表達式提取網頁數據。
考慮腳本的穩定性
try:
? ? ? ? html = urlopen("http://www.pythonscraping.com/exercises/exercise1.html")
except HTTPError as e:
? ? ? ? print(e)
? ? ? ? #return null, break, or do some other "Plan B"
else:
? ? ? ? #program continues. Note: If you return or break in the
? ? ? ?#exception catch, you do not need to use the "else" statement
考慮到某些情況下會出現頁面無法訪問的問題,建議加上以上出錯判斷的代碼。如果嘗試訪問的BeautifulSoup標簽不存在,BeautifulSoup會返回None對象。那么問題來了,訪問None對象會拋出AttributeError異常。以下是一個魯棒的獲取數據的腳本:
第二章 HTML解析的進階
第一章介紹的BeautifulSoup可以很方便地提取需要的帶標簽的數據,但隨著標記深度的遞增,簡單地使用對象數據不利于表達和調試,例如以下代碼就很難理解:
bsObj.findAll("table")[4].findAll("tr")[2].find("td").findAll("div")[1].find("a")
這段代碼不僅不美觀,還不夠魯棒。當爬取的網站做了小幅度的更改后,這段代碼就無效了。有沒有更好的方法呢?
BeautifulSoup的另一個功能
通常,一個HTML頁面都包含有CSS樣式,例如
<span class="green"></span>
<span class="red"></span>
BeautifulSoup可以通過制定class的值,過濾一些不需要的內容。例如
nameList=bsObj.findAll("span", {"class":"green"})
for?name?in?nameList:
print(name.get_text())
這句代碼可以獲得class為green的span內容。其中函數get_text()可以獲得標簽的內容。
findAll和find的函數定義如下
findAll(tag,attributes,recursive,text,limit,keywords)
find(tag,attributes,recursive,text,keywords)
findAll還有很多有用的寫法
.findAll({"h1","h2","h3","h4","h5","h6"})
.findAll("span", {"class":"green","class":"red"})
這些代碼可以列出所有有關的標簽內容。recursive設置為true的話就執行遞歸查詢。默認情況該值為true。
nameList=bsObj.findAll(text="the prince")
print(len(nameList))
以上的代碼可以統計"the prince"字符串出現的次數,輸出為7。(真是太強大了)
allText=bsObj.findAll(id="text") #和bsObj.findAll("", {"id":"text"})是等價的
print(allText[0].get_text())
這段代碼可以根據attribute來選擇內容,代碼的輸出是id是text的div包含的所有文本內容。因為class是Python的關鍵字,bsObj.findAll(class="green")是不允許的,可以用以下代碼替換:
bsObj.findAll(class_="green")
bsObj.findAll("", {"class":"green"}
正則表達式
正則表達式在很多人眼里都是個高大上的工具,什么都不多說了,先來一個例子。
aa*bbbbb(cc)*(d | )
aa*
這里的*指代任何東西
bbbbb
代表5個b,沒有其它的意義
(cc)*
代表任意個重復的c,也可以是0個
(d | )
|代表或的意思,這句代表以d和空格結尾,或者僅僅以空格結尾
一些通用的規則如下,例如E-mail的命名規則:
E-mail能包含的字符為:大小寫字母、數字、點、加號或者下劃線,并且每個E-mail都要包含@符號。這個規則用正則表達式可以這樣寫:[A-Za-z0-9\._+]+
正則表達式非常得智能,它會知道中括號中的內容是指從A到Z的字符都可以,\.代表一個點(period),然后最后的+號以為著這些字符可以出現任意多次,但至少要出現一次。
再看一個更復雜的:[A-Za-z0-9\._+]+@[A-Za-z]+\.(com|org|edu|net),這種正則表達式就可以匹配任意以com|org|edu|net結尾的郵箱地址。
接下來詳細介紹12個在Python中常用的正則表達式
.代表任意一個字符,如果是要查找點的話,就用轉義字符\.
+的作用是將前面一個字符或一個子表達式重復一遍或者多遍。
*跟在其他符號后面表達可以匹配到它0次或多次,例如https*就可以找出http://和https://兩種。
這里舉一個例子介紹正則表達式在實際數據抓取,原書的代碼編譯不過,我做了一些修改。
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bsObj = BeautifulSoup(html.read(), "html.parser")
images = bsObj.findAll("img", {"src":re.compile("\.\.\/img\/gifts\/img.*\.jpg")})
for image in images:
print(image["src"])
訪問屬性
通過這樣的方式就可以訪問某個屬性:myImgTag.attrs['src']
Lambda表達式
作者針對Lambda的描述是某個函數的參數是另一個函數。我的理解是,某個查找的條件是某個判斷函數的返回值。例如:
bsObj.findAll(lambda tag: len(tag.attrs) == 2)
這句代碼可以找出tag有兩個的條目。返回的是len(tag.attrs) == 2為True的所有條目。
感覺這兩章的內容足夠應付基本的爬蟲應用了,以后有額外的需求,再解讀其他幾章。^__^