Python爬蟲入門教程 2-100 妹子圖網站爬取

夢想橡皮擦發表於2018-12-13

前言

從今天開始就要擼起袖子,直接寫Python爬蟲了,學習語言最好的辦法就是有目的的進行,所以,接下來我將用10+篇的部落格,寫爬圖片這一件事情。希望可以做好。

為了寫好爬蟲,我們需要準備一個火狐瀏覽器,還需要準備抓包工具,抓包工具,我使用的是CentOS自帶的tcpdump,加上wireshark ,這兩款軟體的安裝和使用,建議你還是學習一下,後面我們應該會用到。

網路請求模組requests

Python中的大量開源的模組使得編碼變的特別簡單,我們寫爬蟲第一個要了解的模組就是requests。

安裝requests

開啟終端:使用命令

pip3 install requests

等待安裝完畢即可使用

接下來在終端中鍵入如下命令

# mkdir demo  
# cd demo
# touch down.py
複製程式碼

上面的linux命令是 建立一個名稱為demo的資料夾,之後建立一個down.py檔案,你也可以使用GUI工具,像操作windows一樣,右鍵建立各種檔案。

為了提高在linux上的開發效率,我們需要安裝一個visual studio code 的開發工具

對於怎麼安裝vscode,參考官方的https://code.visualstudio.com/docs/setup/linux 有詳細的說明。

對於centos則如下:

sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
sudo sh -c 'echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/vscode.repo'
複製程式碼

然後用yum命令安裝

yum check-update
sudo yum install code
複製程式碼

安裝成功之後,在你的CentOS中會出現如下畫面

這裡寫圖片描述

接著說我們上面的操作 ,因為我們這邊是用gnome圖形介面,所以後面的有些操作,我直接用windows的操作風格講解了

開啟軟體>檔案>開啟檔案>找到我們剛剛建立的down.py檔案

之後,在VSCODE裡面輸入


import requests   #匯入模組

def run():        #宣告一個run方法
    print("跑碼檔案")    #列印內容

if __name__ == "__main__":   #主程式入口
    run()    #呼叫上面的run方法
        
複製程式碼

tips:本教程不是Python3的基礎入門課,所以有些編碼基礎,預設你懂,比如Python沒有分號結尾,需要對齊格式。我會盡量把註釋寫的完整

按鍵盤上的ctrl+s儲存檔案,如果提示許可權不足,那麼按照提示輸入密碼即可

通過終端進入demo目錄,然後輸入

python3 down.py

顯示如下結果,代表編譯沒有問題

[root@bogon demo]# python3 down.py
跑碼檔案
複製程式碼

接下來,我們開始測試requests模組是否可以使用

修改上述程式碼中的


import requests

def run():
    response = requests.get("http://www.baidu.com")
    print(response.text)

if __name__ == "__main__":
    run()
        
複製程式碼

執行結果(出現下圖代表你執行成功了):

這裡寫圖片描述

接下來,我們實際下載一張圖片試試,比如下面這張圖片

這裡寫圖片描述

修改程式碼,在這之前,我們修改一些內容

由於每次修改檔案,都提示必須管理員許可權,所以你可以使用linux命令修改許可權。

[root@bogon linuxboy]# chmod -R 777 demo/


import requests

def run():
    response = requests.get("http://www.newsimg.cn/big201710leaderreports/xibdj20171030.jpg") 
    with open("xijinping.jpg","wb") as f :
        f.write(response.content)   
        f.close

if __name__ == "__main__":
    run()
複製程式碼

執行程式碼之後,發現在資料夾內部生成了一個檔案

這裡寫圖片描述

但是開啟檔案之後發現,這個檔案並不能查閱,這代表這個檔案壓根沒有下載下來

這裡寫圖片描述

我們繼續修改程式碼,因為有的伺服器圖片,都做了一些限制,我們可以用瀏覽器開啟,但是使用Python程式碼並不能完整的下載下來。

修改程式碼


import requests

