[已弃用] 使用 C# 捕获整个网页图像






4.74/5 (56投票s)
【已过时】使用C#捕获整个网页作为单个图像。
引言
【已过时】——我更新此说明,指出以下代码已过时,因为现在所有现代浏览器都有大量的页面截图扩展程序。我保留此代码是为了历史/怀旧原因,但不要浪费时间尝试实现它。——Doug
--------------------------------------------------------------------------------------------------------------------------
本文介绍了一个C#例程,用于将整个网页捕获为图像。许多捕获示例展示了如何抓取屏幕截图,但没有展示如何收集应用程序滚动区域以下的信息。滚动问题或“溢出”程序最常见的例子是网页。
此应用程序抓取页面,另外,作为奖励,它还演示了如何让客户端调整图像的大小和JPEG的质量。它展示了如何将网页名称写入图像,绘制标准分辨率指南,将位图保存为JPEG以及打开存储捕获的目录。
背景
在最近的一个应用程序中,我想为我们的质量保证测试人员提供捕获整个网页的能力。我希望他们能够通过点击另一个测试任务使用的BHO(浏览器帮助对象)中的按钮来实现此目的。我还想减小捕获的大小,因为这些图像会被发送电子邮件,并可能迅速填满我们的邮箱配额。
使用代码
使用此代码最简单的方法是下载源代码,去除可能不需要的代码函数(捕获质量、图像大小、URL写入、指南或打开目录函数)。在代码被精简并且程序能够在没有错误的情况下编译后,将源代码及其依赖项复制到目标项目中。
将源代码复制到项目中时遇到的第一个问题是需要引用SHDocVw.dll和MSHTML.dll。在Visual Studio中,转到“项目”,“添加引用”,然后选择“COM”选项卡。现在,向下滚动到Microsoft部分,查找“Microsoft Internet Controls”。选择它,然后查找“Microsoft HTML Object Library”(参见上图)。
添加引用后,将这些必要的指令添加到项目中。(如果代码没有加载到窗体中,则需要其他一些指令。)
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using System.Drawing.Imaging;
using SHDocVw;
using mshtml;
导入user32函数
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr FindWindowEx(IntPtr parent /*HWND*/,
IntPtr next /*HWND*/, string sClassName, IntPtr sWindowTitle);
[DllImport("user32.dll", ExactSpelling=true, CharSet=CharSet.Auto)]
public static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);
[DllImport("user32.Dll")]
public static extern void GetClassName(int h, StringBuilder s, int nMaxCount);
[DllImport("user32.dll")]
private static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
public const int GW_CHILD = 5;
public const int GW_HWNDNEXT = 2;
找到一个打开的浏览器并为其分配一个浏览器文档。
SHDocVw.WebBrowser m_browser = null;
SHDocVw.ShellWindows shellWindows = new SHDocVw.ShellWindowsClass();
//Find first availble browser window.
//Application can easily be modified to loop through and
//capture all open windows.
string filename;
foreach (SHDocVw.WebBrowser ie in shellWindows)
{
filename = Path.GetFileNameWithoutExtension(ie.FullName).ToLower();
if (filename.Equals("iexplore"))
{
m_browser = ie;
break;
}
}
if (m_browser == null)
{
MessageBox.Show("No Browser Open");
return;
}
//Assign Browser Document
mshtml.IHTMLDocument2 myDoc = (mshtml.IHTMLDocument2)m_browser.Document;
必须确定网页的宽度和高度以及客户端屏幕的分辨率设置。
//Set scrolling on.
myDoc.body.setAttribute("scroll", "yes", 0);
//Get Browser Window Height
int heightsize = (int)myDoc.body.getAttribute("scrollHeight", 0);
int widthsize = (int)myDoc.body.getAttribute("scrollWidth", 0);
//Get Screen Height
int screenHeight = (int)myDoc.body.getAttribute("clientHeight", 0);
int screenWidth = (int)myDoc.body.getAttribute("clientWidth", 0);
要捕获整个网页,必须抓取页面的片段并将它们拼接在一起以构成整个页面。捕获第一个片段后,浏览器向下滚动以进行下一个捕获。随着片段的捕获,它们被拼接成目标位图。重复此过程,直到捕获整个页面。对于比客户端屏幕更宽的页面,页面会水平滚动,然后重复上述过程。
//Get bitmap to hold screen fragment.
Bitmap bm = new Bitmap(screenWidth, screenHeight,
System.Drawing.Imaging.PixelFormat.Format16bppRgb555);
//Create a target bitmap to draw into.
Bitmap bm2 = new Bitmap(widthsize + URLExtraLeft, heightsize +
URLExtraHeight - trimHeight,
System.Drawing.Imaging.PixelFormat.Format16bppRgb555);
Graphics g2 = Graphics.FromImage(bm2);
Graphics g = null;
IntPtr hdc;
Image screenfrag = null;
int brwTop = 0;
int brwLeft = 0;
int myPage = 0;
IntPtr myIntptr = (IntPtr)m_browser.HWND;
//Get inner browser window.
int hwndInt = myIntptr.ToInt32();
IntPtr hwnd = myIntptr;
hwnd = GetWindow(hwnd, GW_CHILD);
StringBuilder sbc = new StringBuilder(256);
//Get Browser "Document" Handle
while (hwndInt != 0)
{
hwndInt = hwnd.ToInt32();
GetClassName(hwndInt, sbc, 256);
if(sbc.ToString().IndexOf("Shell DocObject View", 0) > -1)
{
hwnd = FindWindowEx(hwnd, IntPtr.Zero,
"Internet Explorer_Server", IntPtr.Zero);
break;
}
hwnd = GetWindow(hwnd, GW_HWNDNEXT);
}
//Get Screen Height (for bottom up screen drawing)
while ((myPage * screenHeight) < heightsize)
{
myDoc.body.setAttribute("scrollTop", (screenHeight - 5) * myPage, 0);
++myPage;
}
//Rollback the page count by one
--myPage;
int myPageWidth = 0;
while ((myPageWidth * screenWidth) < widthsize)
{
myDoc.body.setAttribute("scrollLeft", (screenWidth - 5) * myPageWidth, 0);
brwLeft = (int)myDoc.body.getAttribute("scrollLeft", 0);
for (int i = myPage; i >= 0; --i)
{
//Shoot visible window
g = Graphics.FromImage(bm);
hdc = g.GetHdc();
myDoc.body.setAttribute("scrollTop", (screenHeight - 5) * i, 0);
brwTop = (int)myDoc.body.getAttribute("scrollTop", 0);
PrintWindow(hwnd, hdc, 0);
g.ReleaseHdc(hdc);
g.Flush();
screenfrag = Image.FromHbitmap(bm.GetHbitmap());
g2.DrawImage(screenfrag, brwLeft + URLExtraLeft, brwTop +
URLExtraHeight);
}
++myPageWidth;
}
最后,将上述目标保存到一个带有时间戳的JPEG文件中。
关注点
在这个项目中,我玩得很开心,也经历了很多挫折。捕获效果非常好。尝试在“Code Project”页面之一上试用它。
本文未显示,但在源代码中提供的是将文件保存为JPEG。我尝试过GIF和位图,但最终选择了JPEG以减小文件大小。主要目标是能够发送这些文件而不占用大量的邮箱配额。
在实际应用程序中,我有一个将文件复制到剪贴板的选项。但我从未能够将剪贴板图像转换为不会占用大量空间的“设备相关位图”状态。我会复制图像,然后将其粘贴到我的Outlook电子邮件中,结果电子邮件大约有1MB大小。当我使用Photoshop打开JPEG,然后选择它,复制它并将其粘贴到Outlook中时,Adobe设备相关位图小于100KB。简单的Windows画图应用程序也发生了同样的情况。
由于时间限制,我最终只将JPEG文件复制到Outlook。欢迎提供任何关于如何将大型设备无关位图转换为具有较小内存占用量的位图的解决方案。