使用Chrome Headless和Selenium进行下载

85
我正在使用python-selenium和Chrome 59尝试自动化一个简单的下载流程。当我正常启动浏览器时,可以下载文件,但是当我以无界面模式运行时,就无法下载。
# Headless implementation
from selenium import webdriver

chromeOptions = webdriver.ChromeOptions()
chromeOptions.add_argument("headless")

driver = webdriver.Chrome(chrome_options=chromeOptions)

driver.get('https://www.mockaroo.com/')
driver.find_element_by_id('download').click()
# ^^^ Download doesn't start

# Normal Mode
from selenium import webdriver

driver = webdriver.Chrome()

driver.get('https://www.mockaroo.com/')
driver.find_element_by_id('download').click()
# ^^^ Download works normally

我甚至尝试添加了一个默认路径:

prefs = {"download.default_directory" : "/Users/Chetan/Desktop/"}
chromeOptions.add_argument("headless")
chromeOptions.add_experimental_option("prefs",prefs)

在正常实现中添加默认路径是可行的,但在无头版本中仍存在同样的问题。

我该如何在无头模式下启动下载?


我也尝试过使用 submit 并发送 Keys.ENTER。它在普通浏览器中可以工作,但在无头浏览器中不行。 - TheChetan
你想要只使用Chrome完成吗?还是Firefox也可以? - Prakash Palnati
更喜欢使用Chrome或PhantomJS。 - TheChetan
为什么不直接使用urllib来下载文件呢?点击文件以模拟下载只适用于某些用户案例。我曾经使用过浏览器,它会在开始下载前打开“另存为”窗口。如果你是点击以查看服务器上是否存在或验证文件内容,那么urllib可能是你最好的选择。 - TehTris
2
@TehTris 问题是,我正在另一个需要我先登录的网站上进行此操作。这会设置头文件和Cookie,因此在使用它之前需要同时设置两者。但是,仅使用js似乎无法从客户端获取请求头... 因此,我无法使用urllib。 - TheChetan
这个讨论对你有帮助吗? - undetected Selenium
12个回答

63

是的,这是一种为了安全而设定的"特性"。如前所述,这里是有关错误讨论的链接:https://bugs.chromium.org/p/chromium/issues/detail?id=696481

chrome版本62.0.3196.0或更高版本已添加支持以启用下载功能。

下面是一个Python实现。我不得不将该命令添加到chromedriver命令中。我将尝试提交PR以便在未来的库中包括它。

def enable_download_in_headless_chrome(self, driver, download_dir):
    # add missing support for chrome "send_command"  to selenium webdriver
    driver.command_executor._commands["send_command"] = ("POST", '/session/$sessionId/chromium/send_command')

    params = {'cmd': 'Page.setDownloadBehavior', 'params': {'behavior': 'allow', 'downloadPath': download_dir}}
    command_result = driver.execute("send_command", params)

供参考,这里有一个小仓库来演示如何使用它: https://github.com/shawnbutton/PythonHeadlessChrome

更新于2020-05-01已有评论指出这不再起作用。鉴于此补丁现在已经有一年多了,很有可能他们已经改变了底层库。


