【VC++开源代码栏目提醒】:网学会员--在 VC++开源代码编辑为广大网友搜集整理了:Explorer内核的网页信息抓取程序 转 - 技术总结绩等信息,祝愿广大网友取得需要的信息,参考学习。
Explorer内核的网页信息抓取程序 转 转自程序开发背景 本程序来源于我们项目组最近正在开发的一个
开源项目--网页分块工具。
其目的是作为一个底层的信息抽取模块为后期分析提供尽可能详尽的分块线索包括尽可能完整的HTML源
代码和网页元素的位置、颜色、字体、背景色等信息。
程序还要具有较好的适应性能够支持多种网页而事实上很多网页都是不标准的。
从通用性考虑程序应该能够支持多种应用而不仅限于网页分块。
预期目标分析 程序应达到以下几点设计要求 能够指定要处理的网页的URL。
能够为HTML源
代码添加附件信息如元素位置。
对于JavaScript等动态脚本具有良好的解析能力。
通过命令行调用提供良好的通用性。
通过socket套接字返回HTML源
代码。
支持延时读取保证抓取的成功率。
支持超时退出保证程序不会因为加载不成功而卡死。
使用IE内核的原因 本程序的核心部分使用的是IE内核。
至于为什么要基于IE内核而不使用其他浏览器的内核有以下几方面的原因 首先firefox、google chrome等浏览器虽然是开放源
代码的但是其源
代码的阅读难度相当大想在短时间内弄明白是很困难的。
其次IE的相关开发文档比较完整开发环境比较容易构建开发起来更容易上手。
最后从网页的兼容性考虑得益于IE的广泛的市场占有率其兼容性明显要比其他浏览器要好很多尽管对很多标准都支持得不是很好。
综上就可以确定本程序使用IE内核进行开发实验证明这个做法是正确的。
Internet Explorer的程序结构 对于本程序来说其中最重要的的就是网页内容处理层所用到的接口也都位于mshtml.dll文件中。
开发环境 系统Windows XP IDEVisual Studio 2005中文版 IE版本Internet Explorer 6 构建基于对话框的MFC程序 运行visual studio 2005c新建一个项目选择MFC标签下的MFC应用程序作为模板填入项目名称确定。
此时会弹出一个向导按照以下步骤操作点击左侧的应用程序类型选择基于对话框在静态库中使用MFC方便以后发布其他保持默认即可。
然后单击完成程序会自动生成相应的类。
切换到资源视图依次展开在DIALOG中找到以项目名称命名的对话框双击打开。
删除确定和取消按钮。
在对话框窗口上单击右键选择插入activeX控件。
在新弹出的窗口中选择Microsoft web浏览器确定。
调整好IE控件的位置后在其上单击右键选择添加变量输入名称m_webBrowser。
切换到解决方案视图打开对话框的源文件名称通常为Dlg.cpp为项目名。
将下面的
代码添加到对话框初始化函数OnInitDialog中。
LPCTSTR url_T m_webBrowser.NavigateurlvtEmptyvtEmptyvtEmptyvtEmpty 如何确定WebBrowser控件中的网页加载完成 当网页下载完成后WebBrowser控件触发DocumentComplete事件。
通过在程序中添加响应DocumentComplete事件的程序我们就可以在网页下载完成后对其进行分析和处理。
添加事件处理程序的操作步骤如下切换到资源视图打开包含WebBrowser控件的对话框在WebBrowser控件上单击右键选择添加事件处理程序然后在弹出的对话框中选择DocumentComplete消息点击添加编辑以确认。
WebBrowser控件触发DocumentComplete事件的ReadyState属性更改为READYSTATE_COMPLETE时这表示WebBrowser控件已完成下载网页。
虽然通过响应DocumentComplete事件可以知道网页是否加载完成但是有的网页触发了不止一次DocumentComplete事件例如网易首页会从加载开始到完全加载完毕会激发二十多次DocumentComplete事件。
出现这种情况的主要原因是网页中包含JavaScript等动态脚本而且有可能会改变网页元素的结构当这些脚本完成解析后会触发DocumentComplete事件如果网页是由多个frame框架组成的每个框架中的网页加载完成也会触发DocumentComplete事件。
针对第二种情况微软给出了具体的解决方案但是第一种情况仍无法解决。
通过查阅相关的社区我找到了能够基本解决第一种情况的方法--通过将DocumentComplete事件处理函数的参数中的URL与当前的网页文档的URL相比较若相同则说明整个网页都已经完成加载此时再对网页进行分析和处理然后退出。
按照理论只需对网页做一次处理就可以了。
然而在测试新浪博客时我又发现了问题--在博客评论加载完成之前触发很多次DocumentComplete事件其中的一次事件对应的URL与网页文档的URL一样。
如果只对网页处理一次程序是无法处理获取加载评论之后的网页。
这就是为什么程序需要加上延时读取功能的原因具体思路请参照下一节。
当然在WebBrowser控件的事件中还有其他的事件比如NavigateComplete2事件。
我曾经尝试在其他事件触发时对网页进行分析但是都会出错要么只能获取到一部分元素要么直接就弹出错误信息。
这是因为此时网页尚未完全加载很多元素的属性都没确定当然无法确定元素的具体信息如元素位置信息。
延时读取和超时退出 为了降低网络、机器配置、系统软件等外界因素对程序的影响提高读取的成功率。
本程序加入了延时读取和超时退出的功能。
具体实现方法是 首先在程序的初始化函数中如对话框的OnInitDialog函数添加一个固定ID的定时器使程序定时发出一个WM_TIME消息。
具体函数如下 SetTimer88881000NULL//8888为该定时器的ID1000为定时发出WM_TIME消息的时间单位为毫秒。
然后添加一个处理WM_TIME消息的函数其
代码如下 1void CMyBrowserDlgOnTimerUINT_PTR nIDEvent23CTime ct4CTimeSpan cts0005000//程序延时执行时间5CTimeSpan timeOut000m_timeOut//程序超时退出时间67//判断定时器ID若非指定的定时器ID则退出8ifnIDEvent88889CDialogOnTimernIDEvent10return111213//获取当前时间14ctCTimeGetCurrentTime1516//超时退出并输出错误信息17ifctm_timetimeOut18PostQuitMessage3//强制退出程序并返回一个int型的值192021//获取IHTMLDocument2指针以便进行下面的操作22CComQIPtr IHTMLDocument2 spDoc2IHTMLDocument2m_webBrowser.get_Document2324//判断网页加载状态若加载完成则继续处理否则返回2526if1m_flag//m_flag为documentComplete事件触发标志1表示已触发0表示尚未触发27return28else ifm_webBrowser.READYSTATE_COMPLETEm_webBrowser.get_ReadyState29return30else ifctm_timects31return3233…34 使用IE提供的接口 网页内容处理模块的接口都包含在mshtml.h的头文件中使用IE接口时需将此头文件包含在源文件中。
在
VC平台中可以通过使用接口指针来调用接口提供的函数。
下面是该程序中用到的几个重要的IE接口 接口功能说明 IHTMLDocument2获取HTML文件的信息并审查和修改HTML元素和文本。
IHTMLDocument3提供文件对象的额外的属性和方法。
IHTMLElement此接口提供了访问所有元素对象共同的属性和方法的能力 IHTMLDOMNode提供方法来访问所有在文档对象模型DOM中的节点包括节点的迭代插入节点删除节点并得到的属性节点。
IHTMLDOMChildrenCollection提供方法来存取子节点的集合。
接下来我将针对每个接口逐个列举在本程序中较为重要的几个函数展示其示例
代码以及解析在编写相关程序时遇到的问题。
IHTMLDocument2接口 下面的
代码演示的是如何从WebBrowser控件中获取IHTMLDocument接口。
IHTMLDocument2pDoc2IHTMLDocument2m_webBrowser.get_Document IHTMLDocument2接口中有一个比较重要的函数 HRESULTget_bodyIHTMLElementp获取HTML文档中body对象的借口指针 通过get_body函数我们就可以获得BODY元素的接口指针。
在程序中所有的分析和处理工作都是基于BODY元素的而不是从HTML文档的根节点开始处理。
之所以这么做是因为本程序的目的是获取网页内容的布局信息而真正能显示在屏幕上的信息都是位于BODY标签内的因此就没有必要从根节点开始处理。
下面是该函数的简单实例
代码 IHTMLElementpBody//IHTMLElement接口指针指向body对象 HRESULT hr//用于存放函数调用结果 hrpDoc2-get_bodypBody//获取body对象的指针返回操作结果 ifSUCCEEDEDhr//若操作成功则继续执行 //Something to do IHTMLDocument3接口 前面说过了IHTMLDocument3只是IHTMLDocument2接口的扩展而且在本程序中用到该接口的地方也就一两处。
使用IHTMLDocument3接口的原因是其提供了一个get_documentElement函数下面是其介绍和简单的示例 HRESULTget_documentElementIHTMLElementp获取HTML文档中根节点的接口指针 示例 IHTMLElementpDocElem//IHTMLElement接口指针指向body对象 HRESULT hr//用于存放函数调用结果 hrpDoc2-get_documentElementpDocElem//获取body对象的指针返回操作结果 ifSUCCEEDEDhr//若操作成功则继续执行 //Something to do 获取根节点的目的是通过它获取整个HTML文档的源
代码具体如何获得请看下面关于IHTMLElement接口的介绍。
IHTMLElement接口 函数原型功能说明 HRESULTget_innerHTMLBSTRp获取当前对象开始和结束标签之间的HTML源
代码动态内容 HRESULTget_innerTextBSTRp获取当前对象开始和结束标签之间的文本内容动态内容 HRESULT get_outerHTMLBSTRp获取对象的HTML的内容静态内容 HRESULT get_outerTextBSTRp获取对象的文本内容静态内容 下面只给出get_innerHTML函数的使用方法示例另外三个函数类似 IHTMLElementpElem//IHTMLElement接口指针指向body对象 BSTR html//存放html源
代码 _bstr_t html_t//用于将BSTR转换为cout可以处理的字符串 hrpElem-get_innerHTMLhtml ifSUCCEEDEDhr html_thtml coutThe html within this element ishtml_t get_innerHTML与get_outerHTML的区别 对于这四个函数我所要强调的就是他们之间的区别。
InnerHTML和outerHTML函数最大的区别就是前者可以获取到网页中动态的HTML源
代码如利用javascript加载的评论而后者只能获取未解析前的静态内容其功能与在网页上单击右键查看网页源文件获取到的内容一致。
在程序设计的早期阶段先使用get_documentElement获取根节点docElem然后再用get_innerHTML获取完整的HTML源
代码。
后来在测试中发现了问题对于docElem来说无论是使用get_innerHTML还是get_outerHTML都无法获取包含javascript解析结果的HTML源
代码。
又经过多次的测试后发现只有通过get_body函数获取到的bodyElem才能得到真正的动态内容。
如何得到完整的真正的动态HTML源
代码针对这个问题在本程序中采用了一种比较简单的解决方案先从docElem中获取到完整的HTML源
代码再从bodyElem中获取到动态的内容然后再将原先静态的HTML中的BODY标签内的内容用这些动态的内容替换掉最后就可以得到了完整的包含javascript执行结果的动态HTML源
代码。
有人可能会问完整的HTML和body间的内容差别在哪了解HTML的人都知道完整的HTML源
代码不仅包含BODY标签还包含了HEAD标签而HEAD标签对于网页的正常显示起着很大的作用。
出于通用性方面的考虑本程序就以获取尽量完整HTML源
代码作为设计要求。
上面这个问题足足困扰了我一个星期很奇怪微软为什么不允许从根节点获取动态内容呢 BSTR和_bstr_t 细心的话可能会发现
代码示例中在输出HTML源
代码之前先将BSTR类型的变量html赋值给了_bstr_t类型的变量html_t然后再输出到控制台中。
这里涉及到得是BSTR类型在
VC平台中的处理问题。
BSTR是COM中默认的字符串数据格式和char及stdstring等不同BSTR是以message结尾长度为前缀的unicode字符串。
char指针指向的是该串的第一个字符而BSTR的指针是指向该字符串的长度。
操作系统提供相应的API函数如SysAllocString、SysFreeString来管理它以及一些默认的调度
代码。
缺点对于字符串来说理所应当提供的字符串操作如查找子串字符串比较等函数都没有。
更重要的是似乎没有任何函数能复制BSTR。
BSTR有两个包装类分别是CComBSTR和_bstr_t。
_bstr_t是native COM support类而CComBSTR是ATL中的BSTR包装类。
这两个功能上很相似都提供了BSTR字符串的操作函数但实现机制不同_bstr_t更通用些不过如果使用ATL的话可能CComBSTR更方便些。
由于本程序是MFC程序所以使用的是_bstr_t。
总的来说_bstr_t的作用就是将BSTR转换成大多数函数都能处理的类型从而对BSTR字符串的内容进行操作。
获取网页元素的位置信息 在IHTMLElement接口中还提供了两个计算网页元素位置的函数 HRESULTget_offsetLeftlongp获取对象相对于父节点左侧的位置即x坐标 HRESULTget_offsetToplongp获取对象相对于父节点顶部的位置即y坐标 示例
代码 NodepNode//父节点 …. long absX long parentAbsX parentAbsXpNode-getAbsX//获取父节点的绝对坐标 ifSUCCEEDEDspElement-get_offsetLeftabsX absXparentAbsX 值得注意的是这两个函数获取到的都是相对于父节点的坐标计算元素绝对坐标时还需要加上父节点的绝对坐标。
因此在设计程序时使用了一个自定义的Node类其中包含着当前节点位置信息然后传递给子节点子节点计算出相对坐标后再加上该绝对坐标就可以得到子节点的绝对坐标。
在IE的内存模型中网页文档是以DOMDocument Object Model文档对象模型存放在内存中的对网页的处理和分析都是基于DOM来操作的其操作方法与普通的DOM并无太大区别。
下面简单介绍IE处理网页的两个DOM相关接口 IHTMLDOMNode接口 HRESULTget_childNodesIDispatchp获取指定节点的所有直接后裔节点的集合 HRESULTget_nodeTypelongp返回指定节点的类型 在网页文档的DOM结构中标签的属性、文本和注释都是以节点的形式存在的。
然而这些节点却无法使用其他接口来处理如IHTMLElement接口如果要对这些类型的节点强行操作程序就会报错退出。
因此在DOM递归时要进行查询IHTMLElement接口时就要通过IHTMLDOMNode的nodeType来进行判断。
只有当nodeType为element时才有子节点向下递归才不会出错。
nodeType所对应的节点类型attribute属性1element元素3text文本8comment注释。
IHTMLDOMChildrenCollection接口 HRESULTget_lengthlongp获取集合中子节点的个数 HRESULTitemlongindexIDispatchppItem获取指定索引位置的子节点 遍历DOM中的所有节点 结合IHTMLDOMNode接口和IHTMLDOMChildrenCollection接口就可以遍历DOM中的所有节点。
下面是示例
代码 void getAllChildIHTMLDOMNodepNode CComPtr IDispatch spChildrenDisp//用于子节点的集合 CComPtr IDispatch spChildDisp//正在处理的子节点 IHTMLDOMChildrenCollectionspChildrenNode longnodeType//节点类型 pNode-get_nodeTypenodeType pNode-get_childNodesspChildrenDisp if3nodeType//判断节点类型是否为element …//一些额外的操作 spChildrenNodeIHTMLDOMChildrenCollectionspChildrenDisp spChildrenNode-get_lengthchildrenNum//获取子节点的集合长度 forlong i0i childrenNumi//循环递归遍历所有孩子节点 spChildrenNode-itemispChildDisp getAllChildIHTMLDOMNodespChildDisp ifspChildDispNULL spChildDisp.Detach//spChildDisp每次使用后都需要释放 //因为若spChildDisp在使用时非空会报错 JAVA与C的进程间通信 由于本程序是底层模块需要被上层的java程序调用因此就设计到了JAVA与c进程间通信的问题。
经调查了解到JAVA与C通信方式有几种1.JNI 2.CORBA 3.Socket套接字4.文件等。
我曾尝试过使用JNI和CORBA但是都因为太过麻烦而放弃。
而利用文件的方法虽然可以使用但是开销太大--要频繁地进行I/O层的读取操作而且效率低、灵活性差。
所以暂时决定使用命令行加socket的方式实现进程间通信以下是整个程序的架构 具体实现细节可以查阅MSDN和JAVA API关于socket套接字的实现和通过java runtime调用exe程序的相关文档本文就不一一赘述了。
相关资源及链接 MSDN技术资源库
VC知识库《Programming Microsoft Internet Explorer 5》-Scott Roberts 《深入浅出MFC第二版》-候俊杰 《
VC深入详解》-孙鑫 特别声明 1资料来源于互联网版权归属原作者 2资料内容属于网络意见与本账号立场无关 3如有侵权请告知立即删除。
上一篇:
运动物体跟踪算法【毕业论文,绝对精品】
下一篇:
临床前药物安全性评价中毒性病理学新技术的应用