2016-01-18 1 views
0

Я пытаюсь создать инструмент webscraper с помощью приложения Windows Form. Есть ли API или метод для отображения точной позиции xpath выбранного текста? До сих пор я написал код, который позволяет выделить текст на навигационном веб-сайте в элементе управления webbrowser и вывести выделенный выделенный текст в richtextbox с помощью ContextMenuStrip.Есть ли способ получить позицию Xpath выделенного текста с помощью приложения Windows Form в C#?

Код я написал ниже:


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

Это возможно, но вы должны будете построить 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, который, как следует из названия, остановит перемещение DOM, когда узел имеет набор атрибутов id. Это может быть полезно, потому что вы всегда можете легко найти элемент по его идентификатору, без необходимости знать что-либо о его предках и т. Д.