Python爬蟲小白入門(五)PhatomJS+Selenium第二篇

1、前言


前文介紹了PhatomJS 和Selenium 的用法,工具準備完畢,咱們來看看如何使用它們來改造咱們以前寫的小爬蟲。git

咱們的目的是模擬頁面下拉到底部,而後頁面會刷出新的內容,每次會加載10張新圖片。github

大致思路是,用Selenium + PhatomJS 來請求網頁,頁面加載後模擬下拉操做,能夠根據想要獲取的圖片多少來選擇下拉的次數,而後再獲取網頁中的所有內容。web

2、運行環境


個人運行環境以下:chrome

  • 系統版本
    Windows10。數據庫

  • Python版本
    Python3.5,推薦使用Anaconda 這個科學計算版本,主要是由於它自帶一個包管理工具,能夠解決有些包安裝錯誤的問題。去Anaconda官網,選擇Python3.5版本,而後下載安裝。瀏覽器

  • IDE
    我使用的是PyCharm,是專門爲Python開發的IDE。這是JetBrians的產品,點我下載網絡

3、爬蟲實戰改造


3.1 模擬下拉操做

要想實現網頁的下拉操做,須要使用Selenium的一個方法來執行js代碼。該方法以下:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")框架

因而可知,使用execute_script方法能夠調用JavaScript API在一個加載完成的頁面中去執行js代碼。能夠作任何你想作的操做哦,只要用js寫出來就能夠了。ide

改造的爬蟲的第一步就是封裝一個下拉方法,這個方法要能控制下拉的次數,下拉後要有等待頁面加載的時間,以及作一些信息記錄(在看下面的代碼前本身先想想啦):函數

def scroll_down(self, driver, times):
        for i in range(times):
            print("開始執行第", str(i + 1),"次下拉操做")
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  #執行JavaScript實現網頁下拉倒底部
            print("第", str(i + 1), "次下拉操做執行完畢")
            print("第", str(i + 1), "次等待網頁加載......")
            time.sleep(20)  # 等待20秒(時間能夠根據本身的網速而定),頁面加載出來再執行下拉操做

這部分作完以後就是修改頁面爬取邏輯,以前是使用request 請求網址,而後找到圖片url所在位置,再而後挨個請求圖片url。如今,咱們要首先使用Selenium 請求網址,而後模擬下拉操做,等待頁面加載完畢再遵循原有邏輯挨個請求圖片的url。

邏輯部分改造以下:

def get_pic(self):
        print('開始網頁get請求')
        # 使用selenium經過PhantomJS來進行網絡請求
        driver = webdriver.PhantomJS()
        driver.get(self.web_url)
        self.scroll_down(driver=driver, times=5)  #執行網頁下拉到底部操做,執行5次
        print('開始獲取全部a標籤')
        all_a = BeautifulSoup(driver.page_source, 'lxml').find_all('a', class_='cV68d')  #獲取網頁中的class爲cV68d的全部a標籤
        print('開始建立文件夾')
        self.mkdir(self.folder_path)  #建立文件夾
        print('開始切換文件夾')
        os.chdir(self.folder_path)   #切換路徑至上面建立的文件夾

        print("a標籤的數量是:", len(all_a))  #這裏添加一個查詢圖片標籤的數量,來檢查咱們下拉操做是否有誤
        for a in all_a: #循環每一個標籤,獲取標籤中圖片的url而且進行網絡請求,最後保存圖片
            img_str = a['style'] #a標籤中完整的style字符串
            print('a標籤的style內容是:', img_str)
            first_pos = img_str.index('"') + 1  #獲取第一個雙引號的位置,而後加1就是url的起始位置
            second_pos = img_str.index('"', first_pos)  #獲取第二個雙引號的位置
            img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取雙引號之間的內容

            #注:爲了儘快看到下拉加載的效果,截取高度和寬度部分暫時註釋掉,由於圖片較大,請求時間較長。
            ##獲取高度和寬度的字符在字符串中的位置
            #width_pos = img_url.index('&w=')
            #height_pos = img_url.index('&q=')
            #width_height_str = img_url[width_pos : height_pos] #使用切片功能截取高度和寬度參數,後面用來將該參數替換掉
            #print('高度和寬度數據字符串是:', width_height_str)
            #img_url_final = img_url.replace(width_height_str, '')  #把高度和寬度的字符串替換成空字符
            #print('截取後的圖片的url是:', img_url_final)

            #截取url中參數前面、網址後面的字符串爲圖片名
            name_start_pos = img_url.index('photo')
            name_end_pos = img_url.index('?')
            img_name = img_url[name_start_pos : name_end_pos]
            self.save_img(img_url, img_name) #調用save_img方法來保存圖片

邏輯修改完畢。執行一下,發現報錯了,圖片的url截取錯誤。那就看看究竟是爲何,先輸出找到url看看:

