1. 程式人生 > >來,讓我們寫一個網路爬蟲,下載頁面上所有的照片吧!

來,讓我們寫一個網路爬蟲,下載頁面上所有的照片吧!

什麼是網路爬蟲?

網路爬蟲是一種非常有意思的程式。偌大的Internet,就像是一隻蜘蛛織成的大網:一個個超級連結就是蛛絲,將無數頁面連線起來,而網路爬蟲,則會沿著一根根蛛絲,爬遍每一個節點……

網路爬蟲能幹嘛?

蜘蛛在網上爬來爬去,當然不是為了健身。它會在網上尋覓獵物,捕捉它們,並拖回自己的窩裡。

舉一個例子:某天某日的清晨,老闆突然讓你將雪球網上所有的A股行情資訊全部儲存到一個Excel檔案裡,以便他瀏覽。你點開了網址雪球(A股行情),驚喜地發現有三十四頁,一條一條複製,顯然又累又笨,但是老闆要看,你又不得不從,惆悵啊……

很多時候,我們會需要做這樣的工作:將某一型別的文件全部下載儲存下來,然後進行分析。一個一個點選滑鼠,顯然不現實。那麼,就做一個網路爬蟲吧,自動化地把需要的東西儲存下來。

網路爬蟲是怎麼幹的?

一個簡單的網路爬蟲的邏輯並不複雜,就是模仿人類瀏覽網頁的動作:輸入網址,進入頁面,瀏覽網頁,點選連結,進入下一頁面……周而復始。

當然,嘴上說是這樣,還得要將步驟細化一下。所以先來看看瀏覽網頁的時候,我們做了些什麼。

當我們瀏覽網頁的時候,瀏覽器在做些什麼?

蒂姆·伯納斯-李在發明網際網路的時候,為了解決頁面之間相互連線的問題,讓網路瀏覽更加方便,發明了超文字標記語言HTML。它是一個文字文件,但是文件中會有一些特殊的標記——標籤,它可以標記出哪些位置的資訊是標題,哪些位置的資訊是文字,哪些地方是影象,哪些地方是超級連結。它就長這樣。
<html>
<body>

<h1>My First Heading</h1>

<p>My first paragraph.</p>

</body>
</html>
尖括號中間的就是標籤,裡面的字就是標籤的名字,帶斜槓的表示標記結束了。比如,<p>的意思是段落, </p>的意思就是這一段結束了。 瀏覽器可以根據標籤,將網頁依照程式碼的意思表現出來,用點兒術語的話,就是瀏覽器是一個HTML的直譯器。當我們瀏覽網頁的時候,瀏覽器會先將網頁的HTML檔案,以及所需要的其它元素都下載到你的電腦裡,然後根據HTML檔案的內容,將網頁展現在你的面前。 所以網路爬蟲也需要幹這件事情:下載HTML檔案,然後分析它,根據分析的結果將需要的文字或者影象什麼的下載下來,找到超級連結,繼續……

讓我們寫一個網路爬蟲,下載頁面上所有的照片吧!

知乎上有很多提問,下面會有很多照片,比如這個
怎麼用手機拍出精彩的照片?
,圖片都挺好看的,我想全部下載下來,怎麼辦呢?開啟頁面以後,右鍵點選頁面,選擇【檢視原始檔】,就可以看到HTML檔案的內容了。簡單分析一下,會發現照片其實都在這樣的標籤下面:
<img src="https://pic3.zhimg.com/efb71614cacf505f92bd59509919a2d2_b.jpg" data-rawwidth="720" data-rawheight="720" class="origin_image zh-lightbox-thumb" width="720" data-original="https://pic3.zhimg.com/efb71614cacf505f92bd59509919a2d2_r.jpg">
所以,現在就可以制定出我們的策略了:開啟頁面,分析它的HTML原始檔,找出所有img標籤,將裡面的圖片都儲存下來,over!

用Python實現你的第一個網路爬蟲

Python語言語法簡單而靈活,實用的庫(別人寫好,可以直接實用的程式碼)非常多,很適合寫這種小爬蟲。所以我們用Python來寫一個網路爬蟲。可以點選這裡下載Python環境的安裝包,一直下一步就可以安裝好了。具體學習Python可以看這本書,或者這本書。這裡我們的程式碼並不複雜,需要的知識點不多,我會一點一點講解的。當然你可能需要一點點程式設計的知識。 在一個文字編輯器裡面輸入以下程式碼:
#-*-coding:utf-8-*-
import urllib

url = "https://www.zhihu.com/question/20922273"
page = urllib.urlopen(url)
print page.read()
找一個資料夾,儲存為example1.py。然後點選右鍵選擇【EDIT with IDLE】。
開啟以後按F5鍵。應該會出現這樣的視窗。
是不是打印出了原來頁面的HTML檔案的內容? 下面來解釋一下這一段程式碼的意思。 #-*-coding:utf-8-*- 的意思是這一段Python程式的編碼格式是UTF-8。理解這一點需要一些編碼的知識,這裡暫時不用管它。一般每一個Python程式第一句話都是這個。
import urllib 的意思是匯入urllib庫,python自帶的一個網路庫,我們可以用它來實現訪問網頁,下載檔案等功能。當然還有很多功能更高階的庫,比如urllib2或者requests等,這裡我們挑一個簡單的先。
url = "https://www.zhihu.com/question/20922273" 這句話的意思是定義url為字串"https://www.zhihu.com/question/20922273" ,也就是我們要訪問的網頁的網址,可以看出,網址實際上是一個字串變數。
page = urllib.urlopen(url) 這句話的意思是使用urllib庫裡面的urlopen函式,開啟網址url,並把開啟的這個物件命名為page(打開了一個東西,雖然我們不知道它是什麼,但是先給它起個名字吧!)。 page.read() 的意思就是讀取page這個物件的內容。print page.read()的意思自然就是列印這個內容了。 於是,我們就完成了第一步,開啟頁面。下面,我們來寫程式分析開啟的頁面。

