這是《Web Scraping with Python》一書的閱讀筆記亩码。該筆記跳過了一些不必要的描述弹谁,對書的代碼也做了核實(shí)瞬场,也引入了一些我自己對爬蟲腳本實(shí)現(xiàn)的理解买鸽。
第一章 你的第一個(gè)網(wǎng)絡(luò)爬蟲程序
為了幫助理解,作者用了一個(gè)例子贯被。假設(shè)Alice有一個(gè)網(wǎng)絡(luò)服務(wù)器眼五。而Bob想用一個(gè)臺式機(jī)通過瀏覽器訪問Alice的服務(wù)器上運(yùn)行的某個(gè)網(wǎng)站。整個(gè)訪問過程歸納如下:
1. Bob輸入訪問網(wǎng)站的地址后彤灶,Bob的電腦傳輸一段二進(jìn)制的數(shù)據(jù)看幼,這些數(shù)據(jù)包含數(shù)據(jù)頭和數(shù)據(jù)內(nèi)容。數(shù)據(jù)頭包含發(fā)送方的mac地址和目的地的ip地址幌陕,而數(shù)據(jù)內(nèi)容包含了針對Alice網(wǎng)絡(luò)服務(wù)器的請求诵姜,例如,獲得某個(gè)網(wǎng)頁頁面搏熄。
2. Bob的本地網(wǎng)絡(luò)路由器將數(shù)據(jù)打包傳輸?shù)紸lice的ip地址棚唆。
3. Bob的數(shù)據(jù)最后通過物理電纜進(jìn)行傳輸。
4. Alice的服務(wù)器接受到了Bob的數(shù)據(jù)包心例。
5. Alice的服務(wù)器識別存于數(shù)據(jù)頭的端口號宵凌,發(fā)現(xiàn)是80,意味著這是一個(gè)網(wǎng)頁請求契邀,于是調(diào)用網(wǎng)頁服務(wù)器相關(guān)的程序摆寄。
6. 網(wǎng)頁服務(wù)器程序接受到如下信息::
- This is a GET request
- The following file is requested: index.html
7. 網(wǎng)頁服務(wù)器程序載入正確的HTML 文件,并打包通過本地路由發(fā)送給Bob的電腦.
而Python的庫包含了模擬瀏覽器訪問某個(gè)頁面的功能坯门,如下:
from urllib.request import urlopen
html = urlopen("http://pythonscraping.com/pages/page1.html")
print(html.read())
這是一段Python3的程序微饥,請用Python3.X的版本運(yùn)行它。執(zhí)行后古戴,該網(wǎng)頁的HTML內(nèi)容會被打印出來欠橘,其實(shí)就是Chrome瀏覽器右鍵查看網(wǎng)頁源代碼可以看到的網(wǎng)頁內(nèi)容。
urllib 還是 urllib2?
Python2中用urllib2现恼,而Python3中用urllib肃续。urllib是Python的標(biāo)準(zhǔn)庫,用于網(wǎng)頁數(shù)據(jù)請求叉袍,處理Cookies始锚,甚至更改請求者的數(shù)據(jù)頭信息。因?yàn)檎緯拇a都會涉及urllib的使用喳逛,所以有空的時(shí)候可以看看Python的官方文檔:https://docs.python.org/3/library/urllib.html
BeautifulSoup的介紹和安裝
一句話來概括瞧捌,BeautifulSoup將不可能變成了可能,它將HTML的內(nèi)容組織成了Python可以識別的對象格式。因?yàn)锽eautifulSoup不是Python默認(rèn)的庫姐呐,我們要先安裝它殿怜。本書用BeautifulSoup的第四個(gè)版本。下載地址:https://pypi.python.org/pypi/beautifulsoup4曙砂⊥访眨可下載安裝包:beautifulsoup4-4.5.3.tar.gz(md5)。解壓后使用命令:"python.exe setup.py install" 進(jìn)行安裝鸠澈,這種安裝方式在Windows下也可行柱告。當(dāng)然也可以使用pip命令安裝,省去下載安裝包的過程:"pip install beautifulsoup4"款侵,但Windows下末荐,要另外裝pip工具。
BeautifulSoup初體驗(yàn)
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這個(gè)HTML文件新锈,并輸出了h1這個(gè)字段的內(nèi)容:
<h1>An Interesting Title<h1>
上面這個(gè)圖顯示的是HTML的常用結(jié)構(gòu)甲脏。bsObj.h1是一個(gè)快捷的訪問h1數(shù)據(jù)的方法,實(shí)際上類似這樣的訪問也是有效的:bsObj.html.body.h1妹笆,bsObj.body.h1块请,bsObj.html.h1
通過這個(gè)例子,我們應(yīng)該可以體會到BeautifulSoup的方便拳缠。第三章將對BeautifulSoup做更深入的討論墩新,例如:使用正則表達(dá)式提取網(wǎng)頁數(shù)據(jù)。
考慮腳本的穩(wěn)定性
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
考慮到某些情況下會出現(xiàn)頁面無法訪問的問題窟坐,建議加上以上出錯(cuò)判斷的代碼海渊。如果嘗試訪問的BeautifulSoup標(biāo)簽不存在,BeautifulSoup會返回None對象哲鸳。那么問題來了臣疑,訪問None對象會拋出AttributeError異常。以下是一個(gè)魯棒的獲取數(shù)據(jù)的腳本:
第二章 HTML解析的進(jìn)階
第一章介紹的BeautifulSoup可以很方便地提取需要的帶標(biāo)簽的數(shù)據(jù)徙菠,但隨著標(biāo)記深度的遞增讯沈,簡單地使用對象數(shù)據(jù)不利于表達(dá)和調(diào)試,例如以下代碼就很難理解:
bsObj.findAll("table")[4].findAll("tr")[2].find("td").findAll("div")[1].find("a")
這段代碼不僅不美觀婿奔,還不夠魯棒缺狠。當(dāng)爬取的網(wǎng)站做了小幅度的更改后,這段代碼就無效了萍摊。有沒有更好的方法呢挤茄?
BeautifulSoup的另一個(gè)功能
通常,一個(gè)HTML頁面都包含有CSS樣式冰木,例如
<span class="green"></span>
<span class="red"></span>
BeautifulSoup可以通過制定class的值驮樊,過濾一些不需要的內(nèi)容。例如
nameList=bsObj.findAll("span", {"class":"green"})
for?name?in?nameList:
print(name.get_text())
這句代碼可以獲得class為green的span內(nèi)容片酝。其中函數(shù)get_text()可以獲得標(biāo)簽的內(nèi)容囚衔。
findAll和find的函數(shù)定義如下
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"})
這些代碼可以列出所有有關(guān)的標(biāo)簽內(nèi)容。recursive設(shè)置為true的話就執(zhí)行遞歸查詢雕沿。默認(rèn)情況該值為true练湿。
nameList=bsObj.findAll(text="the prince")
print(len(nameList))
以上的代碼可以統(tǒng)計(jì)"the prince"字符串出現(xiàn)的次數(shù),輸出為7审轮。(真是太強(qiáng)大了)
allText=bsObj.findAll(id="text") #和bsObj.findAll("", {"id":"text"})是等價(jià)的
print(allText[0].get_text())
這段代碼可以根據(jù)attribute來選擇內(nèi)容肥哎,代碼的輸出是id是text的div包含的所有文本內(nèi)容。因?yàn)閏lass是Python的關(guān)鍵字疾渣,bsObj.findAll(class="green")是不允許的篡诽,可以用以下代碼替換:
bsObj.findAll(class_="green")
bsObj.findAll("", {"class":"green"}
正則表達(dá)式
正則表達(dá)式在很多人眼里都是個(gè)高大上的工具,什么都不多說了榴捡,先來一個(gè)例子杈女。
aa*bbbbb(cc)*(d | )
aa*
這里的*指代任何東西
bbbbb
代表5個(gè)b,沒有其它的意義
(cc)*
代表任意個(gè)重復(fù)的c吊圾,也可以是0個(gè)
(d | )
|代表或的意思达椰,這句代表以d和空格結(jié)尾,或者僅僅以空格結(jié)尾
一些通用的規(guī)則如下项乒,例如E-mail的命名規(guī)則:
E-mail能包含的字符為:大小寫字母啰劲、數(shù)字、點(diǎn)檀何、加號或者下劃線蝇裤,并且每個(gè)E-mail都要包含@符號。這個(gè)規(guī)則用正則表達(dá)式可以這樣寫:[A-Za-z0-9\._+]+
正則表達(dá)式非常得智能频鉴,它會知道中括號中的內(nèi)容是指從A到Z的字符都可以栓辜,\.代表一個(gè)點(diǎn)(period),然后最后的+號以為著這些字符可以出現(xiàn)任意多次砚殿,但至少要出現(xiàn)一次啃憎。
再看一個(gè)更復(fù)雜的:[A-Za-z0-9\._+]+@[A-Za-z]+\.(com|org|edu|net),這種正則表達(dá)式就可以匹配任意以com|org|edu|net結(jié)尾的郵箱地址似炎。
接下來詳細(xì)介紹12個(gè)在Python中常用的正則表達(dá)式
.代表任意一個(gè)字符辛萍,如果是要查找點(diǎn)的話,就用轉(zhuǎn)義字符\.
+的作用是將前面一個(gè)字符或一個(gè)子表達(dá)式重復(fù)一遍或者多遍羡藐。
*跟在其他符號后面表達(dá)可以匹配到它0次或多次贩毕,例如https*就可以找出http://和https://兩種。
這里舉一個(gè)例子介紹正則表達(dá)式在實(shí)際數(shù)據(jù)抓取仆嗦,原書的代碼編譯不過辉阶,我做了一些修改。
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"])
訪問屬性
通過這樣的方式就可以訪問某個(gè)屬性:myImgTag.attrs['src']
Lambda表達(dá)式
作者針對Lambda的描述是某個(gè)函數(shù)的參數(shù)是另一個(gè)函數(shù)。我的理解是谆甜,某個(gè)查找的條件是某個(gè)判斷函數(shù)的返回值垃僚。例如:
bsObj.findAll(lambda tag: len(tag.attrs) == 2)
這句代碼可以找出tag有兩個(gè)的條目。返回的是len(tag.attrs) == 2為True的所有條目规辱。
感覺這兩章的內(nèi)容足夠應(yīng)付基本的爬蟲應(yīng)用了谆棺,以后有額外的需求,再解讀其他幾章罕袋。^__^