如何使用selenium在点击事件中下载文件?

62

我正在使用Python和Selenium工作。我想使用Selenium从点击事件中下载文件。我编写了以下代码。

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.keys import Keys

browser = webdriver.Firefox()
browser.get("http://www.drugcite.com/?q=ACTIMMUNE")

browser.close()

我希望从给定的URL下载名为“导出数据”的链接中的两个文件。因为这些链接只能通过点击事件才能起作用,所以我该如何实现呢?


2
我建议使用 urllib 并使用 urllib.urlretrieve(url) 来获取下载,其中 url 是链接发送到的网址。 - Serial
1
不行,因为它只适用于点击事件。 - sam
但是如果你解析页面的HTML,就可以获取点击事件发送到浏览器的链接,并使用它。 - Serial
1
哦,算了,现在看着页面,你是对的,我的错。 - Serial
5个回答

79

使用find_element(s)_by_*找到链接,然后调用click方法。

from selenium import webdriver

# To prevent download dialog
profile = webdriver.FirefoxProfile()
profile.set_preference('browser.download.folderList', 2) # custom location
profile.set_preference('browser.download.manager.showWhenStarting', False)
profile.set_preference('browser.download.dir', '/tmp')
profile.set_preference('browser.helperApps.neverAsk.saveToDisk', 'text/csv')

browser = webdriver.Firefox(profile)
browser.get("http://www.drugcite.com/?q=ACTIMMUNE")

browser.find_element_by_id('exportpt').click()
browser.find_element_by_id('exporthlgt').click()

添加了个人资料操作代码以防止下载对话框的出现。


2
@sam,搜索headless + selenium + firefox - falsetru
1
@sam 你可以使用PyVirtualDisplay来运行Firefox无头模式。这对我很有效。 - Iqbal
1
仍然出现下载对话框。 - Iqbal
2
没关系,我找到了答案。如果有其他人需要的话,在这里:https://dev59.com/y18e5IYBdhLWcg3w6NuW - seth127
1
我仍然收到对话框,我在这里找到了解决方案:https://www.reddit.com/r/learnpython/comments/42hg1c/comment/czaiain/?utm_source=share&utm_medium=web2x&context=3 - neurocker
显示剩余12条评论

19
I'll承认,这种解决方案比Firefox Profile saveToDisk替代方案要“hacky”一些,但它适用于Chrome和Firefox,并且不依赖于可能随时更改的特定于浏览器的功能。如果没有其他办法,也许这会给某人在解决未来挑战时提供一些不同的视角。
先决条件:确保您已安装selenium和pyvirtualdisplay...
Python 2:sudo pip install selenium pyvirtualdisplay Python 3:sudo pip3 install selenium pyvirtualdisplay 魔术
import pyvirtualdisplay
import selenium
import selenium.webdriver
import time
import base64
import json

root_url = 'https://www.google.com'
download_url = 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png'

print('Opening virtual display')
display = pyvirtualdisplay.Display(visible=0, size=(1280, 1024,))
display.start()
print('\tDone')

print('Opening web browser')
driver = selenium.webdriver.Firefox()
#driver = selenium.webdriver.Chrome() # Alternately, give Chrome a try
print('\tDone')

print('Retrieving initial web page')
driver.get(root_url)
print('\tDone')

print('Injecting retrieval code into web page')
driver.execute_script("""
    window.file_contents = null;
    var xhr = new XMLHttpRequest();
    xhr.responseType = 'blob';
    xhr.onload = function() {
        var reader  = new FileReader();
        reader.onloadend = function() {
            window.file_contents = reader.result;
        };
        reader.readAsDataURL(xhr.response);
    };
    xhr.open('GET', %(download_url)s);
    xhr.send();
""".replace('\r\n', ' ').replace('\r', ' ').replace('\n', ' ') % {
    'download_url': json.dumps(download_url),
})