3
我尝试过这个方法,但对我不起作用 :( 当我按照那样做时,我什么也得不到,当我关闭“无头”模式时,我可以得到文件,但是 Chrome 会崩溃。如果我完全删除这个答案中的代码以及“无头”模式,Chrome 就能如预期地工作。我猜测 Chrome 的 API 已经改变了? - bitstream
@bitstream 在我的 Chromium 68.0.3440.75chromedriver 2.38 上运行良好,请查看我的完整示例 - Fayçal
@shawn-button,怎么下载视频呢?貌似HTML5视频在chrome浏览器中默认播放。 - Mostafa
3
这个还有效吗?我尝试了Github上的方法,但无法下载文件。我测试了一下代码,在非无头模式下可以下载文件。打印输出显示:浏览器的响应是: 结果:值:无。 - Henry
这里是一个可行且更新的解决方案。截至11/5/21仍然有效。 - gannagainz
显示剩余2条评论

50

2021年,Chromium开发人员新增了第二个无头模式。详情请见https://bugs.chromium.org/p/chromium/issues/detail?id=706008#c36

随后在2023年Chrome 109中,他们将该选项更名为https://github.com/chromium/chromium/commit/e9c516118e2e1923757ecb13e6d9fff36775d1f4

对于Chrome 109及以上版本,--headless=new标志现在允许您在新的无头模式下获得Chrome的完整功能,甚至可以在其中运行扩展程序(对于Chrome版本96至108,请使用--headless=chrome

使用方法:(适用于Chrome 109及以上版本):

options.add_argument("--headless=new")

用法:(Chrome 96 到 Chrome 108):

options.add_argument("--headless=chrome")

如果某个功能在常规的Chrome浏览器中可用,那么现在使用较新的无头模式也应该可用。


1
我刚刚编辑了我的现有解决方案,因为在2023年Chrome 109中,他们将之前的选项从 --headless=chrome 重命名为 --headless=new - Michael Mintz
2
今天测试了一下,它可以用。 - Rolandas Ulevicius
2
这真是太神奇了 ;) - Sasha Kolsky
3
顺便提一下,这已经正式宣布在Chrome 112中发布:https://developer.chrome.com/articles/new-headless/ 请报告您遇到的任何错误,以便我们修复它们! - Mathias Bynens
2
非常感谢,你救了我的一天。直到108版本,即使是“--headless”对我也起作用。 - Nandan A
"--headless=new" 运行良好。 - Aefits

28

以下是基于Shawn Button的答案的Python工作示例。我已使用Chromium 68.0.3440.75chromedriver 2.38进行测试。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_experimental_option("prefs", {
  "download.default_directory": "/path/to/download/dir",
  "download.prompt_for_download": False,
})

chrome_options.add_argument("--headless")
driver = webdriver.Chrome(chrome_options=chrome_options)

driver.command_executor._commands["send_command"] = ("POST", '/session/$sessionId/chromium/send_command')
params = {'cmd': 'Page.setDownloadBehavior', 'params': {'behavior': 'allow', 'downloadPath': "/path/to/download/dir"}}
command_result = driver.execute("send_command", params)

driver.get('http://download-page.url/')
driver.find_element_by_css_selector("#download_link").click()

请注意,不要将元素目标设置为“_blank”,否则切换选项卡并尝试下载文件将无法正常工作。 - romainm
1
感谢您的发布。我也需要添加chromedriver_location = "/path/to/chromedriver",然后在驱动程序定义中引用它,即driver = webdriver.Chrome(chromedriver_location,options=chrome_options)。 顺便说一句:chrome_options参数很快就会被弃用,并已被options参数所取代,正如我在这里的小例子中所演示的那样。 - gannagainz

18

这是Chrome的一个功能,可以防止软件将文件下载到您的计算机。但是有一个解决方法。在这里阅读更多信息

您需要通过DevTools启用它,像这样:

async function setDownload () {
  const client = await CDP({tab: 'ws://localhost:9222/devtools/browser'});
  const info =  await client.send('Browser.setDownloadBehavior', {behavior : "allow", downloadPath: "/tmp/"});
  await client.close();
}

这是某人在提到的话题中给出的解决方案。 他的评论在这里


5
这个解决方案需要修补Chrome,而不是绕过它。命令Browser.setDownloadBehavior在Chrome v62.0.3186.0中不存在 - Florent B.
我几个月前也遇到了同样的问题。直到今天,多亏一个朋友在评论里给了我指引,让我来到了这里。看到这个答案我感到很高兴,但是我真的不知道如何将这段代码复制或者适配到我的源码中。 - aPugLife
@TheChetan 谢谢!有趣的链接,不过我是在用 Java 开发,chromePrefs.put("Browser.setDownloadBehavior", "allow"); 这个会更有帮助。可惜这个字符串并不是真的可行的。): - aPugLife
7
你想在Python中使用selenium怎么做? - Martin Thoma
@Coded9 目前还没有找到解决办法。我偶尔会看一些资讯,但是还没有人发布有效的方法。我看到了一些“解决方法”,但是没有尝试过,也不是官方方法。 - aPugLife
显示剩余2条评论

