????????在上一篇文章中,我們介紹了Selenium的基礎用法和一些常見技巧。今天,我們將通過中國科學:信息科學網站內當前目錄論文下載這一實戰案例來進一步展示Selenium的web自動化流程。
目錄
中國科學:信息科學當期目錄論文下載
1.網頁內容分析
2.下載流程
檢測瀏覽器內文件下載完畢后退出webdriver? ? ? ??
解壓下載的zip文件
完整代碼
中國科學:信息科學當期目錄論文下載
https://www.sciengine.com/SSI/issuehttps://www.sciengine.com/SSI/issue中國科學:信息科學官網。
1.網頁內容分析
????????網頁內容的分析是web自動化中的關鍵一步。通過分析網頁結構,我們可以確定需要抓取的數據位置以及操作元素的方式。
? ? ? ? 與爬蟲不同的是,web自動化通常是對瀏覽器渲染后的html網頁直接進行操作,因此,我們不需要像爬蟲那樣進行抓包分析,只需要在原網頁中定位元素并進行相應的操作即可。
?中國科學信息科學網頁源代碼
? ? ??
?????????觀察網頁源代碼,可以發現,下載時我們主要用到的兩個組件是全選與下載pdf,且這兩個組件均為div元素,具有class_name屬性,那么在代碼中,我們便可以先定位到全選與下載pdf 這兩個div,接著按照順序點擊即可。
2.下載流程
????????先不著急寫代碼,我們先手動操作一番,看一下整個流程是什么樣子。
????????
? ? ? ? 按下全選后,所有文章被選中,且會在下方出現已選中13結果的字樣,表示待下載的文件數量。
????????每個文章的標題都在classname為title的div內的span標簽下,我們分別復制第一個文章與最后一個文章的標題的XPATH:
'//*[@id="journal-list"]/div[1]/div[1]/div[4]/div/div[1]/div/div[2]/div[2]/a/span'
'//*[@id="journal-list"]/div[1]/div[1]/div[4]/div/div[13]/div/div[2]/div[2]/a/span'
??????不難發現,對于第i個文章的標題,其XPATH應該為:
'//*[@id="journal-list"]/div[1]/div[1]/div[4]/div/div[{i}]/div/div[2]/div[2]/a/span'
? ? ?
? 那么,為了功能更完善一些,我們還可以在點擊全選之后按照出現的數量,遍歷查找上述的XPATH下的內容的text屬性,這些text便是所有的論文標題,我們還可以將其寫入到txt中,與下載的論文一起保存到本地。
essay_titles=[]
total_number=browser.find_element(By.XPATH,'//*[@id="selectedArticleNum"]/strong')#定位全選之后彈出的已選中之后的數字total_number=int(total_number.text)#################################查找文章標題for i in range(1,total_number+1):essay_title=browser.find_element(By.XPATH,f'//*[@id="journal-list"]/div[1]/div[1]/div[4]/div/div[{i}]/div/div[2]/div[2]/a/span')essay_titles.append(essay_title.text)#################################將查找到的文章標題寫入到與下載文件同路徑的位置的txt內full_path=os.path.join(self.download_path,'下載論文列表.txt')with open(full_path,'w',encoding='utf-8') as file:for essay_title in self.essay_titles: file.write(essay_title+'\n')
??在按下下載PDF按鈕后,頁面會暫時的跳轉到一個其他url下的空白頁面,過一會兒后,文件開始下載,且下載到本地的格式為zip。
??????
? 按下下載pdf按鈕后頁面變化
檢測瀏覽器內文件下載完畢后退出webdriver? ? ? ??
對于上述兩個流程,倘若我們在代碼中不加任何等待機制:即等待頁面跳轉完畢,文件開始下載至文件下載完畢的等待機制。
即使我們在代碼中沒有寫browser.quit()這樣的命令,webdriver也會自動關閉的。
????????這是因為我們的代碼中涉及到自動化流程的只有點擊全選與下載pdf這兩個按鈕以及查找文章標題,一旦這三個任務完成后,webdriver是會自動關閉的。
? ? ? ? 對于上述問題,最簡單的思路是使用time.sleep()函數,設置足夠多的秒數,保證點擊下載pdf按鈕后,從文件開始下載至文件下載完畢webdriver不會關閉,這里我已經測試過,使用time.sleep(30)足矣。
但是,這樣有點太過于勉強,且不夠優雅,有沒有更好的解決方案呢?
答案是:有的。???????
?????????大家在使用谷歌或者Edge瀏覽器下載文件時,如果在文件下載過程中,中途退出會發現源文件下載取消且中斷,并且在下載文件的位置有一個 文件名.crdownload的文件,這是一個臨時文件,表示文件在瀏覽器下載過程中未完全下載完畢,在下載完畢后文件名后綴中的.crdownload會消失。
????????那么,我們便可以按照下邊的方式來進行等待,這樣等待的好處是只要文件下載完畢,webdriver立即關閉,不會等待多余時長。
import os
import time
def is_download_finished(download_path):files=os.listdir(download_path)for file in files:if file.endswith('crdownload'):#判斷文件夾內是否存在crdownload結尾文件,如果有說明還webdriver內還有文件在下載中return Falsereturn True
while not is_download_finished(download_path):#while循環輪詢time.sleep(1)#這里以1s為單位,若對等待時間要求較高,可以更換為0.1-0.5的小數
webdriver.quit()#關閉先前打開的webdriver
?等待文件下載完畢代碼
????????到這里就萬事大吉了嗎?No,No,No。還記得我們前邊我們說到的,我們在點擊下載pdf后,會有大概5s左右的空閑時間,在這5s內我們會臨時跳轉到一個新的網頁,然后又跳轉回到原來的網頁,文件開始下載嗎?
點擊下載pdf后,跳轉到空白網頁?
大概5s后,返回原網頁,文件開始下載
????????對于上述現象,倘若我們在點擊下載pdf后,直接使用上邊的等待文件下載完畢的代碼的話,由于文件還沒開始下載,文件夾內也根本沒有crdownload結尾的文件,此時代碼中is_download_finishe函數會直接返回True,while循環一次也不執行,webdriver直接就退出了。
????????所以,我們應該等待文件正式下載后再調用上邊的代碼,這里可以使用time.sleep函數,等待幾秒鐘頁面跳轉完畢,當然也可以使用webdriver的current_url屬性,先臨時保存原先網頁url,然后一個while循環判斷webdriver.current_url是否等于原來的網頁的url來進行判斷。為了省事,我們這里就直接使用time.sleep函數進行等待了。
解壓下載的zip文件
解壓已經下載好的zip文件,我們只需要使用python標準庫內置的zipfile模塊即可
import os
import zipfile
def extract_zip_file(download_path):#解壓zip文件filelist=os.listdir(download_path)for file in filelist:if file.endswith('.zip'):zip_file_path=os.path.join(download_path,file)with zipfile.ZipFile(zip_file_path,'r') as zip:zip.extractall(download_path)
運行上述代碼后,給定文件夾下的后綴為zip的文件夾內的內容將被解壓到原路徑下,注意:若你需要解壓指定的zip文件夾,只需要將? if file.endswith('.zip'):更換為if file=='指定的zip文件名':即可。
完整代碼
import os
import time
import zipfile
from selenium import webdriver
from selenium.webdriver.edge.options import Options
from selenium.webdriver.common.by import By
class 中國科學():def __init__(self,download_path:str,headless:bool=False):'''Args:download_path:下載文件保存路徑headless:是否開啟無頭模式'''self.download_path=download_pathself.headless=headlessself.essay_titles=[]def extract_zip_file(self):#解壓zip文件filelist=os.listdir(self.download_path)for file in filelist:if file.endswith('.zip'):zip_file_path=os.path.join(self.download_path,file)with zipfile.ZipFile(zip_file_path,'r') as zip:zip.extractall(self.download_path)def is_download_finished(self):#判斷是否下載完畢files=os.listdir(self.download_path)for file in files:if file.endswith('crdownload'):return Falsereturn Truedef download(self):#下載文件prefs = {'download.default_directory': self.download_path, # 設置默認下載路徑"profile.default_content_setting_values.automatic_downloads": True # 允許多文件下載} self.Options=Options()self.Options.add_argument('--disable-blink-features=AutomationControlled')#隱藏自動化控制self.Options.add_argument('--ignore-ssl-errosr')#忽略ssl錯誤self.Options.add_argument('--ignore-certificate-errors')#忽略證書錯誤self.Options.add_experimental_option("prefs", prefs)self.Options.add_experimental_option('excludeSwitches', ['enable-logging'])self.Options.add_experimental_option('excludeSwitches',['enable-automation'])#隱藏自動化控制if self.headless:#無頭模式運行自動化代碼self.Options.add_argument('--headless')self.Options.add_argument('--disable-gpu')else:passself.browser=webdriver.ChromiumEdge(self.Options)self.browser.maximize_window()#webdriver全屏self.browser.get('https://www.sciengine.com/SSI/issue')self.browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {#執行一段js代碼,隱藏自動化控制"source": """Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"""})select_all=self.browser.find_element(By.CLASS_NAME,'select.borderC2')download_pdf=self.browser.find_element(By.CLASS_NAME,'download.borderC2')self.browser.execute_script('arguments[0].click()',select_all)total_number=self.browser.find_element(By.XPATH,'//*[@id="selectedArticleNum"]/strong')#定位全選之后彈出的已選中之后的數字total_number=int(total_number.text)#################################查找文章標題for i in range(1,total_number+1):essay_title=self.browser.find_element(By.XPATH,f'//*[@id="journal-list"]/div[1]/div[1]/div[4]/div/div[{i}]/div/div[2]/div[2]/a/span')self.essay_titles.append(essay_title.text)#################################將查找到的文章標題寫入到與下載文件同路徑的位置的txt內full_path=os.path.join(self.download_path,'下載論文列表.txt')with open(full_path,'w',encoding='utf-8') as file:for essay_title in self.essay_titles: file.write(essay_title+'\n')##################################點擊下載按鈕,等待下載完畢后退出webdriverself.browser.execute_script('arguments[0].click()',download_pdf)time.sleep(7)#等待7s頁面跳轉完畢while not self.is_download_finished():time.sleep(1)self.browser.quit()self.extract_zip_file()
中國科學(r"E:\OneDrive\Desktop\中國科學信息科學",headless=False).download()
?運行結果