利用 Microsoft 的 HTML 分析器来获得 Web 站点的数据
Jeremy Rule
Microsoft Corporation
2000年5月 程序员面临的一个共同任务就是收集 Web 站点的数据,并将它分布到数据库或其他 Web 页。例如,程序员可能需要从气象站点获得天气预报图,从在线股票经纪人那里获得股票报价,以及从新闻站点获得行业新闻。然后,这些信息被放在一个 Web 页上,供 CIO、商人或销售经理使用。或者,也许程序员需要跟踪历来的气象资料,并需要每天将来自气象站的天气预报信息存入数据库。其应用不胜枚举。
过去,这些选择相当受限制。现在,通过使用象 WinInet.dll 这样的 HTTP 组件或许多其他第三方组件,您就可以获取 Web 页,并利用几百种字符串处理功能来获得网页中您所感兴趣的部分。这一技术已在应用,但很不理想。如果您致力于计算机科学(或者有足够的时间),就会为 HTML 创建一个分析器,以标记 Web 页,然后分析您需要的网页部分。不过,由于 Internet Explorer 的体系结构中已包含了可重复使用的用分析器,这些都不需要了。
Internet Explorer 不只是一个程序,更是许多可重复使用组件的集合与容器。在拆取 Web 页时,最有意思的两个组件是
shdocvw.dll 和
mshtml.dll。第一个组件 shdocvw.dll,包含称为 WebBrowser 的 Microsoft(R) ActiveX(R) 控件,它真实地显示 Web 页。在运行 Internet Explorer 时,显示 Web 页的主窗口就是这样的控件。第二个组件 mshtml.dll,含有能分析 WebBrowser 控件中所包含文档的 HTML 分析器。
可能有这种情况,在您的应用程序内部,已经用 WebBrowser 控件来驻留 Web 页,但仍需要重新创建一个小浏览器来启动 Web 页的拆取。
在
文件菜单上,请单击
新建工程,以创建“标准 EXE”,然后在
工程菜单上单击
部件,以添加
Microsoft HTML Object Library 和
Microsoft Internet Controls。(见图 1。)
图 1. 在工具箱中,可看见 WebBrowser 组件。拖动其中之一,文本框和主窗体上的命令按钮。将此文本框的
Text 属性设置为 “
http://moneycentral.msn.com/”,将此命令按钮的
Caption 属性设置为“浏览(&B)”。(见图 2。)
图 2. 双击该命令按钮,然后在事件处理器中放入下列代码,导航至文本框中命名的 Web 站点:
Private Sub Command1_Click()
WebBrowser1.Navigate Text1.Text
End Sub
保存并运行该程序。试着按
浏览按钮,导航到文本框中指定的站点。您已经创建了一个基本的 Web 浏览器 — 就其本身而言没什么用,甚至没什么意义,但它却是迈向 Web 拆取技术的第一步。
回到工程中,在代码窗口中选择
WebBrowser1 对象,然后选择
DocumentComplete 的事件处理器。一旦整个 Web 页下载到此浏览器中,即触发该事件:
Private Sub WebBrowser1_DocumentComplete_
(ByVal pDisp As Object, URL As Variant)
End Sub
传递到该事件中的 URL 就是我们导航所至的位置,它在日后确定浏览器所在的页面时将更为有用。WebBrowser 控件有一个属性称为
Document(文档),可将其视为 IHTMLDocument 来处理:
Private Sub WebBrowser1_DocumentComplete(_ ByVal pDisp As Object, URL As Variant)
Dim Doc As IHTMLDocument2
Set Doc = WebBrowser1.Document
//下一步:分析该文档
End Sub
较新的 IHTMLDocument2 具有 IHTMLDocument 中无法使用的特性。可对系统使用 IHTMLDocument 替代老版本的 Internet Explorer,如果您有勇气的话,甚至可以使用 IHTMLDocument3。补充说明一下,我们假设您已经导航到 Word 文档或 XML 文档,而非 HTML 文档。不要将变量
doc 声明为 IHTMLDocument2,可将其声明为 Word 的
文档或 XML 的
DOMDocument。
在进行下一步之前,理解 HTML 文档的结构是非常重要的。和 XML 不一样,HTML 文档的组合有一定的自由度。例如,您会遇到未关闭标记的 HTML 文档。HTML 文档确实有某种结构。结构好的 HTML 文档通常具有下列元素:
header information like the
elements like and and
请注意 HTML 的树状结构。标记包含标记又包含标记,如此等等。特别是,每一个标记元素都包含一个
0 到 n 个标记元素的集合。
标记可以包含
标记。每个
标记可以包含
标记,后者又可以包含其他标记如锚或图像等。
现在,分析整个
http://moneycentral.msn.com/,并在带 MSFT 符号的页填上第二个
标记。然后,调用此窗体上的
提交:
Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant)
Dim doc As IHTMLDocument2
Set doc = WebBrowser1.Document
If URL = _
"http://moneycentral.msn.com/home.asp" Then
'填充带输入标记的元素集合
Dim Inputs As IHTMLElementCollection
Set Inputs = doc.All.tags("INPUT")
'选择第一个输入标记
Dim Element As IHTMLElement
Set Element = Inputs.Item(1, 1)
'使用正确的界面
Dim InputElement As IHTMLInputElement
Set InputElement = Element
InputElement.Value = Text1.Text
'调用此页第一个窗体上的提交
doc.Forms.Item(0, 0).submit
End Sub
在此您会看到,标记集合如何包含可视为其特定类型的标记。每一个标记都可用 IHTMLElement 界面表示,或用指定为该标记类型的界面表示。例如,
标记可用 IHTMLTableElement 或 IHTMLElement 表示。
标记的集合都包含下列重要的方法和属性:
长度。可将其理解为
计数,或集合中项目的数量。
项目。用于选择集合中的特殊元素。“项目”有两个参数,第二个参数即命名的标记。
标记。将要过滤的元素传递给标记。标记 ("A") 将返回集合内所有锚的集合。要想有效地拆取页,就需要学会使用标记集合。
现在可能您会问,“为什么不直接转到
http://moneycentral.msn.com/scripts/webquote.dll?ipage=qd&Symbol=msft?”当然是可以的,但这个例子告诉大家如何在更复杂的情况下操纵 HTML 窗体。
如果您未做进一步的改动即运行该程序,就会注意到它将陷入无休止的循环,没完没了地下载同一个页面。程序不断地寻找要填充的窗体,并反复调用
DocumentComplete。要修正这个缺陷,应在
DocumentComplete 中置入一些逻辑,告诉分析器,只有在正确的页面上才提交窗体。
接下来,让我们放入这个逻辑,并引入实际的股票报价。另外,我们不捕获文本框中的 URL,而是捕获股票符号:
Private Sub Command1_Click()
WebBrowser1.Navigate _
"http://moneycentral.msn.com/home.asp"
End Sub
Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant)
Dim doc As IHTMLDocument2
Set doc = WebBrowser1.Document
If URL = "http://moneycentral.msn.com/home.asp" Then
'填充带输入标记的元素集合
Dim Inputs As IHTMLElementCollection
Set Inputs = doc.All.tags("INPUT")
'选择第一个输入标记
Dim Element As IHTMLElement
Set Element = Inputs.Item(1, 1)
'使用正确的界面
Dim InputElement As IHTMLInputElement
Set InputElement = Element
InputElement.Value = Text1.Text
'调用该页第一个窗体上的提交
doc.Forms.Item(0, 0).submit
ElseIf URL = _
"http://moneycentral.msn.com/scripts/webquote.dll?ipage=qd&Symbol=" _
& Text1.Text Then
Dim Tables As IHTMLElementCollection
Set Tables = doc.All.tags("TABLE")
'获得第 14 个表的第二个项目(基于 0)
Dim Quote As IHTMLElement
Set Quote = _
Tables.Item(14, 14).All.tags("TD").Item(2, 2)
'显示开始标记和结束标记之间的文本
MsgBox Quote.innerText
End If
End Sub
图 3. 到了这最后一步,自定义的浏览器已被转入有效的 Web 拆取器。重要的是,要注意有了 IHTMLElement 之后获得文本的可用选项。有 4 个属性:
innerText:开始标记和结束标记之间的文本。
innerHTML:开始标记和结束标记之间的文本和 HTML。
outerText:对象的文本。
outerHTML:对象的文本和 HTML。
还要注意从
第 4 个表(基于 0)的
第 11 个元素中检索到的最终报价字符串。如果 MoneyCentral? 决定重新调整该页怎么办?您最好的策略是根据合理的假定来查询页面。如果您
知道报价
几乎总是放在新闻标题的前面,那么就从新闻标题往回查询那个表。还有一种策略是,当更改页面的格式时,有一种简单的方法来更新分析器。一种方法就是将分析的职能细分为较小的组件。每个组件可以实现一个预定义的界面,接受要分析的 IHTMLDocument。与实际的 Web 页失去同步的分析组件可被替换。这样带来的好处是,多个编程人员都可以编写分析器,只需给定要实现的界面和要拆取的 Web 站点即可。
为了避免复杂,将 IHTMLDocument 从
DocumentComplete 函数传递 COM DLL,后者可以分析 IHTMLDocument 并返回想要的有效负载。这有利于程序的模块化,并易于更新与 Web 站点失去同步的分析部分。它还使多个开发者能同时处理这个项目,因为他们有一个干净的界面来编写分析器。
在把新的程序推向市场以前,还有几个实际问题要考虑。首先,很可能 MoneyCentral 和其他许多站点不愿意别人下载他们的内容,也不喜欢看广告。您可能得与摘取其内容的站点签订一份协议。
还有很重要的一点要注意,即如果您是 Web 站点的操作员,那么还有更好的办法将您的内容提供给其他系统。虽然可以让其他人来拆取您的 Web 页,但这仍很笨拙。还有一个更好的方法是,提供 XML 来表现内容。并且,随着 XML 被广泛采用,Web 站点开始提供其数据的 XML 表现形式以及 HTML 界面,也不值得大惊小怪。在这样的时刻到来之前,您也许还得拆取 Web 页。Web 页的拆取往往失之笨拙,但 Microsoft HTML 分析器可令其稍微好一些。