def run():
    # 標頭檔案,header是字典型別
    headers = {
        "Host":"www.newsimg.cn",
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5383.400 QQBrowser/10.0.1313.400"
    }
    response = requests.get("http://www.newsimg.cn/big201710leaderreports/xibdj20171030.jpg",headers=headers) 
    with open("xijinping.jpg","wb") as f :
        f.write(response.content)   
        f.close

if __name__ == "__main__":
    run()
        
複製程式碼

好了,這次在終端編譯一下python檔案

python3 down.py

發現圖片下載下來了

這裡寫圖片描述

我們重點檢視上述程式碼中 requests.get部分,新增了一個headers的實參。這樣我們程式就下載下來了完整的圖片。

Python爬蟲頁面分析

有了上面這個簡單的案例,我們接下來的操作就變的簡單多了。爬蟲是如何進行的呢?

輸入域名->下載原始碼->分析圖片路徑->下載圖片

上面就是他的步驟

輸入域名

我們今天要爬的網站叫做 www.meizitu.com/a/pure.html

為啥爬取這個網站,因為好爬。

好了,接下來分析這個頁面

這裡寫圖片描述

做爬蟲很重要的一點,就是你要找到分頁的地方,因為有分頁代表著有規律,有規律,我們就好爬了(可以做的更智慧一些,輸入首頁網址,爬蟲自己就能分析到這個網站中的所有地址)

上面圖片中,我們發現了分頁,那麼找規律吧

這裡寫圖片描述

使用火狐瀏覽器的開發者工具,發現分頁規律

http://www.meizitu.com/a/pure_1.html
http://www.meizitu.com/a/pure_2.html
http://www.meizitu.com/a/pure_3.html
http://www.meizitu.com/a/pure_4.html
複製程式碼

好了,接下來用Python實現這部分(以下寫法有部分物件導向的寫法,沒有基礎的同學,請百度找些基礎來看,不過對於想學習的你來說,這些簡單極了)


import requests
all_urls = []  #我們拼接好的圖片集和列表路徑
class Spider():
    #建構函式,初始化資料使用
    def __init__(self,target_url,headers):
        self.target_url = target_url
        self.headers = headers

    #獲取所有的想要抓取的URL
    def getUrls(self,start_page,page_num):
        
        global all_urls
        #迴圈得到URL
        for i in range(start_page,page_num+1):
            url = self.target_url  % i
            all_urls.append(url)

if __name__ == "__main__":
    headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
            'HOST':'www.meizitu.com'
    }
    target_url = 'http://www.meizitu.com/a/pure_%d.html' #圖片集和列表規則
    
    spider = Spider(target_url,headers)
    spider.getUrls(1,16)
    print(all_urls)
        
複製程式碼

上面的程式碼,可能需要有一定的Python基礎可以看懂,不過你其實仔細看一下,就幾個要點

第一個是 class Spider(): 我們宣告瞭一個類,然後我們使用 def __init__去宣告一個建構函式,這些我覺得你找個教程30分鐘也就學會了。

拼接URL,我們可以用很多辦法,我這裡用的是最直接的,字串拼接。

注意上述程式碼中有一個全域性的變數 all_urls 我用它來儲存我們的所有分頁的URL

接下來,是爬蟲最核心的部分程式碼了

我們需要分析頁面中的邏輯。首先開啟 www.meizitu.com/a/pure_1.ht… ,右鍵審查元素。

這裡寫圖片描述

這裡寫圖片描述

發現上圖紅色框框裡面的連結

點選圖片之後,發現進入一個圖片詳情頁面,發現竟然是一組圖片,那麼現在的問題是

我們要解決第一步,需要在 www.meizitu.com/a/pure_1.ht… 這種頁面中爬取所有的 www.meizitu.com/a/5585.html 這種地址

這裡我們採用多執行緒的方式爬取(這裡還用了一種設計模式,叫觀察者模式)

import threading   #多執行緒模組
import re #正規表示式模組
import time #時間模組

複製程式碼

首先引入三個模組,分別是多執行緒,正規表示式,時間模組

新增加一個全域性的變數,並且由於是多執行緒操作,我們需要引入執行緒鎖

all_img_urls = []       #圖片列表頁面的陣列

g_lock = threading.Lock()  #初始化一個鎖  