9

更新的 Python 解决方案 - 经过在 ChromeDriver v88 和 v89 上的测试(截至2021年3月4日)

这将允许您在无头模式下点击下载文件。

    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.chrome.options import Options

    # Instantiate headless driver
    chrome_options = Options()

    # Windows path
    chromedriver_location = 'C:\\path\\to\\chromedriver_win32\\chromedriver.exe'
    # Mac path. May have to allow chromedriver developer in os system prefs
    '/Users/path/to/chromedriver'

    chrome_options.add_argument("--headless")
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument("--disable-dev-shm-usage")
    
    chrome_prefs = {"download.default_directory": r"C:\path\to\Downloads"} # (windows)
    chrome_options.experimental_options["prefs"] = chrome_prefs

    driver = webdriver.Chrome(chromedriver_location,options=chrome_options)

    # Download your file
    driver.get('https://www.mockaroo.com/')
    driver.find_element_by_id('download').click()

这应该是被接受的答案,只需将下载路径添加到chrome_prefs作为experimental_options并解决问题,谢谢! - Darklord5

4

在我获取页面后,执行 driver.get_screenshot_as_file('foo.png'),我得到了一个实际内容的图像,看起来不错。此外,驱动程序能够找到按钮。正在调查中。 - Jugurtha Hadjar

1

使用selenium-cucumber-js / selenium-webdriver的JavaScript的完整工作示例:

const chromedriver = require('chromedriver');
const selenium = require('selenium-webdriver');
const command = require('selenium-webdriver/lib/command');
const chrome = require('selenium-webdriver/chrome');

module.exports = function() {

  const chromeOptions = new chrome.Options()
    .addArguments('--no-sandbox', '--headless', '--start-maximized', '--ignore-certificate-errors')
    .setUserPreferences({
      'profile.default_content_settings.popups': 0, // disable download file dialog
      'download.default_directory': '/tmp/downloads', // default file download location
      "download.prompt_for_download": false,
      'download.directory_upgrade': true,
      'safebrowsing.enabled': false,
      'plugins.always_open_pdf_externally': true,
      'plugins.plugins_disabled': ["Chrome PDF Viewer"]
    })
    .windowSize({width: 1600, height: 1200});

  const driver = new selenium.Builder()
    .withCapabilities({
      browserName: 'chrome',
      javascriptEnabled: true,
      acceptSslCerts: true,
      path: chromedriver.path
    })
    .setChromeOptions(chromeOptions)
    .build();

  driver.manage().window().maximize();

  driver.getSession()
    .then(session => {
      const cmd = new command.Command("SEND_COMMAND")
        .setParameter("cmd", "Page.setDownloadBehavior")
        .setParameter("params", {'behavior': 'allow', 'downloadPath': '/tmp/downloads'});
      driver.getExecutor().defineCommand("SEND_COMMAND", "POST", `/session/${session.getId()}/chromium/send_command`);
      return driver.execute(cmd);
    });

  return driver;
};

关键部分是:

  driver.getSession()
    .then(session => {
      const cmd = new command.Command("SEND_COMMAND")
        .setParameter("cmd", "Page.setDownloadBehavior")
        .setParameter("params", {'behavior': 'allow', 'downloadPath': '/tmp/downloads'});
      driver.getExecutor().defineCommand("SEND_COMMAND", "POST", `/session/${session.getId()}/chromium/send_command`);
      return driver.execute(cmd);
    });

测试环境:

  • Chrome 67.0.3396.99
  • Chromedriver 2.36.540469
  • selenium-cucumber-js 1.5.12
  • selenium-webdriver 3.0.0

