65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用 Selenium Webdriver 自动将网页保存为单个 .MHTML 文件

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2023年9月25日

CPOL

3分钟阅读

viewsIcon

5870

使用 Selenium Webdriver 将网页保存为单个自包含文件

引言

EdgeSinglePageDownloader 是一个简单的控制台应用程序,它使用 Edge Selenium Webdriver 下载一组网页并将它们保存为指定文件夹中的单个 .MHTML 文件(它也应该适用于 ChromeDriver)。这是一个简单的概念验证,展示了如何使用 Selenium Webdriver 实现此功能,并允许批量下载和保存一组网页。

背景

Edge Selenium Webdriver 是一个 NuGet 包,允许您通过模拟用户交互来自动化 Microsoft Edge。通过向 Edge 发送 CTRL + S 键来保存浏览的网页,以弹出“另存为”对话框,然后指定文件名,选择文件格式(网页,单个文件 .mhtml)并单击“保存”按钮。不幸的是,Selenium Webdriver 提供的 SendKeys 方法不起作用(至少我在 Windows 上无法使其工作),因此在多次尝试后,我切换到使用 VBScript SendKeys 方法,该方法可以完美运行,但有一个缺点,即需要 Windows 操作系统。

Using the Code

使用代码非常简单,您只需要

  • 调整 DefaultSaveFolder 常量以指定保存 mhtml 文件的本地文件夹,默认值为 C:\temp
    const string DefaultSaveFolder = "c:\\temp";
  • 调整 urlsToSave 变量的初始化,其中包含您想要保存的 url,默认提供 The Verge 和 Wired 的 url
    var urlsToSave = new List<string> 
                     { "https://www.theverge.com", "https://www.wired.com/" };

之后,只需运行代码,Edge 将启动,所有 urlsToSave 将按顺序浏览,并保存在 DefaultSaveFolder 中,文件名为 Page_1.mhtmlPage_2.mhtml、...、Page_n.mhtml

代码非常简单,全部包含在 Main 函数和 SaveAsSingleFile 辅助函数中。

Main 函数执行以下步骤

  • 循环遍历 urlsToSave 变量中的所有 url,并为每个 url
    • 实例化一个 EdgeDriver 类(由 Selenium Webdriver 提供),该类启动一个新的 Edge 浏览器
    • 通过调用 EdgeDriver.Navigate().GoToUrl 使浏览器导航到 url
    • 通过调用辅助函数 SaveAsSingleFile 将网页保存为单个 .mhtml 文件
static void Main(string[] args)
{
    var options = new EdgeOptions();

    var service = EdgeDriverService.CreateDefaultService();
    service.EnableVerboseLogging = true;

    WshShell = new WshShellClass();

    var urlsToSave = new List<string> 
        { "https://www.theverge.com", "https://www.wired.com/" };

    var i = 1;
    foreach (var url in urlsToSave)
    {
        Driver = new EdgeDriver(service, options);

        Driver.Navigate().GoToUrl(url);

        SaveAsSingleFile(Path.Combine(DefaultSaveFolder, 
                         $"Page_{i++}.mhtml"),url);

        Driver.Close();
            }
    }

SaveAsSingleFile 辅助函数执行以下步骤

  • 检查输出目录是否存在,如果不存在则创建它
  • 检查输出文件是否存在且格式为 .mhtml,如果存在,则直接退出而不重新保存,否则删除现有文件
  • 使用 WshShell.SendKeys 向 Edge 浏览器发送 CTRL(^ 字符)+ S 键,这将弹出“另存为”对话框(下图)。请注意,“文件名”和“保存类型”文本框的标签分别有一个带下划线的字符 'n' 和 't',您可以通过按 ALT + 这些字符之一来聚焦它们的控件。请注意,这些快捷方式取决于本地化(我使用的是英文本地化 Windows),在您的 Windows 安装中,它们可能会有所不同,因此请根据需要进行调整。
  • 发送 ALT(% 字符)+ 'n' 和传递给函数的文件名
  • 发送 ALT(% 字符)+ 't',向下箭头以打开所有“保存类型”的可能格式,向上箭头以选择“网页,单个文件 (*.mhtml)”并按两次 ENTER(~ 字符)以确认文件格式并按“保存”按钮。
  • 之后,它会等待 1 分钟(由 MaxWaitForSaveMSec 常量指定)来检查已创建的已保存文件是否为 MHTML 格式(它会检查文件是否包含 string "Snapshot-Content-Location: {url}"),如果不是,它会返回函数开头重新执行所有操作。

static void SaveAsSingleFile(string filename, string url)
{
again:
    if (!Directory.Exists(Path.GetDirectoryName(filename)))
        Directory.CreateDirectory(Path.GetDirectoryName(filename));
        
    if (System.IO.File.Exists(filename))
    {
        // simple check that the existing file format is mhtml, 
        // otherwise delete and re-save it
        if (!System.IO.File.ReadAllText(filename).Contains
                            ($"Snapshot-Content-Location: {url}"))
            System.IO.File.Delete(filename);
        else
            return;
    }
    
    WshShell.SendKeys("^s");
    Thread.Sleep(1000);
    // send alt+n, enter filename
    WshShell.SendKeys($"%n{filename}");
    Thread.Sleep(20);
    // send alt+t, down arrow, up arrow (to select single mhtml), press enter twice
    WshShell.SendKeys($"%t");
    Thread.Sleep(20);
    WshShell.SendKeys($"{{DOWN}}");
    Thread.Sleep(20);
    WshShell.SendKeys($"{{UP}}");
    Thread.Sleep(20);
    WshShell.SendKeys($"~~");
    
    // waits up to MaxWaitForSaveMSec to check that the file is saved correctly
    var endtime = DateTime.Now.AddMilliseconds(MaxWaitForSaveMSec);
    
    while (DateTime.Now < endtime)
    {
        Thread.Sleep(1000);
        // simple check that the file is present and its format is mhtml, 
        // otherwise retry again to save
        if (System.IO.File.Exists(filename))
        {
            if (!System.IO.File.ReadAllText(filename).Contains
                                ($"Snapshot-Content-Location: {url}"))
                goto again;
            else
                break;
        }
    }
}

关注点

要使用 VBScript SendKeys 方法,您必须创建一个 WScript.Shell COM 对象的实例。最简单的方法是直接引用其 ActiveX 控件文件,方法是右键单击项目文件 -> 添加 -> COM 引用 -> 浏览 -> 选择 C:\Windows\SysWOW64\wshom.ocx

您应该会在 Visual Studio 的 Dependencies/COM 节点中看到 Interop.IWshRuntimeLibrary。单击它并将“嵌入互操作类型”从“”更改为“”(如果您使用 Net Core)。

Interop.IWshRuntimeLibrary

完成此操作后,您可以通过以下方式实例化 WScript.Shell COM 对象:

var wshShell = new WshShellClass();

历史

  • V1.0(2023 年 9 月 22 日):初始版本
© . All rights reserved.