複製程式碼

宣告一個生產者的類,用來不斷的獲取圖片詳情頁地址,然後新增到 all_img_urls 這個全域性變數中


#生產者,負責從每個頁面提取圖片列表連結
class Producer(threading.Thread):   

    def run(self):
        headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
            'HOST':'www.meizitu.com'
        }
        global all_urls
        while len(all_urls) > 0 :
            g_lock.acquire()  #在訪問all_urls的時候,需要使用鎖機制
            page_url = all_urls.pop()   #通過pop方法移除最後一個元素,並且返回該值
            
            g_lock.release() #使用完成之後及時把鎖給釋放,方便其他執行緒使用
            try:
                print("分析"+page_url)   
                response = requests.get(page_url , headers = headers,timeout=3)
                all_pic_link = re.findall('<a target=\'_blank\' href="(.*?)">',response.text,re.S)   
                global all_img_urls
                g_lock.acquire()   #這裡還有一個鎖
                all_img_urls += all_pic_link   #這個地方注意陣列的拼接,沒有用append直接用的+=也算是python的一個新語法吧
                print(all_img_urls)
                g_lock.release()   #釋放鎖
                time.sleep(0.5)
            except:
                pass


複製程式碼

上述程式碼用到了繼承的概念,我從threading.Thread中繼承了一個子類,繼承的基礎學習,你可以去翻翻 www.runoob.com/python3/pyt… 菜鳥教程就行。

執行緒鎖,在上面的程式碼中,當我們操作all_urls.pop()的時候,我們是不希望其他執行緒對他進行同時操作的,否則會出現意外,所以我們使用g_lock.acquire()鎖定資源,然後使用完成之後,記住一定要立馬釋放g_lock.release(),否則這個資源就一直被佔用著,程式無法進行下去了。

匹配網頁中的URL,我使用的是正規表示式,後面我們會使用其他的辦法,進行匹配。

re.findall()方法是獲取所有匹配到的內容,正規表示式,你可以找一個30分鐘入門的教程,看看就行。

程式碼容易出錯的地方,我放到了

try: except: 裡面,當然,你也可以自定義錯誤。

如果上面的程式碼,都沒有問題,那麼我們就可以在程式入口的地方編寫

for x in range(2):
    t = Producer()
    t.start()
        
複製程式碼

執行程式,因為我們的Producer繼承自threading.Thread類,所以,你必須要實現的一個方法是 def run 這個我相信在上面的程式碼中,你已經看到了。然後我們可以執行啦~~~

執行結果:

這裡寫圖片描述

這樣,圖片詳情頁面的列表就已經被我們儲存起來了。

接下來,我們需要執行這樣一步操作,我想要等待圖片詳情頁面全部獲取完畢,在進行接下來的分析操作。

這裡增加程式碼

#threads= []   
#開啟兩個執行緒去訪問
for x in range(2):
    t = Producer()
    t.start()
    #threads.append(t)

# for tt in threads:
#     tt.join()

print("進行到我這裡了")


複製程式碼

註釋關鍵程式碼,執行如下

