前文介紹了PhatomJS 和Selenium 的用法,工具準備完畢,咱們來看看如何使用它們來改造咱們以前寫的小爬蟲。git
咱們的目的是模擬頁面下拉到底部,而後頁面會刷出新的內容,每次會加載10張新圖片。github
大致思路是,用Selenium + PhatomJS 來請求網頁,頁面加載後模擬下拉操做,能夠根據想要獲取的圖片多少來選擇下拉的次數,而後再獲取網頁中的所有內容。web
個人運行環境以下:chrome
系統版本
Windows10。數據庫
Python版本
Python3.5,推薦使用Anaconda 這個科學計算版本,主要是由於它自帶一個包管理工具,能夠解決有些包安裝錯誤的問題。去Anaconda官網,選擇Python3.5版本,而後下載安裝。瀏覽器
IDE
我使用的是PyCharm,是專門爲Python開發的IDE。這是JetBrians的產品,點我下載。網絡
要想實現網頁的下拉操做,須要使用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就沒什麼問題了,程序能夠順利的執行。
可是,細心的小夥伴兒確定發現了,咱們這個爬蟲在爬取的過程當中中斷了,或者過一段時間網站圖片更新了,我再想爬,有好多圖片都是重複的,卻又從新爬取一遍,浪費時間。那麼咱們有什麼辦法來達到去重的效果呢?
想要作去重,無非就是在爬取圖片的時候記錄圖片名字(圖片名字是惟一的)。在爬取圖片前,先檢查該圖片是否已經爬取過,若是沒有,則繼續爬取,若是已經爬取過,則不進行爬取。
去重的邏輯如上,實現方式的不一樣主要體如今記錄已經爬取過的信息的途徑不一樣。好比可使用數據庫記錄、使用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() #執行類中的方法
註釋寫的很詳細,有任何問題能夠留言。
該實戰就先到這裏,須要注意的是Unsplash 這個網站常常不穩定,小夥伴有遇到請求異常的狀況能夠多試幾回。
後面我開始寫一個查找The Beatles 樂隊的歷年專輯封面圖片和專輯名稱的爬蟲。個人設計師小夥伴兒想要收集Beatles 樂隊歷年的專輯封面圖片,而後作一個該樂隊的設計海報。
至於爬蟲框架的使用,後面我會再專門介紹,最好找到一個好的使用框架的實戰例子,這樣才能更好的理解框架的做用與優勢。
OK, See you then.