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

一、前言


前文介紹了PhatomJS 和Selenium 的用法,工具準(zhǔn)備完畢,我們來看看如何使用它們來改造我們之前寫的小爬蟲。

我們的目的是模擬頁面下拉到底部,然后頁面會刷出新的內(nèi)容,每次會加載10張新圖片。

大體思路是,用Selenium + PhatomJS 來請求網(wǎng)頁,頁面加載后模擬下拉操作,可以根據(jù)想要獲取的圖片多少來選擇下拉的次數(shù),然后再獲取網(wǎng)頁中的全部內(nèi)容。

二、運(yùn)行環(huán)境


我的運(yùn)行環(huán)境如下:

  • 系統(tǒng)版本
    Windows10。

  • Python版本
    Python3.5,推薦使用Anaconda 這個科學(xué)計(jì)算版本,主要是因?yàn)樗詭б粋€包管理工具,可以解決有些包安裝錯誤的問題。去Anaconda官網(wǎng),選擇Python3.5版本,然后下載安裝。

  • IDE
    我使用的是PyCharm,是專門為Python開發(fā)的IDE。這是JetBrians的產(chǎn)品,點(diǎn)我下載。

三、爬蟲實(shí)戰(zhàn)改造


3.1 模擬下拉操作

要想實(shí)現(xiàn)網(wǎng)頁的下拉操作,需要使用Selenium的一個方法來執(zhí)行js代碼。該方法如下:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

由此可見,使用execute_script方法可以調(diào)用JavaScript API在一個加載完成的頁面中去執(zhí)行js代碼??梢宰鋈魏文阆胱龅牟僮髋叮灰胘s寫出來就可以了。

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

    def scroll_down(self, driver, times):
        for i in range(times):
            print("開始執(zhí)行第", str(i + 1),"次下拉操作")
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  #執(zhí)行JavaScript實(shí)現(xiàn)網(wǎng)頁下拉倒底部
            print("第", str(i + 1), "次下拉操作執(zhí)行完畢")
            print("第", str(i + 1), "次等待網(wǎng)頁加載......")
            time.sleep(20)  # 等待20秒(時間可以根據(jù)自己的網(wǎng)速而定),頁面加載出來再執(zhí)行下拉操作

這部分做完之后就是修改頁面爬取邏輯,之前是使用request 請求網(wǎng)址,然后找到圖片url所在位置,再然后挨個請求圖片url。現(xiàn)在,我們要首先使用Selenium 請求網(wǎng)址,然后模擬下拉操作,等待頁面加載完畢再遵循原有邏輯挨個請求圖片的url。

邏輯部分改造如下:

    def get_pic(self):
        print('開始網(wǎng)頁get請求')
        # 使用selenium通過PhantomJS來進(jìn)行網(wǎng)絡(luò)請求
        driver = webdriver.PhantomJS()
        driver.get(self.web_url)
        self.scroll_down(driver=driver, times=5)  #執(zhí)行網(wǎng)頁下拉到底部操作,執(zhí)行5次
        print('開始獲取所有a標(biāo)簽')
        all_a = BeautifulSoup(driver.page_source, 'lxml').find_all('a', class_='cV68d')  #獲取網(wǎng)頁中的class為cV68d的所有a標(biāo)簽
        print('開始創(chuàng)建文件夾')
        self.mkdir(self.folder_path)  #創(chuàng)建文件夾
        print('開始切換文件夾')
        os.chdir(self.folder_path)   #切換路徑至上面創(chuàng)建的文件夾

        print("a標(biāo)簽的數(shù)量是:", len(all_a))  #這里添加一個查詢圖片標(biāo)簽的數(shù)量,來檢查我們下拉操作是否有誤
        for a in all_a: #循環(huán)每個標(biāo)簽,獲取標(biāo)簽中圖片的url并且進(jìn)行網(wǎng)絡(luò)請求,最后保存圖片
            img_str = a['style'] #a標(biāo)簽中完整的style字符串
            print('a標(biāo)簽的style內(nèi)容是:', 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的切片功能截取雙引號之間的內(nèi)容

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

            #截取url中參數(shù)前面、網(wǎng)址后面的字符串為圖片名
            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) #調(diào)用save_img方法來保存圖片

