【问题标题】:Using selenium to save images from page使用 selenium 保存页面中的图像
【发布时间】:2020-09-04 08:44:03
【问题描述】:

我正在使用 Selenium 和 Google Chrome 驱动程序以编程方式打开页面。在每个页面上都有一个动态生成的图像,我想下载它。目前,我正在等待页面完成加载,然后我获取图像 URL 并使用 System.Net.WebClient 下载它。

这很好,除了我下载了两次图像 - 一次在浏览器中,一次在 WebClient 中。问题是每张图片大约 15MB,下载两次加起来很快。

那么 - 是否可以直接从谷歌浏览器获取图像?

【问题讨论】:

  • 你能分享你的代码来从页面下载图片吗?

标签: c# selenium download selenium-chromedriver


【解决方案1】:

一种方法是使用由 webdriver 执行的 javascript 获取图像的 base64 字符串。然后你可以将图片的base64string保存到文件中。

基本上,如果你的图片是

<img id='Img1' src='someurl'>

然后你可以像这样转换它

var base64string = driver.ExecuteScript(@"
    var c = document.createElement('canvas');
    var ctx = c.getContext('2d');
    var img = document.getElementById('Img1');
    c.height=img.naturalHeight;
    c.width=img.naturalWidth;
    ctx.drawImage(img, 0, 0,img.naturalWidth, img.naturalHeight);
    var base64String = c.toDataURL();
    return base64String;
    ") as string;

var base64 = base64string.Split(',').Last();
using (var stream = new MemoryStream(Convert.FromBase64String(base64)))
{
    using (var bitmap = new Bitmap(stream))
    {
        var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ImageName.png");
        bitmap.Save(filepath, ImageFormat.Png);
    }
}

【讨论】:

    【解决方案2】:

    是的,您可以通过几个步骤来完成:

    1. 截取网页截图并保存到磁盘
    2. 找到图片元素
    3. 查找图像元素的位置、宽度和高度
    4. 从您在第 1 步中截取的屏幕截图中裁剪您需要的图像
    5. 将图像保存到磁盘(或用它做其他事情)

    示例代码 - 请添加您的代码以捕获异常

            IWebDriver driver = new ChromeDriver();
    
            //replace with the page you want to navigate to
            string your_page = "https://www.google.com"; 
            driver.Navigate().GoToUrl(your_page);
    
            ITakesScreenshot ssdriver = driver as ITakesScreenshot;
            Screenshot screenshot = ssdriver.GetScreenshot();
    
            Screenshot tempImage = screenshot;
    
            tempImage.SaveAsFile(@"C:\full.png", ImageFormat.Png);
    
            //replace with the XPath of the image element
            IWebElement my_image = driver.FindElement(By.XPath("//*[@id=\"hplogo\"]/canvas[1]"));
    
            Point point = my_image.Location;
            int width = my_image.Size.Width;
            int height = my_image.Size.Height;
    
            Rectangle section = new Rectangle(point, new Size(width, height));
            Bitmap source = new Bitmap(@"C:\full.png");
            Bitmap final_image = CropImage(source, section);
    
            final_image.Save(@"C:\image.jpg");
    

    CropImage 方法由 James Hill 发布, How to cut a part of image in C#

    但为了清楚起见,我也会在这里添加它

        public Bitmap CropImage(Bitmap source, Rectangle section)
        {
            Bitmap bmp = new Bitmap(section.Width, section.Height);
            Graphics g = Graphics.FromImage(bmp);
            g.DrawImage(source, 0, 0, section, GraphicsUnit.Pixel);
            return bmp;
        }
    

    【讨论】:

    • 缺点是这会将图像保存为渲染后的图像,因此已调整大小或剪切的图像将无法正确保存。由于问题说图像是 15MB,因此特别有可能在播放中
    • 这种方式只适用于没有任何 css 规则的图像,如 margin 或 padding 会偏移它的位置。
    【解决方案3】:

    以上所有答案都有效。但是,它们都有局限性。 mecek 的方法很酷,但它只适用于支持 html 5 的浏览器(虽然现在大多数浏览器都支持),而且会降低图像质量。截图方法也会降低图像质量。使用 System.Net.WebClient 可以避免这个问题,但在下载验证码图像的情况下不起作用。实际上,在下载验证码图像时对我有用的唯一方法是使用 Actions 类(如果您使用的是 Selenium 的 java 版本,则使用 Robot),如下所示:

    using OpenQA.Selenium;
    using OpenQA.Selenium.Chrome;
    using OpenQA.Selenium.Interactions;
    using System.Windows.Automation;//you need to add UIAutomationTypes and UIAutomationClient to references
    using System.Runtime.InteropServices;
    
    [DllImport("User32.dll")]
    static extern int SetForegroundWindow(IntPtr point);
    
    private IntPtr getIntPtrHandle(IWebDriver driver, int timeoutSeconds = 30)
    {
            var end = DateTime.Now.AddSeconds(timeoutSeconds);
            while (DateTime.Now < end)
            {
                var ele = AutomationElement.RootElement;
                foreach (AutomationElement child in ele.FindAll(TreeScope.Children, Condition.TrueCondition))
                {
                    if (!child.Current.Name.Contains(driver.Title)) continue;
                    return new IntPtr(child.Current.NativeWindowHandle);
                }
            }
            return IntPtr.Zero;
    }
    
    private void downloadCaptcha(IWebDriver chromeDriver)
    {
        OpenQA.Selenium.IWebElement captchaImage = chromeDriver.FindElement(By.Id("secimg0"));
        var handle = getIntPtrHandle(chromeDriver);
        SetForegroundWindow(handle);//you need a p/invoke 
        Thread.Sleep(1500);//setting foreground window takes time
        Actions action = new Actions(chromeDriver);
        action.ContextClick(captchaImage).Build().Perform();
        Thread.Sleep(300);
        SendKeys.Send("V");
        var start = Environment.TickCount;
        while (Environment.TickCount - start < 2000)
        {//can't use Thread.Sleep here, alternatively you can use a Timer
              Application.DoEvents();
        }
        SendKeys.SendWait(@"C:\temp\vImage.jpg");
        SendKeys.SendWait("{ENTER}");
    }
    

    这是我发现使用 Selenium Chrome 驱动程序下载验证码图像而不损失其质量(以获得更好的 OCR 效果)的唯一方法,尽管限制也很明显。

    【讨论】:

      【解决方案4】:

      根据 meceks 的回答,我使用以下版本来捕获 webdriver 映像,效果很好。

      它以 90% 的质量创建 base64 jpeg 字符串。为了避免像素化问题,我将图像绘制到画布上,该画布比我稍后将要呈现的图像大。因此,图像被放大以最适合 600 像素的框,同时保留纵横比。 由于 jpeg 不支持透明度,我用白色背景清除上下文。

      var base64string = (driver as IJavaScriptExecutor).ExecuteScript(@"
      var canvas = document.createElement('canvas');
      var ctx = canvas.getContext('2d');
      
      function getMaxSize(srcWidth, srcHeight, maxWidth, maxHeight) {
          var widthScale = null;
          var heightScale = null;
      
          if (maxWidth != null)
          {
              widthScale = maxWidth / srcWidth;
          }
          if (maxHeight != null)
          {
              heightScale = maxHeight / srcHeight;
          }
      
          var ratio = Math.min(widthScale || heightScale, heightScale || widthScale);
          return {
              width: Math.round(srcWidth * ratio),
              height: Math.round(srcHeight * ratio)
          };
      }
      
      function getBase64FromImage(img, width, height) {
          var size = getMaxSize(width, height, 600, 600)
          canvas.width = size.width;
          canvas.height = size.height;
          ctx.fillStyle = 'white';
          ctx.fillRect(0, 0, size.width, size.height);
          ctx.drawImage(img, 0, 0, size.width, size.height);
          return canvas.toDataURL('image/jpeg', 0.9);
      }
      
      var img = document.querySelector('#foo');
          return getBase64FromImage(img, img.width, img.height);
      ") as string;
      
      var base64 = base64string.Split(',').Last();
      

      【讨论】:

        【解决方案5】:

        您可以使用this 技术阻止在 Google Chrome 中下载图片。它运行一个名为“Block Image”的 Google Chrome 扩展程序。这样就不会使用 chrome 下载图像,只需使用其 URL 和 System.Net.WebClient 正常下载图像即可。

        【讨论】:

          【解决方案6】:

          您是否尝试使用 ImageIO 下载图像?

          String imageUrl = "image.png";
          BufferedImage bufferedImage = ImageIO.read(imageUrl);
          ImageIO.write(bufferedImage, "png", new File("savedImage.png"));
          

          【讨论】:

            【解决方案7】:

            在 java 中尝试以下操作:

            JavascriptExecutor js = (JavascriptExecutor) driver;                              
            String base64string = (String) js.executeScript("var c = document.createElement('canvas');"
                                              + " var ctx = c.getContext('2d');"
                                              + "var img = document.getElementsByTagName('img')[0];"
                                              + "c.height=img.naturalHeight;"
                                              + "c.width=img.naturalWidth;"
                                              + "ctx.drawImage(img, 0, 0,img.naturalWidth, img.naturalHeight);"
                                              + "var base64String = c.toDataURL();"
                                              + "return base64String;");
            String[] base64Array = base64string.split(",");
            
            String base64 = base64Array[base64Array.length - 1];
            
            byte[] data = Base64.decode(base64);
            
            ByteArrayInputStream memstream = new ByteArrayInputStream(data);
            BufferedImage saveImage = ImageIO.read(memstream);
            
            ImageIO.write(saveImage, "png", new File("C:\\ClaimsData\\downloadspdfs\\" + originalName));
            

            【讨论】:

            • 只是一个旁注:当然,如果这对您很重要,那么您将丢失原始照片的所有 EXIF/元数据。
            【解决方案8】:

            根据Mehmet Mecek的回答,我做了自己的小方法, 根据类名获取图像(因为没有可用的“id”),并且由于我要获取的每个图像都有相同的类名,所以我使用 src 属性内容(图像的 URL)来过滤 javascript 结果获取我想要的特定图像...

            src 也可以只是部分字符串(如文件名),但必须对 HTML 中的原始字符串区分大小写。

            注意:不包括错误检查。

            工作就像一个魅力。

            
             public static void LoadImageFromClassAndSrcInfo(IWebDriver webDriver, string className, string partialSrc, string localFile)
                    {
                        IJavaScriptExecutor js = (IJavaScriptExecutor) webDriver;
                        string base64string = js.ExecuteScript(@"
                var c = document.createElement('canvas');
                var ctx = c.getContext('2d');
                var img = Array.prototype.filter.call(document.getElementsByClassName('"+className+@"'), ({ src }) => src.includes('"+ partialSrc +@"') )[0];
                c.height=img.naturalHeight;
                c.width=img.naturalWidth;
                ctx.drawImage(img, 0, 0,img.naturalWidth, img.naturalHeight);
                var base64String = c.toDataURL();
                return base64String;
                ") as string;
            
                        var base64 = base64string.Split(',').Last();
            
                        using (var stream = new MemoryStream(Convert.FromBase64String(base64)))
                        {
                            using (var bitmap = new Bitmap(stream))
                            {
                                bitmap.Save(localFile, ImageFormat.Jpeg);
                            }
                        }
                    }
            
            

            【讨论】:

              【解决方案9】:

              只是想分享一个经验: 我想将验证码图像放在单独的表单中,以便用户可以将文本放在字段中作为主网页的替代品。 我结合了 TH Todorov 在https://stackoverflow.com/a/30025029(上面已经回答)和 James Hill 在How to cut a part of image in C#(上面提到)提出的方法,并提出了以下代码。它在一个案例中完美运行,您不需要将图片保存在驱动器上。我也适用于网站中的任何图像。我希望这会有所帮助。

              private Bitmap GetCaptchaImage()
                      {
                          ITakesScreenshot ssdriver = driver as ITakesScreenshot;
                          Screenshot screenshot = ssdriver.GetScreenshot();
                          IWebElement captchaImage = driver.FindElement(By.XPath("put the captcha image path here"));
              
                          Point point = captchaImage.Location;
                          int width = captchaImage.Size.Width;
                          int height = captchaImage.Size.Height;
              
                          Rectangle section = new Rectangle(point, new Size(width, height));
                          Bitmap source = new Bitmap(new MemoryStream(screenshot.AsByteArray));
              
                          Bitmap finalCaptchImage = CropImage(source, section);
                          return finalCaptchImage;
                      }
                      private Bitmap CropImage(Bitmap source, Rectangle section)
                      {
                          Bitmap bmp = new Bitmap(section.Width, section.Height);
                          Graphics g = Graphics.FromImage(bmp);
                          g.DrawImage(source, 0, 0, section, GraphicsUnit.Pixel);
                          return bmp;
                      }
              

              【讨论】:

              • 非常好的 Abbasali,感谢您分享您的解决方案 - 非常简洁
              • 在屏幕截图的那部分没有得到任何东西 ;(
              • 亲爱的 Tushar,首先我重复我的 cmets 说我在这里分享的代码只是一个想法,即为其他答案添加功能以处理从屏幕截图创建的图像。然后我在我的代码中分享了一个类的方法。我再次检查了它,它的工作原理。因为没有提供详细信息,我猜它可能与初始化 webdriver 相关,或者图像的地址(在此代码中:xpath)。如果我能提供任何帮助,请告诉我。
              【解决方案10】:
              I'm using Selenium & Google Chrome Driver
              

              关于硒的讨论。

              once in the browser, once with WebClient
              

              HTML 单元?

              无论如何,您为什么不使用 webclient (htmlunit-driver) 或纯 htmlunit (http://htmlunit.sourceforge.net/)。 Htmlunit 默认不下载图片。

              您可以根据需要自行下载。

              【讨论】:

              • 感谢编码,selenium 非常有能力做 htmlunit 所做的事情。我使用的是 C#,所以当提到 WebClient 时,我指的是 System.Net.WebClient。我不能使用纯 System.Net.WebClient 的原因是因为页面很复杂,因为它多次使用 jquery 来获取其余内容(在本例中为 jpeg 查看器)。 Selenium 让我克服了这个障碍,但缺点是图像被下载了两次。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-05-12
              • 1970-01-01
              • 2022-12-16
              • 1970-01-01
              • 2014-01-03
              • 2014-07-19
              • 2014-04-17
              相关资源
              最近更新 更多