print('Looping until file is retrieved')
downloaded_file = None
while downloaded_file is None:
    # Returns the file retrieved base64 encoded (perfect for downloading binary)
    downloaded_file = driver.execute_script('return (window.file_contents !== null ? window.file_contents.split(\',\')[1] : null);')
    print(downloaded_file)
    if not downloaded_file:
        print('\tNot downloaded, waiting...')
        time.sleep(0.5)
print('\tDone')

print('Writing file to disk')
fp = open('google-logo.png', 'wb')
fp.write(base64.b64decode(downloaded_file))
fp.close()
print('\tDone')
driver.close() # close web browser, or it'll persist after python exits.
display.popen.kill() # close virtual display, or it'll persist after python exits.

解释

首先,我们在目标域名上加载一个URL以从中下载文件。这样我们就可以在该域名上执行AJAX请求,而不会遇到跨站脚本问题。

接下来,我们将一些JavaScript注入到DOM中,触发一个AJAX请求。一旦AJAX请求返回响应,我们就将响应加载到一个FileReader对象中。从那里,我们可以通过调用readAsDataUrl()提取文件的base64编码内容。然后,我们将base64编码内容附加到全局可访问变量window上。

最后,由于AJAX请求是异步的,我们进入一个Python while循环,等待内容附加到窗口。一旦附加完成,我们解码从窗口检索到的base64内容并将其保存到文件中。

这个解决方案应该适用于Selenium支持的所有现代浏览器,并且无论是文本还是二进制文件,以及所有MIME类型都可以使用。

备选方法

尽管我还没有测试过,但Selenium确实可以让您等待直到DOM中出现元素。您可以在DOM中创建一个具有特定ID的元素,并使用该元素的绑定作为检索已下载文件的触发器,而不是循环直到全局可访问变量被填充。

我有一个下载按钮用于下载PDF文件,但是它被验证码保护,因此与会话绑定。我拥有的download_url不是指向.pdf文件,而是指向一个JavaScript页面,其中包含一个$(document).ready(function () {,该函数调用了一个$.post()来获取实际的PDF文件。当我使用您的解决方案时,我最终下载的是HTML文件,而不是我想要下载的PDF文件。在这种情况下,我该如何进行调整? - OscarVanL

4

在Chrome中,我会通过点击链接来下载文件,然后打开chrome://downloads页面,然后像这样从shadow DOM中检索已下载文件的列表:

docs = document
  .querySelector('downloads-manager')
  .shadowRoot.querySelector('#downloads-list')
  .getElementsByTagName('downloads-item')

这个解决方案仅适用于Chrome,数据还包括文件路径和下载日期。(注意这段代码是来自JS,可能不是正确的Python语法)


2
请注意问题标签。这是一个Python问题,不是JS! - smart-developer

1
这里是完整的可工作代码。您可以使用网络爬虫来输入用户名、密码和其他字段。要获取网页上出现的字段名称,请使用检查元素。元素名称(用户名、密码或单击按钮)可以通过类或名称输入。
from selenium import webdriver
# Using Chrome to access web
options = webdriver.ChromeOptions() 
options.add_argument("download.default_directory=C:/Test") # Set the download Path
driver = webdriver.Chrome(options=options)
# Open the website
try:
    driver.get('xxxx') # Your Website Address
    password_box = driver.find_element_by_name('password')
    password_box.send_keys('xxxx') #Password
    download_button = driver.find_element_by_class_name('link_w_pass')
    download_button.click()
    driver.quit()
except:
    driver.quit()
    print("Faulty URL")

0
最简单的方法是使用add_experimental_option,特别是在使用远程webdriver时,对于Chrome浏览器。
chrome_options = webdriver.ChromeOptions()
preferences = {"download.default_directory" : "/some/path"}
#be sure to add preferences as an experimental option
chrome_options.add_experimental_option("prefs", preferences) 

driver = webdriver.Remote(
  command_executor="http://localhost:4444",
  options=chrome_options
)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接