感谢您发布JavaScript解决方案。执行该命令并不完全明显。 - justspamjustin

1

通常情况下,用另一种语言写同样的东西是多余的,但由于这个问题让我发疯,所以我希望能够帮助其他人避免这种痛苦... 这里是C#版本的Shawn Button的答案(在无头chrome=71.0.3578.98、chromedriver=2.45.615279、平台=Linux 4.9.125-linuxkit x86_64上进行了测试):

            var enableDownloadCommandParameters = new Dictionary<string, object>
            {
                { "behavior", "allow" },
                { "downloadPath", downloadDirectoryPath }
            };
            var result = ((OpenQA.Selenium.Chrome.ChromeDriver)driver).ExecuteChromeCommandWithResult("Page.setDownloadBehavior", enableDownloadCommandParameters);

0

我通过使用@Shawn Button分享的解决方法,并在“downloadPath”参数中使用完整路径来解决了这个问题。使用相对路径无法工作并给出错误。

版本:
Chrome版本75.0.3770.100(官方版本)(32位)
ChromeDriver 75.0.3770.90


2
请提供您的Chrome浏览器和Chrome驱动程序版本。每个版本都会进行重大更改,因此解决方法可能会变得无用。例如:https://bugs.chromium.org/p/chromium/issues/detail?id=696481#c198 - Ferhat

0
以下是Java、Selenium、Chromedriver和Chrome v 71.x的等效代码。其中,com.fasterxml.jackson.corecom.fasterxml.jackson.annotationcom.fasterxml.jackson.databind是额外的JAR包。在代码中,关键是允许下载保存。

System.setProperty("webdriver.chrome.driver","C:\libraries\chromedriver.exe");

            String downloadFilepath = "C:\\Download";
            HashMap<String, Object> chromePreferences = new HashMap<String, Object>();
            chromePreferences.put("profile.default_content_settings.popups", 0);
            chromePreferences.put("download.prompt_for_download", "false");
            chromePreferences.put("download.default_directory", downloadFilepath);
            ChromeOptions chromeOptions = new ChromeOptions();
            chromeOptions.setBinary("C:\\pathto\\Chrome SxS\\Application\\chrome.exe");

            //ChromeOptions options = new ChromeOptions();
            //chromeOptions.setExperimentalOption("prefs", chromePreferences);
            chromeOptions.addArguments("start-maximized");
            chromeOptions.addArguments("disable-infobars");


            //HEADLESS CHROME
            **chromeOptions.addArguments("headless");**

            chromeOptions.setExperimentalOption("prefs", chromePreferences);
            DesiredCapabilities cap = DesiredCapabilities.chrome();
            cap.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
            cap.setCapability(ChromeOptions.CAPABILITY, chromeOptions);

            **ChromeDriverService driverService = ChromeDriverService.createDefaultService();
            ChromeDriver driver = new ChromeDriver(driverService, chromeOptions);

            Map<String, Object> commandParams = new HashMap<>();
            commandParams.put("cmd", "Page.setDownloadBehavior");
            Map<String, String> params = new HashMap<>();
            params.put("behavior", "allow");
            params.put("downloadPath", downloadFilepath);
            commandParams.put("params", params);
            ObjectMapper objectMapper = new ObjectMapper();
            HttpClient httpClient = HttpClientBuilder.create().build();
            String command = objectMapper.writeValueAsString(commandParams);
            String u = driverService.getUrl().toString() + "/session/" + driver.getSessionId() + "/chromium/send_command";
            HttpPost request = new HttpPost(u);
            request.addHeader("content-type", "application/json");
            request.setEntity(new StringEntity(command));**
            try {
                httpClient.execute(request);
            } catch (IOException e2) {
                // TODO Auto-generated catch block
                e2.printStackTrace();
            }**

        //Continue using the driver for automation  
    driver.manage().window().maximize();

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