You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用Selenium在IE中无直接链接指定文件夹下载文件(禁用WinForms等工具)

解决IE浏览器中Selenium下载文件的痛点问题

我太懂你说的这些糟心事了!用Selenium操作IE下载文件确实比Chrome、Firefox麻烦不止一个档次——没法直接指定下载文件夹、还要和原生Windows弹窗死磕,那些AutoIt、Robot之类的方案要么动不动就失效,要么要加一堆等待逻辑、引入冗余库,并行测试更是直接歇菜。还有那种没有直接下载链接、靠JS生成或者服务器动态返回的文件,简直是雪上加霜。不过别慌,这些问题都有靠谱的解决办法,下面我就分享一套基于C#的实现思路,Java也完全可以照着逻辑复刻。


一、彻底绕开下载弹窗:通过注册表预设IE下载路径

其实IE是支持通过注册表配置默认下载目录的,只要提前设置好,下载时就会直接把文件丢到指定路径,完全不用管弹窗。测试完成后再恢复原路径就行,不会影响用户正常使用IE。

using Microsoft.Win32;

// 保存原始下载路径,用于测试后恢复
private string _originalIEDownloadPath;

// 设置IE默认下载路径
public void SetIEDownloadPath(string targetPath)
{
    using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Internet Explorer\Main", true))
    {
        // 先保存原始路径
        _originalIEDownloadPath = key.GetValue("Download Directory")?.ToString() ?? "";
        // 写入新的下载路径
        key.SetValue("Default Download Directory", targetPath, RegistryValueKind.String);
        key.SetValue("Download Directory", targetPath, RegistryValueKind.String);
    }
}

// 测试完成后恢复IE原始下载路径
public void RestoreIEDownloadPath()
{
    if (!string.IsNullOrEmpty(_originalIEDownloadPath))
    {
        using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Internet Explorer\Main", true))
        {
            key.SetValue("Default Download Directory", _originalIEDownloadPath, RegistryValueKind.String);
            key.SetValue("Download Directory", _originalIEDownloadPath, RegistryValueKind.String);
        }
    }
}

二、处理无直接下载链接的场景(JS生成/服务器动态返回)

如果下载是通过页面JS触发,或者链接是服务器动态生成的、没法从HTML里直接提取,有两种靠谱的解决思路:

方法1:直接调用页面的下载逻辑

如果页面本身有现成的JS下载函数(比如点击按钮时调用的initDownload()),直接用Selenium的ExecuteScript方法触发就行,完全不用管前端的交互:

IWebDriver driver = new InternetExplorerDriver();
// 直接执行页面的下载JS函数
driver.ExecuteScript("initDownload();");

方法2:拦截网络请求获取真实下载URL

要是没法直接调用JS函数,就用网络拦截工具(比如FiddlerCore)监听IE的请求,找到下载对应的真实接口URL,然后直接用HttpClient下载到指定路径,比通过浏览器下载更稳定:

using Fiddler;
using System.Net.Http;
using System.IO;

// 初始化FiddlerCore监听
public void StartDownloadInterceptor(string savePath)
{
    FiddlerApplication.BeforeRequest += (session) =>
    {
        // 根据请求特征判断是否是下载请求(比如路径包含download、响应类型是文件)
        if (session.RequestPath.Contains("download") && session.ResponseHeaders.ContentType?.Contains("application/octet-stream") == true)
        {
            string realDownloadUrl = session.fullUrl;
            // 用HttpClient直接下载文件
            using (HttpClient client = new HttpClient())
            {
                var response = client.GetAsync(realDownloadUrl).Result;
                using (var stream = response.Content.ReadAsStreamAsync().Result)
                {
                    string fileName = session.ResponseHeaders.GetValues("Content-Disposition")?
                        .FirstOrDefault()?
                        .Split(';')
                        .Where(s => s.Trim().StartsWith("filename="))
                        .Select(s => s.Trim().Replace("filename=", "").Trim('"'))
                        .FirstOrDefault() ?? "downloaded_file";
                    
                    using (var fileStream = new FileStream(Path.Combine(savePath, fileName), FileMode.Create))
                    {
                        stream.CopyTo(fileStream);
                    }
                }
            }
        }
    };
    FiddlerApplication.Startup(8888, true, true);
}

三、替代AutoIt/Robot:用Windows API处理下载弹窗(万不得已时用)

如果实在绕不开下载弹窗(比如某些老系统的IE版本不支持注册表配置),可以用Windows原生API查找并操作弹窗控件,比AutoIt稳定,还不用额外安装第三方工具:

using System.Runtime.InteropServices;

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

const uint BM_CLICK = 0x00F5;

// 点击IE下载弹窗的"保存"按钮
public void ClickIEDownloadSaveButton()
{
    // 查找IE下载弹窗的句柄(窗口类名是#32770,标题是"文件下载")
    IntPtr dialogHandle = FindWindow("#32770", "文件下载");
    if (dialogHandle != IntPtr.Zero)
    {
        // 查找"保存"按钮的句柄(可以用Windows自带的Spy++工具获取控件文本)
        IntPtr saveButtonHandle = FindWindowEx(dialogHandle, IntPtr.Zero, "Button", "保存(&S)");
        if (saveButtonHandle != IntPtr.Zero)
        {
            // 发送点击消息
            SendMessage(saveButtonHandle, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
        }
    }
}

总的来说,优先用注册表配置下载路径来避免弹窗,遇到无直接链接的情况要么直接触发JS要么拦截网络请求,实在要处理弹窗就用Windows API,这套方案稳定性高,也能支持并行测试,不用那些容易掉链子的第三方工具。

内容的提问来源于stack exchange,提问作者alex

火山引擎 最新活动