在网络世界中,经常需要保存网页以便在没有网络的情况下查看。本文将介绍如何使用Visual C++创建一个离线浏览器,它可以下载网页及其所有资源,并将它们保存到本地硬盘上。将使用WinInet、URL Moniker和MSHTML等API来实现这一功能。
制作离线浏览器的动机源于一个需要记录用户与网页交互的模块。为了不使用浏览器的"另存为"选项,决定自己开发一个工具来保存网页到本地硬盘。以下是实现这一功能的简要算法描述:
1. 下载网页的HTML内容,例如www.google.com,并将其保存到指定的文件夹中。
2. 遍历HTML文档,查找每个标签的src属性,src属性的值是资源的URL。如果资源的URL是绝对的,例如www.google.com/images/logo.gif,那么没有问题。但如果URL是相对的,例如images/logo.gif,则需要使用主机名将其转换为绝对路径。
3. 更新src属性以反映资源URL的任何变化。相对URL将保持不变,但对于绝对地址,src属性现在将更改为相对路径。
4. 将原始src属性的值保存到srcdump中,仅供将来参考,以便原始src仍然可用。
在开发记录用户与网页交互的模块时,需要将网页保存到本地硬盘上。寻找了一些能够实现这一功能的代码,但没有找到有用的资料,因此决定自己开发。在这里分享这段代码,希望它能帮助其他从事相关工作的人,并希望得到一些反馈,指出可能犯的错误。没有使用MFC,以确保它与Win32应用程序以及MFC兼容。
以下是使用代码的示例:
LoadHtml()函数根据bDownload参数的值工作在两种模式下:
如果bDownload为true,则假定HTML已经使用SetHtml()函数加载,并且不会执行以下代码片段,只是从URL中填充主机名和端口字段。
如果bDownload为false,则首先从指定的URL下载HTML,然后填充主机名和端口字段。
HINTERNET hNet = InternetOpen("Offline Browser", INTERNET_OPEN_TYPE_PROXY, NULL, NULL, 0);
if (hNet == NULL) return;
HINTERNET hFile = InternetOpenUrl(hNet, sUrl.c_str(), NULL, 0, 0, 0);
if (hFile == NULL) return;
while (true) {
const int MAX_BUFFER_SIZE = 65536;
unsigned long nSize = 0;
char szBuffer[MAX_BUFFER_SIZE+1];
BOOL bRet = InternetReadFile(hFile, szBuffer, MAX_BUFFER_SIZE, &nSize);
if (!bRet || nSize <= 0) break;
szBuffer[nSize] = '\0';
m_sHtml += szBuffer;
}
BrowseOffline()假设HTML已经加载。首先,它通过将HTML加载到MSHTML DOMDocument接口来构建HTML DOM树:
SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, 1);
VARIANT *param;
bstr_t bsData = (LPCTSTR)m_sHtml.c_str();
hr = SafeArrayAccessData(psa, (LPVOID*)&m);
param->vt = VT_BSTR;
param->bstrVal = (BSTR)bsData;
hr = pDoc->write(psa);
hr = pDoc->close();
SafeArrayDestroy(psa);
一旦构建了DOM树,就可以遍历它并寻找需要下载的资源。目前,只寻找所有元素中的src属性,一旦找到src属性,就下载并保存到本地文件夹。
MSHTML::IHTMLElementCollectionPtr pCollection = pDoc->all;
for (long a = 0; a < pCollection->length; a++) {
std::string sValue;
IHTMLElementPtr pElem = pCollection->item(a);
if (GetAttribute(pElem, L"src", sValue)) {
if (!IsAbsolute(sValue)) {
if (sValue[0] == '/') sValue = sValue.substr(1, sValue.length()-1);
CreateDirectories(sValue, m_sDir);
if (!DownloadResource(sValue, sValue)) {
std::string sTemp = m_sScheme + m_sHost;
sTemp += sValue;
if (sTemp[0] == '/') sTemp = sTemp.substr(1, sTemp.length()-1);
SetAttribute(pElem, L"src", sTemp);
SetAttribute(pElem, L"srcdump", sValue);
} else {
SetAttribute(pElem, L"srcdump", sValue);
}
} else {
std::string sTemp;
sTemp = TrimHostName(sValue);
CreateDirectories(sTemp, m_sDir);
if (DownloadResource(sTemp, sTemp)) {
if (sTemp[0] == '/') sTemp = sTemp.substr(1, sTemp.length()-1);
SetAttribute(pElem, L"src", sTemp);
SetAttribute(pElem, L"srcdump", sValue);
}
}
}
}
由于src和srcdump属性的值发生了变化,原始HTML被更新并保存为[GUID].html,其中GUID是使用CoCreateGuid()生成的全局唯一标识符。
MSHTML::IHTMLDocument3Ptr pDoc3 = pDoc;
MSHTML::IHTMLElementPtr pDocElem;
pDoc3->get_documentElement(&pDocElem);
BSTR bstrHtml;
pDocElem->get_outerHTML(&bstrHtml);
std::string sNewHtml((const char*)OLE2T(bstrHtml));
SaveHtml(sNewHtml);
一旦有了资源的绝对URL,就可以直接下载并保存到适当的本地文件夹中。
if (URLDownloadToFile(NULL, sTemp.c_str(), sTemp2.c_str(), 0, NULL) == S_OK) return true;
else return false;
COfflineBrowser obj;
char szUrl[1024];
printf("Enter URL: ");
gets(szUrl);
obj.SetDir("c:\\MyTemp\\");
obj.LoadHtml(szUrl, true);
obj.BrowseOffline();