【问题标题】:Selenium (Python) - waiting for a download process to complete using Chrome web driverSelenium (Python) - 使用 Chrome 网络驱动程序等待下载过程完成
【发布时间】:2018-06-24 02:28:45
【问题描述】:

我通过 chromewebdriver (windows) 使用 selenium 和 python 来自动执行从不同页面下载大量文件的任务。 我的代码有效,但解决方案远非理想:下面的函数单击网站按钮,启动生成 PDF 文件然后下载的 java 脚本函数。

我不得不使用静态等待来等待下载完成(丑陋)我无法检查文件系统以验证下载何时完成,因为我正在使用多线程(下载大量文件一次从不同的页面),并且文件的名称是在网站本身中动态生成的。

我的代码:

def file_download(num, drivervar):
Counter += 1
    try:
        drivervar.get(url[num])
        download_button = WebDriverWait(drivervar, 20).until(EC.element_to_be_clickable((By.ID, 'download button ID')))
        download_button.click()
        time.sleep(10) 
    except TimeoutException: # Retry once
        print('Timeout in thread number: ' + str(num) + ', retrying...')
..... 

是否可以在 webdriver 中确定下载完成?我想避免使用 time.sleep(x)。

非常感谢。

【问题讨论】:

  • 如果 UI 中没有任何内容表示完成,那么除了检查文件系统之外没有其他方法可以判断。为什么你不能这样做?
  • 我以前从未尝试过,但是您可以将下载路径设置为某个特定值吗?例如每次运行创建一个带时间戳的文件夹,然后将下载路径指向该文件夹?这样,您每个文件夹只会获得一个文件,并且能够确定下载何时完成。我的理解是,在实例化驱动程序后,您无法更改下载路径,因此如果您尝试这种方法,请记住这一点。您可以编写另一个脚本,在第一个脚本完成后进行清理,例如获取所有文件并将它们放入单个文件夹并删除所有子文件夹。

标签: python-3.x selenium selenium-chromedriver


【解决方案1】:

您可以通过驱动访问chrome://downloads/获取每次下载的状态。

等待所有下载完成并列出所有路径:

def every_downloads_chrome(driver):
    if not driver.current_url.startswith("chrome://downloads"):
        driver.get("chrome://downloads/")
    return driver.execute_script("""
        var items = document.querySelector('downloads-manager')
            .shadowRoot.getElementById('downloadsList').items;
        if (items.every(e => e.state === "COMPLETE"))
            return items.map(e => e.fileUrl || e.file_url);
        """)


# waits for all the files to be completed and returns the paths
paths = WebDriverWait(driver, 120, 1).until(every_downloads_chrome)
print(paths)

已更新以支持版本 81 之前的更改。

【讨论】:

  • 好主意,我会试试看。谢谢!
  • 惊人的想法(+1)
  • developers.chrome.com/extensions/downloads 这是api文档,文档说“文件名”来获取下载的文件名,但是,根据我在python中的测试,它区分大小写,应该是“文件名”和“文件路径”。
  • 以上代码到今天为止运行良好,然后失败了。我必须将 file_url 更改为 fileUrl 才能让它再次工作。
  • paths 返回下载文件路径的列表。要获取下载的文件路径,请使用paths[0]
【解决方案2】:

我遇到了同样的问题并找到了解决方案。您可以检查 .crdownload 是否在您的下载文件夹中。如果下载文件夹中有 0 个扩展名为 .crdownload 的文件,则您的所有下载都已完成。我认为这只适用于铬和铬。

def downloads_done():
    while True:
        for filename in os.listdir("/downloads"):
            if ".crdownload" in i:
                time.sleep(0.5)
                downloads_done()

每当您调用 downloads_done() 时,它都会自行循环,直到所有下载完成。如果您要下载 80 GB 这样的海量文件,那么我不推荐这样做,因为这样该函数可以达到最大递归深度。

2020 年编辑:

def wait_for_downloads():
    print("Waiting for downloads", end="")
    while any([filename.endswith(".crdownload") for filename in 
               os.listdir("/downloads")]):
        time.sleep(2)
        print(".", end="")
    print("done!")

print() 中的“end”关键字参数通常包含一个换行符,但我们替换它。 虽然 /downloads 文件夹中没有以 .crdownload 结尾的文件名 休眠 2 秒并在控制台打印一个不带换行符的点