邏輯修改完畢。執(zhí)行一下,發(fā)現(xiàn)報錯了,圖片的url截取錯誤。那就看看到底是為什么,先輸出找到url看看:

看看輸出內(nèi)容,發(fā)現(xiàn)通過PhatomJS 請求網(wǎng)頁獲得的url(https://blablabla) 中,竟然沒有雙引號,這跟使用Chrome 看到的不一樣。好吧,那就按照沒有雙引號的字符串重新截取圖片的url。

其實(shí),我們只需要把url的起始位置和結(jié)束邏輯改成通過小括號來獲取就可以了:

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

好啦,這次獲取圖片的url就沒什么問題了,程序可以順利的執(zhí)行。

但是,細(xì)心的小伙伴兒肯定發(fā)現(xiàn)了,我們這個爬蟲在爬取的過程中中斷了,或者過一段時間網(wǎng)站圖片更新了,我再想爬,有好多圖片都是重復(fù)的,卻又重新爬取一遍,浪費(fèi)時間。那么我們有什么辦法來達(dá)到去重的效果呢?

3.2 去重

想要做去重,無非就是在爬取圖片的時候記錄圖片名字(圖片名字是唯一的)。在爬取圖片前,先檢查該圖片是否已經(jīng)爬取過,如果沒有,則繼續(xù)爬取,如果已經(jīng)爬取過,則不進(jìn)行爬取。

去重的邏輯如上,實(shí)現(xiàn)方式的不同主要體現(xiàn)在記錄已經(jīng)爬取過的信息的途徑不同。比如可以使用數(shù)據(jù)庫記錄、使用log文件記錄,或者直接檢查已經(jīng)爬取過的數(shù)據(jù)信息。

根據(jù)爬取內(nèi)容的不同實(shí)現(xiàn)方式也不同。我們這里先使用去文件夾下獲取所有文件名,然后在爬取的時候做對比。
后面的爬蟲實(shí)戰(zhàn)再使用其他方式來做去重。

單從爬取unsplash 網(wǎng)站圖片這個實(shí)例來看,需要知道文件夾中所有文件的名字就夠了。

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) 就能滿足需求。

寫一個獲取文件夾內(nèi)所有文件名的方法:

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

因?yàn)樵诒4鎴D片之前就要用到文件名的對比,所以把save_img方法修改了一下,在方法外部拼接圖片名字,然后直接作為參數(shù)傳入,而不是在圖片存儲的過程中命名:

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

為了更清晰去重邏輯,我們每次啟動爬蟲的時候檢測一下圖片存放的文件夾是否存在,如果存在則檢測與文件夾中的文件做對比,如果不存在則不需要做對比(因?yàn)槭切陆ǖ穆?,文件夾里面肯定什么文件都沒有)。
邏輯修改如下,是新建的則返回True,不是新建的則返回False:

    def mkdir(self, path):  ##這個函數(shù)創(chuàng)建文件夾
        path = path.strip()
        isExists = os.path.exists(path)
        if not isExists:
            print('創(chuàng)建名字叫做', path, '的文件夾')
            os.makedirs(path)
            print('創(chuàng)建成功!')
            return True
        else:
            print(path, '文件夾已經(jīng)存在了,不再創(chuàng)建')
            return False

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

is_new_folder = self.mkdir(self.folder_path)  #創(chuàng)建文件夾,并判斷是否是新創(chuàng)建
file_names = self.get_files(self.folder_path)  #獲取文件家中的所有文件名,類型是list
            if is_new_folder:
                self.save_img(img_url, img_name)  # 調(diào)用save_img方法來保存圖片
            else:
                if img_name not in file_names:
                    self.save_img(img_url, img_name)  # 調(diào)用save_img方法來保存圖片
                else:
                    print("該圖片已經(jīng)存在:", img_name, ",不再重新下載。")

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