[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
進行到我這裡了
['http://www.meizitu.com/a/5585.html', 

複製程式碼

把上面的tt.join等程式碼註釋開啟

[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
['http://www.meizitu.com/a/5429.html', ......
進行到我這裡了

複製程式碼

發現一個本質的區別,就是,我們由於是多執行緒的程式,所以,當程式跑起來之後,print("進行到我這裡了")不會等到其他執行緒結束,就會執行到,但是當我們改造成上面的程式碼之後,也就是加入了關鍵的程式碼 tt.join() 那麼主執行緒的程式碼會等到所以子執行緒執行完畢之後,在接著向下執行。這就滿足了,我剛才說的,先獲取到所有的圖片詳情頁面的集合,這一條件了。

join所完成的工作就是執行緒同步,即主執行緒遇到join之後進入阻塞狀態,一直等待其他的子執行緒執行結束之後,主執行緒在繼續執行。這個大家在以後可能經常會碰到。

下面編寫一個消費者/觀察者,也就是不斷關注剛才我們獲取的那些圖片詳情頁面的陣列。

新增一個全域性變數,用來儲存獲取到的圖片連結

pic_links = []            #圖片地址列表

複製程式碼
#消費者
class Consumer(threading.Thread) : 
    def run(self):
        headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
            'HOST':'www.meizitu.com'
        }
        global all_img_urls   #呼叫全域性的圖片詳情頁面的陣列
        print("%s is running " % threading.current_thread)
        while len(all_img_urls) >0 : 
            g_lock.acquire()
            img_url = all_img_urls.pop()
            g_lock.release()
            try:
                response = requests.get(img_url , headers = headers )
                response.encoding='gb2312'   #由於我們呼叫的頁面編碼是GB2312,所以需要設定一下編碼
                title = re.search('<title>(.*?) | 妹子圖</title>',response.text).group(1)
                all_pic_src = re.findall('<img alt=.*?src="(.*?)" /><br />',response.text,re.S)
                
                pic_dict = {title:all_pic_src}   #python字典
                global pic_links
                g_lock.acquire()
                pic_links.append(pic_dict)    #字典陣列
                print(title+" 獲取成功")
                g_lock.release()
                
            except:
                pass
            time.sleep(0.5)


複製程式碼

看到沒有,上面的程式碼其實和我們剛才寫的極其相似,後面,我會在github上面把這部分程式碼修改的更加簡潔一些,不過這才是第二課,後面我們的路長著呢。

程式碼中比較重要的一些部分,我已經使用註釋寫好了,大家可以直接參考。大家一定要注意我上面使用了兩個正規表示式,分別用來匹配title和圖片的url這個title是為了後面建立不同的資料夾使用的,所以大家注意吧。

#開啟10個執行緒去獲取連結
for x in range(10):
    ta = Consumer()
    ta.start()

複製程式碼

執行結果:

[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
['http://www.meizitu.com/a/5585.html', ......
<function current_thread at 0x7f7caef851e0> is running 
<function current_thread at 0x7f7caef851e0> is running 
<function current_thread at 0x7f7caef851e0> is running 
<function current_thread at 0x7f7caef851e0> is running 
<function current_thread at 0x7f7caef851e0> is running 
<function current_thread at 0x7f7caef851e0> is running 
<function current_thread at 0x7f7caef851e0> is running 
<function current_thread at 0x7f7caef851e0> is running 
進行到我這裡了
<function current_thread at 0x7f7caef851e0> is running 
<function current_thread at 0x7f7caef851e0> is running 
清純美如畫,攝影師的御用麻豆 獲取成功
宅男女神葉梓萱近日拍攝一組火爆寫真 獲取成功
美(bao)胸(ru)女王帶來制服誘惑 獲取成功
每天睜開眼看到美好的你,便是幸福 獲取成功
可愛女孩,願暖風呵護純真和執著 獲取成功
清純妹子如一縷陽光溫暖這個冬天 獲取成功 .....


複製程式碼

是不是感覺距離成功有進了一大步

接下來就是,我們開篇提到的那個儲存圖片的操作了,還是同樣的步驟,寫一個自定義的類

class DownPic(threading.Thread) :

    def run(self):
        headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
            'HOST':'mm.chinasareview.com'
        
        }
        while True:   #  這個地方寫成死迴圈,為的是不斷監控圖片連結陣列是否更新
            global pic_links
            # 上鎖
            g_lock.acquire()
            if len(pic_links) == 0:   #如果沒有圖片了,就解鎖
                # 不管什麼情況,都要釋放鎖
                g_lock.release()
                continue
            else:
                pic = pic_links.pop()
                g_lock.release()
                # 遍歷字典列表
                for key,values in  pic.items():
                    path=key.rstrip("\\")
                    is_exists=os.path.exists(path)
                    # 判斷結果
                    if not is_exists:
                        # 如果不存在則建立目錄
                        # 建立目錄操作函式
                        os.makedirs(path) 
                
                        print (path+'目錄建立成功')
                        
                    else:
                        # 如果目錄存在則不建立,並提示目錄已存在
                        print(path+' 目錄已存在') 
                    for pic in values :
                        filename = path+"/"+pic.split('/')[-1]
                        if os.path.exists(filename):
                            continue
                        else:
                            response = requests.get(pic,headers=headers)
                            with open(filename,'wb') as f :
                                f.write(response.content)
                                f.close


複製程式碼

我們獲取圖片連結之後,就需要下載了,我上面的程式碼是首先建立了一個之前獲取到title的檔案目錄,然後在目錄裡面通過下面的程式碼,去建立一個檔案。

涉及到檔案操作,引入一個新的模組

import os  #目錄操作模組

複製程式碼
# 遍歷字典列表
for key,values in  pic.items():
    path=key.rstrip("\\")
    is_exists=os.path.exists(path)
    # 判斷結果
    if not is_exists:
        # 如果不存在則建立目錄
        # 建立目錄操作函式
        os.makedirs(path) 

        print (path+'目錄建立成功')
        
    else:
        # 如果目錄存在則不建立,並提示目錄已存在
        print(path+' 目錄已存在') 
    for pic in values :
        filename = path+"/"+pic.split('/')[-1]
        if os.path.exists(filename):
            continue
        else:
            response = requests.get(pic,headers=headers)
            with open(filename,'wb') as f :
                f.write(response.content)
                f.close

複製程式碼

因為我們的圖片連結陣列,裡面存放是的字典格式,也就是下面這種格式


[{"妹子圖1":["http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/01.jpg","http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/02.jpg"."http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/03.jpg"]},{"妹子圖2":["http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/01.jpg","http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/02.jpg"."http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/03.jpg"]},{"妹子圖3":["http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/01.jpg","http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/02.jpg"."http://mm.chinasareview.com/wp-content/uploads/2016a/08/24/03.jpg"]}]

複製程式碼

需要先迴圈第一層,獲取title,建立目錄之後,在迴圈第二層去下載圖片,程式碼中,我們在修改一下,把異常處理新增上。

try:
    response = requests.get(pic,headers=headers)
    with open(filename,'wb') as f :
        f.write(response.content)
        f.close
except Exception as e:
    print(e)
    pass


複製程式碼

然後在主程式中編寫程式碼


#開啟10個執行緒儲存圖片
for x in range(10):
    down = DownPic()
    down.start()
        
複製程式碼

執行結果:


[linuxboy@bogon demo]$ python3 down.py
分析http://www.meizitu.com/a/pure_2.html
分析http://www.meizitu.com/a/pure_1.html
['http://www.meizitu.com/a/5585.html', 'http://www.meizitu.com/a/5577.html', 'http://www.meizitu.com/a/5576.html', 'http://www.meizitu.com/a/5574.html', 'http://www.meizitu.com/a/5569.html', .......
<function current_thread at 0x7fa5121f2268> is running 
<function current_thread at 0x7fa5121f2268> is running 
<function current_thread at 0x7fa5121f2268> is running 
進行到我這裡了
清純妹子如一縷陽光溫暖這個冬天 獲取成功
清純妹子如一縷陽光溫暖這個冬天目錄建立成功
可愛女孩,願暖風呵護純真和執著 獲取成功
可愛女孩,願暖風呵護純真和執著目錄建立成功
超美,純純的你與藍藍的天相得益彰 獲取成功
超美,純純的你與藍藍的天相得益彰目錄建立成功
美麗凍人,雪地裡的跆拳道少女 獲取成功
五官精緻的美眉,彷彿童話裡的公主 獲取成功
有自信迷人的笑容,每天都是燦爛的 獲取成功
五官精緻的美眉,彷彿童話裡的公主目錄建立成功
有自信迷人的笑容,每天都是燦爛的目錄建立成功
清純美如畫,攝影師的御用麻豆 獲取成功


複製程式碼

檔案目錄下面同時出現

這裡寫圖片描述

點開一個目錄

這裡寫圖片描述

好了,今天的一個簡單的爬蟲成了

最後我們在程式碼的頭部寫上

# -*- coding: UTF-8 -*-   
複製程式碼

防止出現 Non-ASCII character 'xe5' in file報錯問題。

最後放上github程式碼地址:

github.com/wangdezhen/…

相關文章