在上一篇文章中,我们介绍了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 False
return 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_path
self.headless=headless
self.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 False
return True
def 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:
pass
self.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')
#################################
#点击下载按钮,等待下载完毕后退出webdriver
self.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()
运行结果