from selenium import webdriver  #導(dǎo)入Selenium
import requests
from bs4 import BeautifulSoup  #導(dǎo)入BeautifulSoup 模塊
import os  #導(dǎo)入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'  #要訪問的網(wǎng)頁地址
        self.folder_path = 'C:\D\BeautifulPicture'  #設(shè)置圖片要存放的文件目錄

    def get_pic(self):
        print('開始網(wǎng)頁get請求')
        # 使用selenium通過PhantomJS來進(jìn)行網(wǎng)絡(luò)請求
        driver = webdriver.PhantomJS()
        driver.get(self.web_url)
        self.scroll_down(driver=driver, times=3)  #執(zhí)行網(wǎng)頁下拉到底部操作,執(zhí)行3次
        print('開始獲取所有a標(biāo)簽')
        all_a = BeautifulSoup(driver.page_source, 'lxml').find_all('a', class_='cV68d')  #獲取網(wǎng)頁中的class為cV68d的所有a標(biāo)簽
        print('開始創(chuàng)建文件夾')
        is_new_folder = self.mkdir(self.folder_path)  #創(chuàng)建文件夾,并判斷是否是新創(chuàng)建
        print('開始切換文件夾')
        os.chdir(self.folder_path)   #切換路徑至上面創(chuàng)建的文件夾

        print("a標(biāo)簽的數(shù)量是:", len(all_a))   #這里添加一個查詢圖片標(biāo)簽的數(shù)量,來檢查我們下拉操作是否有誤
        file_names = self.get_files(self.folder_path)  #獲取文件家中的所有文件名,類型是list

        for a in all_a: #循環(huán)每個標(biāo)簽,獲取標(biāo)簽中圖片的url并且進(jìn)行網(wǎng)絡(luò)請求,最后保存圖片
            img_str = a['style'] #a標(biāo)簽中完整的style字符串
            print('a標(biāo)簽的style內(nèi)容是:', img_str)
            first_pos = img_str.index('(') + 1
            second_pos = img_str.index(')')
            img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取雙引號之間的內(nèi)容

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

            #截取url中參數(shù)前面、網(wǎng)址后面的字符串為圖片名
            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)  # 調(diào)用save_img方法來保存圖片
            else:
                if img_name not in file_names:
                    self.save_img(img_url, img_name)  # 調(diào)用save_img方法來保存圖片
                else:
                    print("該圖片已經(jīng)存在:", img_name, ",不再重新下載。")

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

    def request(self, url):  #返回網(wǎng)頁的response
        r = requests.get(url)  # 像目標(biāo)url地址發(fā)送get請求,返回一個response對象。有沒有headers參數(shù)都可以。
        return r

    def mkdir(self, path):  ##這個函數(shù)創(chuàng)建文件夾
        path = path.strip()
        isExists = os.path.exists(path)
        if not isExists:
            print('創(chuàng)建名字叫做', path, '的文件夾')
            os.makedirs(path)
            print('創(chuàng)建成功!')
            return True
        else:
            print(path, '文件夾已經(jīng)存在了,不再創(chuàng)建')
            return False

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

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

beauty = BeautifulPicture()  #創(chuàng)建類的實(shí)例
beauty.get_pic()  #執(zhí)行類中的方法

注釋寫的很詳細(xì),有任何問題可以留言。

四、后語


該實(shí)戰(zhàn)就先到這里,需要注意的是Unsplash 這個網(wǎng)站經(jīng)常不穩(wěn)定,小伙伴有遇到請求異常的情況可以多試幾次。

后面我開始寫一個查找The Beatles 樂隊(duì)的歷年專輯封面圖片和專輯名稱的爬蟲。我的設(shè)計(jì)師小伙伴兒想要收集Beatles 樂隊(duì)歷年的專輯封面圖片,然后做一個該樂隊(duì)的設(shè)計(jì)海報。

至于爬蟲框架的使用,后面我會再專門介紹,最好找到一個好的使用框架的實(shí)戰(zhàn)例子,這樣才能更好的理解框架的作用與優(yōu)點(diǎn)。

OK, See you then.

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

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,699評論 19 139
  • 勵志30年成就3000萬中國人魅力表達(dá),身心富足。每日必推送一篇有關(guān)演講與口才的文章,你我共同挑戰(zhàn)韌性吧,我挑戰(zhàn)寫...
    鉑津閱讀 338評論 0 0
  • DM大貓閱讀 257評論 0 2
  • 細(xì)雨落,潤你我, 曾幾何時, 你我,同堂坐。 日落共回味, 夢囈欣喜把笑說。 樂,樂,樂! 世事多,難捉摸, 昔時...
    火猴閱讀 546評論 4 1

友情鏈接更多精彩內(nèi)容