看看輸出內容,發現經過PhatomJS 請求網頁得到的url(https://blablabla) 中,居然沒有雙引號,這跟使用Chrome 看到的不同。好吧,那就按照沒有雙引號的字符串從新截取圖片的url。

其實,咱們只須要把url的起始位置和結束邏輯改爲經過小括號來獲取就能夠了:

first_pos = img_str.index('(') + 1  #起始位置是小括號的左邊
second_pos = img_str.index(')')  #結束位置是小括號的右邊

好啦,此次獲取圖片的url就沒什麼問題了,程序能夠順利的執行。

可是,細心的小夥伴兒確定發現了,咱們這個爬蟲在爬取的過程當中中斷了,或者過一段時間網站圖片更新了,我再想爬,有好多圖片都是重複的,卻又從新爬取一遍,浪費時間。那麼咱們有什麼辦法來達到去重的效果呢?

3.2 去重

想要作去重,無非就是在爬取圖片的時候記錄圖片名字(圖片名字是惟一的)。在爬取圖片前,先檢查該圖片是否已經爬取過,若是沒有,則繼續爬取,若是已經爬取過,則不進行爬取。

去重的邏輯如上,實現方式的不一樣主要體如今記錄已經爬取過的信息的途徑不一樣。好比可使用數據庫記錄、使用log文件記錄,或者直接檢查已經爬取過的數據信息。

根據爬取內容的不一樣實現方式也不一樣。咱們這裏先使用去文件夾下獲取全部文件名,而後在爬取的時候作對比。
後面的爬蟲實戰再使用其餘方式來作去重。

單從爬取unsplash 網站圖片這個實例來看,須要知道文件夾中全部文件的名字就夠了。

Python os 模塊中有兩種可以獲取文件夾下全部文件名的方法,分別來個示例:

for root, dirs, files in os.walk(path):
    for file in files:
        print(file)
for file in os.listdir(path):
    print(file)

其中,os.walk(path) 是獲取path文件夾下全部文件名和全部其子目錄中的文件夾名和文件名。
而os.listdir(path) 只是獲取path文件夾下的全部文件的名字,並不care其子文件夾。

這裏咱們使用os.listdir(path) 就能知足需求。

寫一個獲取文件夾內全部文件名的方法:

def get_files(self, path):
        pic_names = os.listdir(path)
        return pic_names

由於在保存圖片以前就要用到文件名的對比,因此把save_img方法修改了一下,在方法外部拼接圖片名字,而後直接做爲參數傳入,而不是在圖片存儲的過程當中命名:

def save_img(self, url, file_name): ##保存圖片
        print('開始請求圖片地址,過程會有點長...')
        img = self.request(url)
        print('開始保存圖片')
        f = open(file_name, 'ab')
        f.write(img.content)
        print(file_name,'圖片保存成功!')
        f.close()

爲了更清晰去重邏輯,咱們每次啓動爬蟲的時候檢測一下圖片存放的文件夾是否存在,若是存在則檢測與文件夾中的文件作對比,若是不存在則不須要作對比(由於是新建的嘛,文件夾裏面確定什麼文件都沒有)。
邏輯修改以下,是新建的則返回True,不是新建的則返回False:

def mkdir(self, path):  ##這個函數建立文件夾
        path = path.strip()
        isExists = os.path.exists(path)
        if not isExists:
            print('建立名字叫作', path, '的文件夾')
            os.makedirs(path)
            print('建立成功!')
            return True
        else:
            print(path, '文件夾已經存在了,再也不建立')
            return False

而後修改咱們的爬取邏輯部分:
主要是添加獲取的文件夾中的文件名列表,而後判斷圖片是否存在。

is_new_folder = self.mkdir(self.folder_path)  #建立文件夾,並判斷是不是新建立
file_names = self.get_files(self.folder_path)  #獲取文件家中的全部文件名,類型是list
if is_new_folder:
                self.save_img(img_url, img_name)  # 調用save_img方法來保存圖片
            else:
                if img_name not in file_names:
                    self.save_img(img_url, img_name)  # 調用save_img方法來保存圖片
                else:
                    print("該圖片已經存在:", img_name, ",再也不從新下載。")

好了,來個完整版代碼(也能夠去 GitHub下載):

from selenium import webdriver  #導入Selenium
import requests
from bs4 import BeautifulSoup  #導入BeautifulSoup 模塊
import os  #導入os模塊
import time

class BeautifulPicture():

    def __init__(self):  #類的初始化操做
        self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1'}  #給請求指定一個請求頭來模擬chrome瀏覽器
        self.web_url = 'https://unsplash.com'  #要訪問的網頁地址
        self.folder_path = 'C:\D\BeautifulPicture'  #設置圖片要存放的文件目錄

    def get_pic(self):
        print('開始網頁get請求')
        # 使用selenium經過PhantomJS來進行網絡請求
        driver = webdriver.PhantomJS()
        driver.get(self.web_url)
        self.scroll_down(driver=driver, times=3)  #執行網頁下拉到底部操做,執行3次
        print('開始獲取全部a標籤')
        all_a = BeautifulSoup(driver.page_source, 'lxml').find_all('a', class_='cV68d')  #獲取網頁中的class爲cV68d的全部a標籤
        print('開始建立文件夾')
        is_new_folder = self.mkdir(self.folder_path)  #建立文件夾,並判斷是不是新建立
        print('開始切換文件夾')
        os.chdir(self.folder_path)   #切換路徑至上面建立的文件夾

        print("a標籤的數量是:", len(all_a))   #這裏添加一個查詢圖片標籤的數量,來檢查咱們下拉操做是否有誤
        file_names = self.get_files(self.folder_path)  #獲取文件家中的全部文件名,類型是list

        for a in all_a: #循環每一個標籤,獲取標籤中圖片的url而且進行網絡請求,最後保存圖片
            img_str = a['style'] #a標籤中完整的style字符串
            print('a標籤的style內容是:', img_str)
            first_pos = img_str.index('(') + 1
            second_pos = img_str.index(')')
            img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取雙引號之間的內容

            # 注:爲了儘快看到下拉加載的效果,截取高度和寬度部分暫時註釋掉,由於圖片較大,請求時間較長。
            #獲取高度和寬度的字符在字符串中的位置
            # width_pos = img_url.index('&w=')
            # height_pos = img_url.index('&q=')
            # width_height_str = img_url[width_pos : height_pos] #使用切片功能截取高度和寬度參數,後面用來將該參數替換掉
            # print('高度和寬度數據字符串是:', width_height_str)
            # img_url_final = img_url.replace(width_height_str, '')  #把高度和寬度的字符串替換成空字符
            # print('截取後的圖片的url是:', img_url_final)

            #截取url中參數前面、網址後面的字符串爲圖片名
            name_start_pos = img_url.index('.com/') + 5  #經過找.com/的位置,來肯定它以後的字符位置
            name_end_pos = img_url.index('?')
            img_name = img_url[name_start_pos : name_end_pos] + '.jpg'
            img_name = img_name.replace('/','')  #把圖片名字中的斜槓都去掉

            if is_new_folder:
                self.save_img(img_url, img_name)  # 調用save_img方法來保存圖片
            else:
                if img_name not in file_names:
                    self.save_img(img_url, img_name)  # 調用save_img方法來保存圖片
                else:
                    print("該圖片已經存在:", img_name, ",再也不從新下載。")

    def save_img(self, url, file_name): ##保存圖片
        print('開始請求圖片地址,過程會有點長...')
        img = self.request(url)
        print('開始保存圖片')
        f = open(file_name, 'ab')
        f.write(img.content)
        print(file_name,'圖片保存成功!')
        f.close()

    def request(self, url):  #返回網頁的response
        r = requests.get(url)  # 像目標url地址發送get請求,返回一個response對象。有沒有headers參數均可以。
        return r

    def mkdir(self, path):  ##這個函數建立文件夾
        path = path.strip()
        isExists = os.path.exists(path)
        if not isExists:
            print('建立名字叫作', path, '的文件夾')
            os.makedirs(path)
            print('建立成功!')
            return True
        else:
            print(path, '文件夾已經存在了,再也不建立')
            return False

    def scroll_down(self, driver, times):
        for i in range(times):
            print("開始執行第", str(i + 1),"次下拉操做")
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  #執行JavaScript實現網頁下拉倒底部
            print("第", str(i + 1), "次下拉操做執行完畢")
            print("第", str(i + 1), "次等待網頁加載......")
            time.sleep(30)  # 等待30秒,頁面加載出來再執行下拉操做

    def get_files(self, path):
        pic_names = os.listdir(path)
        return pic_names

beauty = BeautifulPicture()  #建立類的實例
beauty.get_pic()  #執行類中的方法

註釋寫的很詳細,有任何問題能夠留言。

4、後語


該實戰就先到這裏,須要注意的是Unsplash 這個網站常常不穩定,小夥伴有遇到請求異常的狀況能夠多試幾回。

後面我開始寫一個查找The Beatles 樂隊的歷年專輯封面圖片和專輯名稱的爬蟲。個人設計師小夥伴兒想要收集Beatles 樂隊歷年的專輯封面圖片,而後作一個該樂隊的設計海報。

至於爬蟲框架的使用,後面我會再專門介紹,最好找到一個好的使用框架的實戰例子,這樣才能更好的理解框架的做用與優勢。

OK, See you then.

相關文章
相關標籤/搜索