用Python實現(xiàn)網(wǎng)絡爬蟲

Web Scraping with Python

這是《Web Scraping with Python》一書的閱讀筆記。該筆記跳過了一些不必要的描述,對書的代碼也做了核實,也引入了一些我自己對爬蟲腳本實現(xiàn)的理解。

第一章 你的第一個網(wǎng)絡爬蟲程序

為了幫助理解,作者用了一個例子。假設Alice有一個網(wǎng)絡服務器。而Bob想用一個臺式機通過瀏覽器訪問Alice的服務器上運行的某個網(wǎng)站。整個訪問過程歸納如下:

1. Bob輸入訪問網(wǎng)站的地址后,Bob的電腦傳輸一段二進制的數(shù)據(jù),這些數(shù)據(jù)包含數(shù)據(jù)頭數(shù)據(jù)內容。數(shù)據(jù)頭包含發(fā)送方的mac地址和目的地的ip地址,而數(shù)據(jù)內容包含了針對Alice網(wǎng)絡服務器的請求,例如,獲得某個網(wǎng)頁頁面。

2. Bob的本地網(wǎng)絡路由器將數(shù)據(jù)打包傳輸?shù)紸lice的ip地址。

3. Bob的數(shù)據(jù)最后通過物理電纜進行傳輸。

4. Alice的服務器接受到了Bob的數(shù)據(jù)包。

5. Alice的服務器識別存于數(shù)據(jù)頭的端口號,發(fā)現(xiàn)是80,意味著這是一個網(wǎng)頁請求,于是調用網(wǎng)頁服務器相關的程序。

6. 網(wǎng)頁服務器程序接受到如下信息::

- This is a GET request

- The following file is requested: index.html

7. 網(wǎng)頁服務器程序載入正確的HTML 文件,并打包通過本地路由發(fā)送給Bob的電腦.

而Python的庫包含了模擬瀏覽器訪問某個頁面的功能,如下:

from urllib.request import urlopen

html = urlopen("http://pythonscraping.com/pages/page1.html")

print(html.read())

這是一段Python3的程序,請用Python3.X的版本運行它。執(zhí)行后,該網(wǎng)頁的HTML內容會被打印出來,其實就是Chrome瀏覽器右鍵查看網(wǎng)頁源代碼可以看到的網(wǎng)頁內容。

urllib 還是 urllib2?

Python2中用urllib2,而Python3中用urllib。urllib是Python的標準庫,用于網(wǎng)頁數(shù)據(jù)請求,處理Cookies,甚至更改請求者的數(shù)據(jù)頭信息。因為正本書的代碼都會涉及urllib的使用,所以有空的時候可以看看Python的官方文檔:https://docs.python.org/3/library/urllib.html

BeautifulSoup的介紹和安裝

一句話來概括,BeautifulSoup將不可能變成了可能,它將HTML的內容組織成了Python可以識別的對象格式。因為BeautifulSoup不是Python默認的庫,我們要先安裝它。本書用BeautifulSoup的第四個版本。下載地址:https://pypi.python.org/pypi/beautifulsoup4??上螺d安裝包: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文件的結構

上面這個圖顯示的是HTML的常用結構。bsObj.h1是一個快捷的訪問h1數(shù)據(jù)的方法,實際上類似這樣的訪問也是有效的:bsObj.html.body.h1,bsObj.body.h1,bsObj.html.h1

通過這個例子,我們應該可以體會到BeautifulSoup的方便。第三章將對BeautifulSoup做更深入的討論,例如:使用正則表達式提取網(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)頁面無法訪問的問題,建議加上以上出錯判斷的代碼。如果嘗試訪問的BeautifulSoup標簽不存在,BeautifulSoup會返回None對象。那么問題來了,訪問None對象會拋出AttributeError異常。以下是一個魯棒的獲取數(shù)據(jù)的腳本:

容錯的腳本例子

第二章 HTML解析的進階

第一章介紹的BeautifulSoup可以很方便地提取需要的帶標簽的數(shù)據(jù),但隨著標記深度的遞增,簡單地使用對象數(shù)據(jù)不利于表達和調試,例如以下代碼就很難理解:

bsObj.findAll("table")[4].findAll("tr")[2].find("td").findAll("div")[1].find("a")

這段代碼不僅不美觀,還不夠魯棒。當爬取的網(wǎng)站做了小幅度的更改后,這段代碼就無效了。有沒有更好的方法呢?

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內容。其中函數(shù)get_text()可以獲得標簽的內容。

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"})

這些代碼可以列出所有有關的標簽內容。recursive設置為true的話就執(zhí)行遞歸查詢。默認情況該值為true。

nameList=bsObj.findAll(text="the prince")

print(len(nameList))

以上的代碼可以統(tǒng)計"the prince"字符串出現(xiàn)的次數(shù),輸出為7。(真是太強大了)

allText=bsObj.findAll(id="text") #和bsObj.findAll("", {"id":"text"})是等價的

print(allText[0].get_text())

這段代碼可以根據(jù)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和空格結尾,或者僅僅以空格結尾

一些通用的規(guī)則如下,例如E-mail的命名規(guī)則:

E-mail能包含的字符為:大小寫字母、數(shù)字、點、加號或者下劃線,并且每個E-mail都要包含@符號。這個規(guī)則用正則表達式可以這樣寫:[A-Za-z0-9\._+]+

正則表達式非常得智能,它會知道中括號中的內容是指從A到Z的字符都可以,\.代表一個點(period),然后最后的+號以為著這些字符可以出現(xiàn)任意多次,但至少要出現(xiàn)一次。

再看一個更復雜的:[A-Za-z0-9\._+]+@[A-Za-z]+\.(com|org|edu|net),這種正則表達式就可以匹配任意以com|org|edu|net結尾的郵箱地址。

接下來詳細介紹12個在Python中常用的正則表達式

.代表任意一個字符,如果是要查找點的話,就用轉義字符\.

+的作用是將前面一個字符或一個子表達式重復一遍或者多遍。

*跟在其他符號后面表達可以匹配到它0次或多次,例如https*就可以找出http://和https://兩種。

這里舉一個例子介紹正則表達式在實際數(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"])


訪問屬性

通過這樣的方式就可以訪問某個屬性:myImgTag.attrs['src']

Lambda表達式

作者針對Lambda的描述是某個函數(shù)的參數(shù)是另一個函數(shù)。我的理解是,某個查找的條件是某個判斷函數(shù)的返回值。例如:

bsObj.findAll(lambda tag: len(tag.attrs) == 2)

這句代碼可以找出tag有兩個的條目。返回的是len(tag.attrs) == 2為True的所有條目。

感覺這兩章的內容足夠應付基本的爬蟲應用了,以后有額外的需求,再解讀其他幾章。^__^

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容