1. 程式人生 > >python——圖片爬蟲:爬取愛女神網站(www.znzhi.net)上的妹子圖 進階篇

python——圖片爬蟲:爬取愛女神網站(www.znzhi.net)上的妹子圖 進階篇

我講解了圖片爬蟲的基本步驟,並實現了爬蟲程式碼

在本篇中,我將帶領大家對基礎篇中的程式碼進行改善,加入多執行緒,提高爬取效率。

首先我們明確一個改進的思路,就是在函式downloadAlbum(url)中:

# 迴圈下載專輯中各個圖片  
    for num in range(1, pic_count+1):  
        pic_url = url+"/"+str(num)+'.html'  
        i=0  
        while downloadOnePic(path, pic_url) == False:  
            print 'redownload'  
            i=i+1  
            if i > 5:  
                print "timeout's time too much"  
                break  
將for迴圈體中,呼叫downloadOnePic()的操作放到子執行緒中,然後迴圈建立子執行緒,即有n個圖片,迴圈建立n個子執行緒,相當於n個圖片同時處於下載狀態

一、新建子執行緒類

# 迴圈下載專輯中各個圖片  
    for num in range(1, pic_count+1):  
        pic_url = url+"/"+str(num)+'.html'  
        i=0  
        while downloadOnePic(path, pic_url) == False:  
            print 'redownload'  
            i=i+1  
            if i > 5:  
                print "timeout's time too much"  
                break  

二、迴圈建立子執行緒

# 迴圈下載專輯中各個圖片,對每個圖片的下載各開啟一個執行緒
    for num in range(1, pic_count+1):
        pic_url = url+"/"+str(num)+'.html'
        # 建立新的子執行緒
        threadD = threadDownload(path,pic_url)
        # 開啟子執行緒
        threadD.start()
    # 在主執行緒中迴圈查詢當前正在活動的執行緒數量
    while threading.active_count() != 0:
        # 當正在活動的執行緒數量為1,即只剩主執行緒時,表示所有子執行緒都已關閉,即所有圖片下載完畢
        if threading.active_count() == 1:
            print '  all pic has downloaded of this page:' + url
            return True

在原本for迴圈體呼叫downloadOnePic()的地方改為建立新的子執行緒,並啟動

然後用while迴圈查詢當前正在活動的執行緒數量,並在迴圈體中判斷當前的執行緒數量,當活動執行緒數量等於1,即只剩主執行緒時,表示所有子執行緒已關閉,所有圖片下載完畢,此時return退出

此爬蟲已經改進完成,通過使用多執行緒提高爬取效率。若還有其他可改進的地方,歡迎交流學習。

完整程式碼如下:

# coding:utf8
# python環境2.7
# 爬取網站:http://www.znzhi.net/
# author:CodeZ

import os
import re
import threading
import urllib2

import time

from bs4 import BeautifulSoup

BASE_PATH = 'picture'
HOST_HOT = 'http://www.znzhi.net/hot'
HOST_ALBUM = 'http://www.znzhi.net/p'
MAX_PAGE_NUM = 387

class threadDownload(threading.Thread):
    def __init__(self,path,url):
        threading.Thread.__init__(self)
        # 路徑引數
        self.path = path
        # url引數
        self.url = url
    def run(self):
        i=0
        while downloadOnePic(self.path, self.url) == False:
            print 'redownload'
            i=i+1
            if i > 5:
                print "timeout's time too much"
                break

def downloadUrl(url):
    # 捕獲異常(超時)
    try:
        # 開啟網頁
        response = urllib2.urlopen(url, timeout=10)
        # 設定編碼方式
        response.encoding = 'utf-8'
        # 判斷http請求的狀態
        if response.getcode() == 200:
            # 狀態正常(200),返回頁面資料
            return response.read()
        else:
            # 失敗,列印訊息,返回空資料
            print "error:url visit failed"
            return ''
    except Exception, e:
        # 列印異常
        print "exception:"+e.message
        print "reopen"
        # 重新下載
        return downloadUrl(url)

# 獲取相簿名稱
def getPicName(picUrl) :
    # 擷取地址中最後一個/後面的字元,即圖片名
    picName = os.path.basename(picUrl)
    if '.jpg' in picName:
        return picName
    return 'error.jpg'
# 下載單張照片
def downloadOnePic(path,url):
    soup = BeautifulSoup(downloadUrl(url),
                         'html.parser',
                         from_encoding='utf-8')
    # 獲取存有img節點
    img_node = soup.find('div', class_='main-image').find('img')
    # 獲取img的src值,即圖片地址
    pic_url = img_node.get('src')
    # 呼叫getPicName()獲取圖片名稱
    pic_name = getPicName(pic_url).encode('utf-8')
    try:
        # 訪問圖片地址,獲取資料
        content = urllib2.urlopen(pic_url, timeout=10).read()
        # 儲存圖片到本地
        with open(path + '/' + pic_name, 'wb') as code:
            code.write(content)
        print '  -> ' + pic_name + " download success"
    #捕獲異常
    except Exception, e:
        print "exception:"+e.message
        return False
    return True