在发现请求后,我真的不建议再使用 selenium,但如果它是一个带有 cloudflare 和验证码等的戒备森严的网站,那么你可能不得不求助于 selenium。

【讨论】:

  • 很好,回答,我不得不稍微修改一下以包含 .tmp 文件 - 而 any([filename.endswith(".crdownload") 或 filename.endswith(".tmp") os.listdir(default_download_directory)]) 中的文件名:
  • 添加完整的下载路径,即C:\\Users\\Dev\\Downloads\\如果你得到系统找不到指定的路径:
【解决方案3】:

使用 Chrome 80,我不得不通过以下代码更改 @florent-b 的答案:

def every_downloads_chrome(driver):
    if not driver.current_url.startswith("chrome://downloads"):
        driver.get("chrome://downloads/")
    return driver.execute_script("""
        return document.querySelector('downloads-manager')
        .shadowRoot.querySelector('#downloadsList')
        .items.filter(e => e.state === 'COMPLETE')
        .map(e => e.filePath || e.file_path || e.fileUrl || e.file_url);
        """)

我相信这是复古兼容的,我的意思是这将适用于旧版本的 Chrome。

【讨论】:

  • 这在最新的 chrome 版本中运行良好,但是当 chrome 在无头模式下运行时它会失败。你有什么想法吗?
  • 我找到原因了:MacOS不能在headless模式下运行stackoverflow.com/a/57606294/9779432
【解决方案4】:

在无头模式下运行 Chrome 时打开 chrome://downloads/ 存在问题。

以下函数使用复合方法,无论模式是否为无头模式,都可以使用,选择每种模式中可用的更好方法。

假定调用者在每次调用此函数后清除在file_download_path 下载的所有文件。

import os
import logging
from selenium.webdriver.support.ui import WebDriverWait

def wait_for_downloads(driver, file_download_path, headless=False, num_files=1):
    max_delay = 60
    interval_delay = 0.5
    if headless:
        total_delay = 0
        done = False
        while not done and total_delay < max_delay:
            files = os.listdir(file_download_path)
            # Remove system files if present: Mac adds the .DS_Store file
            if '.DS_Store' in files:
                files.remove('.DS_Store')
            if len(files) == num_files and not [f for f in files if f.endswith('.crdownload')]:
                done = True
            else:
                total_delay += interval_delay
                time.sleep(interval_delay)
        if not done:
            logging.error("File(s) couldn't be downloaded")
    else:
        def all_downloads_completed(driver, num_files):
            return driver.execute_script("""
                var items = document.querySelector('downloads-manager').shadowRoot.querySelector('#downloadsList').items;
                var i;
                var done = false;
                var count = 0;
                for (i = 0; i < items.length; i++) {
                    if (items[i].state === 'COMPLETE') {count++;}
                }
                if (count === %d) {done = true;}
                return done;
                """ % (num_files))

        driver.execute_script("window.open();")
        driver.switch_to_window(driver.window_handles[1])
        driver.get('chrome://downloads/')
        # Wait for downloads to complete
        WebDriverWait(driver, max_delay, interval_delay).until(lambda d: all_downloads_completed(d, num_files))
        # Clear all downloads from chrome://downloads/
        driver.execute_script("""
            document.querySelector('downloads-manager').shadowRoot
            .querySelector('#toolbar').shadowRoot
            .querySelector('#moreActionsMenu')
            .querySelector('button.clear-all').click()
            """)
        driver.close()
        driver.switch_to_window(driver.window_handles[0])