寫程式,找出所有的圖片地址

在IDLE裡面,把前面寫的程式碼改一改。
#-*-coding:utf-8-*-
import urllib
from sgmllib import SGMLParser

class ZhihuParser(SGMLParser):
    imgList = []
    def reset(self):
        SGMLParser.reset(self)  #初始化
        self.imgList = []
    def start_img(self, attrs):
        imgUrl = [v for k, v in attrs if k=='data-original']
        if imgUrl:
            self.imgList.append(imgUrl[0])
            imgUrl = ""
   
url = "https://www.zhihu.com/question/20922273"
page = urllib.urlopen(url)
parser = ZhihuParser()
parser.feed(page.read())
print parser.imgList

點選F5執行,看看結果。
果然把所有的圖片地址都打印出來了。 下面來解釋一下程式碼。 from sgmllib import SGMLParser 這一句的意思是從sgmllib檔案裡面匯入SGMLParser這個包。這個包是Python自帶的一個解析HTML的庫。
class ZhihuParser(SGMLParser):是定義一個類ZhihuParser,它繼承SGMLParser這個類。我們對它進行一些修改。
imgList = [] 聲明瞭一個列表,我們用它來儲存所有圖片的地址。
def reset(self): 這個函式是ZhihuParser類的初始化函式。每一次生成ZhihuParser類的物件的時候都會呼叫這個函式。我們讓這個函式先初始化SGMLParser.reset(self),然後把列表制空self.imgList = []。
def start_img(self, attrs): 這個函式是解析img標籤的函式。當SGMLParser遇到一個img標籤的時候,就會呼叫這個函式。比如我們要解析<a>標籤,那就自己定義一個函式start_a,解析<head>標籤,就定義一個函式start_head,函式的內容是我們需要的動作。對於</something>這樣的結束標籤,對應的函式是end_something(self)。start_something函式的引數是(self,attrs)。self自然就是類本身,attrs是SGMLParser解析出來的標籤引數,比如知乎img的程式碼:
<img src="//zhstatic.zhihu.com/assets/zhihu/ztext/whitedot.jpg" data-rawwidth="720" data-rawheight="720" class="origin_image zh-lightbox-thumb lazy" width="720" data-original="https://pic3.zhimg.com/efb71614cacf505f92bd59509919a2d2_r.jpg" data-actualsrc="https://pic3.zhimg.com/efb71614cacf505f92bd59509919a2d2_b.jpg">
裡面的src, data-rawwidth等等,都是標籤的引數,它以字典的形式傳入函式中,即一系列引數名:引數的二元對。這裡我們需要提取出地址,即data-original的內容,imgUrl = [v for k, v in attrs if k=='data-original'],意思是遍歷attrs,如果字典的名為data-original,就儲存下來。 如果imgUrl儲存成功,我們就把它匯入到列表裡,self.imgList.append(imgUrl[0])。然後將臨時變數imgUrl制空(這句話其實不必要)。
parser = ZhihuParser()定義了一個ZhihuParser物件,parser.feed(page.read())將頁面的內容傳入parser,進行解析。print parser.imgList將所有的圖片地址打印出來。
下面我們繼續修改程式碼,把所有圖片儲存下來。 在上面的基礎上加入如下程式碼
cnt = 1
for url in parser.imgList:
    f = open("%d.jpg"%cnt,'wb')
    img = urllib.urlopen(url)
    f.write(img.read())
    f.close()
    print "%d was done!"%cnt
    cnt += 1
cnt = 1是定義一個計數器,記錄我們下載圖片的個數。 for url in parser.imgList:遍歷圖片地址列表。 f = open("%d.jpg"%cnt,'wb')開啟一個檔案。我們將下載下來的圖片寫入這個檔案中。
img = urllib.urlopen(url)開啟圖片地址。
f.write(img.read())將圖片寫入檔案,然後關閉檔案。
執行程式,不一會就看見我們將所有的網頁上的圖片都下載了下來。
於是乎,我們就完成了一個最簡單的網路爬蟲。

後記

事實上,真正的通用網路爬蟲是很難寫的,要考慮的因素很多。比如怎樣規避網站的反爬蟲機制(有的網站可不願意你下載檔案,佔用頻寬),怎樣避免爬蟲陷阱(比如有兩個頁面有相互連線的超級連結,於是爬蟲就會一直在這兩個頁面間爬來爬去,不去其他頁面),怎樣大規模快速地爬(分散式爬蟲),怎樣解析JavaScript等等等等。事實上,網路爬蟲是搜尋引擎的核心技術之一,google,百度等搜尋引擎公司每天都不停地在網路上爬取頁面,解析,並排序,給我們提供搜尋服務。