def downloadAlbum(url):
    print "album:"+url
    # 獲取當前頁面資料
    content = downloadUrl(url)
    # 傳入頁面資料content,建立beautifulsoup物件soup
    soup = BeautifulSoup(content,
                         'html.parser',
                         from_encoding='utf-8')
    # 獲取存有圖片專輯標題的h2標籤
    title = soup.find('div', class_='content').find('h2')
    # 檢查是否有內容,在實際爬取中,有遇到過空圖片專輯的情況,
    if title == None:
        print "error:web content has lost"
        return
    # 通過正則篩選出標題中含有的總圖片數值
    title_num = re.findall(r'\d+', title.get_text())
    pic_count = int(title_num[-1])
    # 將(1/num)擷取去除,並新增總圖片數 [num]
    title_split = title.get_text().split(' (', 1)
    album_title = title_split[0]+'['+str(pic_count)+']'
    # 刪去標題中的'/'字元,防止在用標題作為名稱建圖片資料夾時報錯
    album_title = album_title.replace('/', ' ')
    # 拼接本地資料夾路徑,並檢查路徑是否存在,防止重複下載
    path = BASE_PATH + "/" + album_title
    if os.path.exists(path):
        print '  -> ' + album_title + ' has exists'
        return True
    # 新建存放當前專輯的圖片資料夾
    checkDocuments(path)
    print path
    # 新建一個html,存有此圖片專輯相關資訊
    with open(path+'/source.html','w') as fout:
        fout.write("<html>")
        fout.write("<body>")
        fout.write("<p>"+album_title.encode('utf-8')+"-["+str(pic_count)+"p]"+"</p>")
        fout.write("<a href=\""+url.encode('utf-8')+"\">來源網址:"+url.encode('utf-8')+"</a>")
        fout.write("</body>")
        fout.write("</html>")
    # 迴圈下載專輯中各個圖片,對每個圖片的下載各開啟一個執行緒
    for num in range(1, pic_count+1):
        pic_url = url+"/"+str(num)+'.html'
        # 建立新的子執行緒
        threadD = threadDownload(path,pic_url)
        # 開啟子執行緒
        threadD.start()
    # 在主執行緒中迴圈查詢當前正在活動的執行緒數量
    while threading.active_count() != 0:
        # 當正在活動的執行緒數量為1,即只剩主執行緒時,表示所有子執行緒都已關閉,即所有圖片下載完畢
        if threading.active_count() == 1:
            print '  all pic has downloaded of this page:' + url
            return True

def downloadPage(url):
    print "page:"+url
    # 獲取當前頁面資料
    content = downloadUrl(url)
    # 傳入頁面資料content,建立beautifulsoup物件soup
    soup = BeautifulSoup(content,
                         'html.parser',
                         from_encoding='utf-8')
    # 獲取單頁中18個圖片專輯的父節點
    album_block = soup.find('ul', id='images')
    # 獲取父節點下圖片專輯地址的a節點集
    album_nodes = album_block.findAll('a', href=re.compile(r'http://www.znzhi.net/p/'))
    # 由於每個專輯的a標籤有兩個,用[::2]獲取a節點集中的偶數項,迴圈下載圖片專輯
    for album_node in album_nodes[::2]:
        # 呼叫downloadAlbum
        # 傳入album_node.get('href')獲取a節點的href值,即專輯地址
        downloadAlbum(album_node.get('href'))
        # 若執行中想終止爬蟲程式,可在同父目錄下新建stop.txt檔案
        if os.path.exists('stop.txt'):
            exit(0)
        # 設定圖片專輯下載間隙休眠,防止因訪問頻繁,被網站拉黑
        time.sleep(4)
# 檢查本地檔案路徑是否存在,不存在則建立
def checkDocuments(path):
    if os.path.exists(path) == False:
        os.mkdir(path)
# main函式
if __name__ == "__main__":
    # 檢查本地下載路徑是否存在
    checkDocuments(BASE_PATH)
    # 迴圈訪問
    for i in range(1, MAX_PAGE_NUM+1):
        # 拼接頁地址,格式為:http://www.znzhi.net/hot/頁碼.html
        page_url = HOST_HOT+'/'+str(i)+'.html'
        # 儲存當前頁碼,供檢視下載進度
        with open('cur_page.txt', 'w') as fpage:
            fpage.write(str(i))
        # 以頁為單位進行下載
        downloadPage(page_url)