【讨论】:

    【解决方案5】:
    import os
    from selenium import webdriver
    from selenium.webdriver.support.wait import WebDriverWait
    
    class MySeleniumTests(unittest.TestCase):
    
        selenium = None
    
        @classmethod
        def setUpClass(cls):
            cls.selenium = webdriver.Firefox(...)
    
        ...
    
        def test_download(self):
            os.chdir(self.download_path) # default download directory
    
            # click the button
            self.selenium.get(...)
            self.selenium.find_element_by_xpath(...).click()
    
            # waiting server for finishing inner task
            def download_begin(driver):
                if len(os.listdir()) == 0:
                    time.sleep(0.5)
                    return False
                else:
                    return True
            WebDriverWait(self.selenium, 120).until(download_begin) # the max wating time is 120s
    
            # waiting server for finishing sending.
            # if size of directory is changing,wait
            def download_complete(driver):
                sum_before=-1
                sum_after=sum([os.stat(file).st_size for file in os.listdir()])
                while sum_before != sum_after:
                    time.sleep(0.2)
                    sum_before = sum_after
                    sum_after = sum([os.stat(file).st_size for file in os.listdir()])
                return True
            WebDriverWait(self.selenium, 120).until(download_complete)  # the max wating time is 120s
    

    你必须做这些事情

    1. 等待服务器完成内部业务(例如从数据库查询)。
    2. 等待服务器完成文件发送。

    (我的英文不太好)

    【讨论】:

      【解决方案6】:

      为了获得多于一件的退货,我不得不通过下面的代码更改@thdox的答案:

      def every_downloads_chrome(driver):
          if not driver.current_url.startswith("chrome://downloads"):
              driver.get("chrome://downloads/")
          return driver.execute_script("""
              var elements = document.querySelector('downloads-manager')
              .shadowRoot.querySelector('#downloadsList')
              .items
              if (elements.every(e => e.state === 'COMPLETE'))
              return elements.map(e => e.filePath || e.file_path || e.fileUrl || e.file_url);
              """)
      

      【讨论】:

        【解决方案7】:

        这可能不适用于所有用例,但对于我需要等待一个 pdf 下载的简单需求,它的效果很好。基于Walter's comment above

        def get_non_temp_len(download_dir):
            non_temp_files = [i for i in os.listdir(download_dir) if not (i.endswith('.tmp') or i.endswith('.crdownload'))]
            return len(non_temp_files)
        
        download_dir = 'your/download/dir'
        original_count = get_non_temp_len(download_dir) # get the file count at the start
        
        # do your selenium stuff 
        
        while original_count == get_non_temp_len(download_dir):
            time.sleep(.5) # wait for file count to change
            
        driver.quit()
        

        【讨论】:

          【解决方案8】:

          我遇到了同样的问题,这个方法对我有用。

          import time
          from selenium import webdriver
          from selenium.webdriver.common.keys import Keys
          from selenium.common.exceptions import ElementClickInterceptedException
          from threading import Thread
          import os
          import datetime
          def checkFilePresence(downloadPath, numberOfFilesInitially, artistName, 
              songTitle):
              timeNow = datetime.datetime.now()
              found = False
              while not found:
                  numberOfFilesNow = len(os.listdir(downloadPath))
                  if numberOfFilesNow > numberOfFilesInitially:
                      for folders, subfolders, files in os.walk(downloadPath):
                          for file in files:
                              modificationTime = datetime.datetime.fromtimestamp\
                              (os.path.getctime(os.path.join(folders, file)))
                              if modificationTime > timeNow:
                                  if file.endswith('.mp3'):
                                      return
          

          【讨论】:

            【解决方案9】:

            此代码在无头模式下工作并返回下载的文件名(基于 @protonum 代码):

            def wait_for_downloads(download_path):
                max_delay = 30
                interval_delay = 0.5
                total_delay = 0
                file = ''
                done = False
                while not done and total_delay < max_delay:
                    files = [f for f in os.listdir(download_path) if f.endswith('.crdownload')]
                    if not files and len(file) > 1:
                        done = True
                    if files:
                        file = files[0]
                    time.sleep(interval_delay)
                    total_delay += interval_delay
                if not done:
                    logging.error("File(s) couldn't be downloaded")
                return download_path + '/' + file.replace(".crdownload", "")
            

            【讨论】:

              【解决方案10】:
              def wait_for_download_to_be_don(self, path_to_folder, file_name):
                  max_time = 60
                  counter = 0
                  while not os.path.exists(path_to_folder + file_name) and time_counter < max_time:
                      sleep(0.5)
                      time_counter += 0.5
                      if time_counter == max_time:
                          assert os.path.exists(path_to_folder + file_name), "The file wasn't downloaded"
              

              【讨论】:

                【解决方案11】:

                在使用测试自动化时,开发人员使软件可测试至关重要。结合可测试性检查软件是您的工作,这意味着您需要请求一个微调器或一个简单的 HTML 标记,以指示下载何时成功完成。

                在你这种情况下,无法在 UI 中查看,也无法在系统中查看,这是最好的解决方法。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-10-20
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多