有没有办法在C#中使用Windows Form Application获取突出显示文本的Xpath位置?

时间:2016-01-18 16:33:47

标签: c# visual-studio xpath web-scraping webbrowser-control

我正在尝试使用Windows窗体应用程序创建一个webscraper工具。是否有API或方法显示所选文本的确切xpath位置?到目前为止,我编写的代码允许我在webbrowser控件中的导航网站上突出显示文本,并使用ContextMenuStrip将此突出显示的文本输出到richtextbox中。

我在下面写的代码是:

 private void getSelectedTextToolStripMenuItem_Click(object sender, EventArgs e)
    {

        IHTMLDocument2 htmlDocument = webBrowser1.Document.DomDocument as IHTMLDocument2;
        IHTMLSelectionObject currentSelection = htmlDocument.selection;
        if (currentSelection != null)
        {
            IHTMLTxtRange range = currentSelection.createRange() as IHTMLTxtRange;
            if (range != null)
            {
                richTextBox1.Text = range.htmlText;
            }

按钮导航到以下网站:

private void button1_Click(object sender, EventArgs e)
    {
        this.webBrowser1.Navigate("https://uk.finance.yahoo.com/q?s=%5EFTSE");
        webBrowser1.DocumentCompleted +=
        webBrowser1_DocumentCompleted;          
    }

到目前为止,它完全符合我的要求。但是,我现在希望获得突出显示的任何内容的xpath位置,而不是仅输出文本内容。我的想法是,如果我想提取实时数据(即Yahoo Finance网页上的市场数据),网站上的数据会不断变化,所以我有兴趣获得html页面结构中的位置。关于这是否可能以及我应该遵循哪些步骤的任何想法?

1 个答案:

答案 0 :(得分:1)

这是可能的,但你必须通过从所选元素上升层次结构来自己构建XPath,通过执行以下操作:

private void getSelectedXPathToolStripMenuItem_Click(object sender, EventArgs e)
{
    var doc = (IHTMLDocument2)webBrowser1.Document.DomDocument;
    IHTMLElement selectedElement = null;
    var sel = doc.selection;
    if (sel.type == "Text")
        selectedElement = ((IHTMLTxtRange)sel.createRange()).parentElement();
    else if (sel.type == "Control")
        selectedElement = ((IHTMLControlRange)sel.createRange()).commonParentElement();

    var node = (IHTMLDOMNode)selectedElement;
    MessageBox.Show(GetXPath(node, true));
}

string GetXPath(IHTMLDOMNode node, bool stopAtId)
{
    var path = new Stack<string>();
    while (node != null && node as IHTMLDocument2 == null)
    {
        var index = 0;
        // find previous siblings with the same tag name
        var prev = node.previousSibling;
        while (prev != null)
        {
            if (prev.nodeType == 1 && prev.nodeName == node.nodeName)
                index++;
            prev = prev.previousSibling;
        }
        var showIndex = index > 0;
        // if there were none, find if there are any next siblings with the same tag name
        var next = node.nextSibling;
        while (next != null)
        {
            if (next.nodeType == 1 && next.nodeName == node.nodeName)
            {
                showIndex = true;
                break;
            }
            next = next.nextSibling;
        }
        var id = ((IHTMLDOMAttribute2)((IHTMLAttributeCollection2)node.attributes).getNamedItem("id")).value;
        if (id != string.Empty)
        {
            showIndex = false;
        }
        var part = node.nodeName + (showIndex ? string.Format("[{0}]", index + 1) : string.Empty) + (id != string.Empty ? string.Format("[@id = '{0}']", id) : string.Empty);
        if (id != string.Empty && stopAtId)
            part = "/" + part;
        path.Push(part);
        if (id != string.Empty && stopAtId)
            break;
        node = node.parentNode;
    }

    return "/" + string.Join("/", path);
}

在这个例子中,我创建了一个名为getSelectedXPathToolStripMenuItem_Click的新方法,该方法对应于上下文菜单中的新菜单项,以在消息框中的选择中显示XPath。显然,如果需要,您可以将其更改为将其放入RTB中。

主要工作在GetXPath方法中完成,该方法执行DOM遍历。它会检查以前的兄弟节点,以确定节点的索引,如果它是具有该名称的第一个兄弟节点,它也会检查下一个兄弟节点,以查看是否应该包含索引(1)。

它还接受一个名为stopAtId的布尔参数,顾名思义,当节点设置了id属性时,它将停止遍历DOM。这可能很有用,因为你总是可以通过它的id很容易地找到一个元素,而不需要知道它的祖先等等。