Hola buenas,
Necesito obtener estos datos y de forma ordenada, en el mismo orden de la tabla.
Por ejemplo tengo esta tabla (que he simplificado):<html>
<div id="contenedor-portadilla">
<table class="TablaPaises" id="calsificacion_completa">
<thead>
<tr class="Estadistica">
<th colspan="2" class="sinfondo"> </th>
<th colspan="1">Totales</th>
</tr>
</thead>
<tbody><tr>
<td class="Num">1</td>
<td class="Pais">España</td>
</tr>
<tr>
<td class="Num">2</td>
<td class="Pais">Portugal</td>
</tr>
</tbody></table></html>
Quedaría así:
Totales
1 España
2 Portugal
Lo he intentado cargando la página en webbrowser y usando webbrowser.document ... y sus propiedades, el tagname, y eso pero solo he conseguido un listado del <td> sin ningún orden y todo mezclado.
yo quiero poder conseguir los datos a voluntad para mostrarlo en un ListView
Agradezco cualquier ayuda.
Sl2
Ve analizando la tabla. Buscas las etiquetas tr. Dentro de las tr, localizas las td. Al localizar las td, coges el contenido. Y así con todas las columnas. Basta ir comparando hasta encontrar la cadena deseada (teniendo cuidado de no buscar dentro de comillas).
Si la respuesta es XHMTL lo más fácil puede ser cargarlo como XmlDocument (creo que se llamaba así la clase) y mediante XPath obtener los nodos td
Si el resultado los cargas en un List (o Array), no recuerdo ahora puedes usar el método Sort para ordenar.
Un saludo,
Un método eficiente para parsear un documento HTML es haciendo uso de la librería (gratuita) profesional HtmlAgilityPack, esta se basa en la utilización de expresiones XPATH.
En este ejemplo creo una (simple) Class llamada RowData que servirá para mantener una colección rehutilizable con los datos obtenidos de la tabla:
Imports HtmlAgilityPack
Public Class Form1
Dim htmlCode As XElement =
<html>
<div id="contenedor-portadilla">
<table class="TablaPaises" id="calsificacion_completa">
<thead>
<tr class="Estadistica">
<th colspan="2" class="sinfondo"></th>
<th colspan="1">Totales</th>
</tr>
</thead>
<tbody>
<tr>
<td class="Num">1</td>
<td class="Pais">España</td>
</tr>
<tr>
<td class="Num">2</td>
<td class="Pais">Portugal</td>
</tr>
</tbody>
</table>
</div>
</html>
Public Class RowData
Public Property Index As Integer
Public Property Country As String
End Class
Private Sub Test() Handles MyBase.Load
Dim doc As New HtmlDocument
doc.LoadHtml(htmlCode.ToString)
Dim tColumnName As String =
doc.DocumentNode.SelectSingleNode(".//table[@class='TablaPaises']/thead/tr[@class='Estadistica']/th[@colspan='1']").InnerText
Dim tRows As IEnumerable(Of RowData) =
From node As HtmlNode In doc.DocumentNode.SelectNodes(".//table[@class='TablaPaises']/tbody/tr")
Select New RowData With
{
.Index = CInt(node.SelectSingleNode(".//td[@class='Num']").InnerText),
.Country = node.SelectSingleNode(".//td[@class='Pais']").InnerText.Trim(" "c)
}
For Each row As RowData In tRows
Debug.WriteLine(String.Format("Num: {0}, País: {1}", CStr(row.Index), row.Country))
Next row
End Sub
End Class
De todas formas, cómo te ha comentado @fran800m puedes parsear un objeto de tipo XElement (o XDocument), puedes utilizar una sintaxis parecida a esta:
Dim value As String = htmlCode.<Nombre De Elemento 1>.<Nombre De Elemento 2>.<etc...>.<@Nombre de Atributo>.Value
También puedes usar las funciones built-in de dicha Class XElement para buscar descendientes, ancestros, enumerar las expresiones XPATH disponibles, etc.
Saludos!
Cuantas respuestas, muchas gracias :D
Estos días he tenido problemas con la compu, concrectamente con la concexión a Internet (estuve haciendo cosas raras...), pero ya está arreglado. A causa de ello no he podido ver vuestras respuestas. Las vi ayer, pero he estado terminando de recuperar el sistema.
Ahora probaré el código de Elektro, a ver que tal.
Sl2
De nuevo gracias por las respuestas.
Me he descargado Html Agility Pack, pero antes de usarlo he buscado la forma de no depender de librerías externas. He encontrado esta forma usando System.Windows.Forms.HtmlDocument y algo tan viejo como la función InStr.
Antes de usar Instr es obtener las líneas <td class="Pais">*</td> mediante miembro OuterHtml de HtmlElement, luego mediante Instr determinar si estamos en "Num" o "Pais" y en cuyo caso se obtiene el InnerText, que sería el número o país.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim doc As HtmlDocument = WebBrowser1.Document
Dim divs As HtmlElementCollection = doc.GetElementsByTagName("div")
Dim Tipo As String
Label1.Text = ""
For Each div As HtmlElement In divs
If div.GetAttribute("id") = "contenedor-portadilla" Then
Dim tables As HtmlElementCollection = doc.GetElementsByTagName("table")
For Each table As HtmlElement In tables
Dim tds As HtmlElementCollection = table.GetElementsByTagName("td")
Dim tdText As String = String.Empty
For Each td As HtmlElement In tds
tdText = td.InnerText
Tipo = td.OuterHtml
'Obtiene la columna "num"
' If InStr(Tipo, "num", CompareMethod.Text) > 0 Then
' Label1.Text = Label1.Text & tdText & vbCrLf
' End If
'Obtiene la columna "pais"
If InStr(Tipo, "Pais", CompareMethod.Text) > 0 Then
Label1.Text = Label1.Text & tdText & vbCrLf
End If
Next
'Obtiene la cabezera "Totales"
Dim ths As HtmlElementCollection = table.GetElementsByTagName("th")
Dim thText As String = String.Empty
For Each th As HtmlElement In ths
thText = th.InnerText
Label1.Text = Label1.Text & thText & vbCrLf
Next
Next
End If
Next
End Sub
Ignoraba que hacer esto se llama "parsear html", este termino me ha venido muy bien a la hora de buscar en la red sobre este tema y he encontrado otras formas que más adelante probaré.
Sl2
Cita de: okik en 17 Junio 2015, 13:24 PMHe encontrado esta forma usando System.Windows.Forms.HtmlDocument y algo tan viejo como la función InStr.
El código que has mostrado está bastante vb6-estilizado.
En lugar de la función InStr, puedes utilizar la función String.IndexOf()
En lugar de la constante vbCrLf, puedes usar la constante ControlChars.CrLf
Saludos!
Cita de: Eleкtro en 17 Junio 2015, 15:32 PM
El código que has mostrado está bastante vb6-estilizado.
En lugar de la función InStr, puedes utilizar la función String.IndexOf()
En lugar de la constante vbCrLf, puedes usar la constante ControlChars.CrLf
Saludos!
Gracias elektro, por la información. No sabía lo de ControlChars.Crlf, pero si uso vbCrlf ¿tampoco pasa nada no?. Quiero decir, que no afecta en nada al programa, supongo. Es más, se me ocurre que también se podría usar ChrW(13).
En cuanto a lo de String.IndexOf, eso si que lo sabía. Ahora me doy cuenta que también sirve :P
CitarEs más, se me ocurre que también se podría usar ChrW(13).
El mismo "error" de nuevo. puedes utilizar la función
Convert.ToChar.
Cita de: okik en 18 Junio 2015, 19:13 PMpero si uso vbCrlf ¿tampoco pasa nada no?. Quiero decir, que no afecta en nada al programa, supongo.
No, en realidad no pasa nada ...o según se mire,
los wrappers de vb6 siguen siendo puro código .Net, pero la razón de que existan es por compatibilidad, esto quiere decir que un buen día podrían dejar de existir,
pero, yo te lo comento más que nada por que simplemente el hecho de recurrir a las viejas técnicas o nombres de funciones de VB6 no es algo bueno ya que de alguna manera te estancas en esa tecnología de hace ya casi 2 décadas, y entonces no sigues buenas prácticas de programación en .Net, ya que esto es .Net, no VB6,
y, aunque de todas formas sea puro código .Net cómo ya he dicho, si vieras el código fuente de esos wrappers (mediante Reflection) es bastante horrible en comparación con el resto de sus equivalentes funciones built-in que no forman parte del namespace
Microsoft.VisualBasic,
por ende, te recomiendo no utilizar practicamente ningún miembro que forme parte del namespace
Microsoft.VisualBasic, es decir, ningún nombre de función que conozcas de Vb6 plus las constantes y todo eso, ya que todo tiene su equivalente en otras clases más "seguras" y optimizadas de .Net.
PD: En mi opinión, es una mala costumbre que les pasa a muchas personas, bien por que hacen la transición de vb6 a .net y preservan sus costumbres, o también a iniciados en VB.Net que hacen copy/pastes de Google, ya que en la mayoría de respuestas que encuentres en Google solo verás morralla vb6-estilizada de programadores que siguen este tipo de prácticas con VB.Net.
Saludos!
Bien Elektro, seguiré tu consejo.
Al final he encontrado una manera más elegante de obtener el listado de la tabla. Curiosamente al intentar ayudar a otro usuario que trata de loguear y accionar un botón de una web. Antes intentaba encontrar conseguir el atributo "class" y no funcionaba, pues resulta que se llama usando el término "classname".
Dim doc As HtmlDocument = WebBrowser1.Document
Dim divs As HtmlElementCollection = doc.GetElementsByTagName("td")
For Each div As HtmlElement In divs
If div.GetAttribute("classname") = "Pais" Then
ListBox1.Items.Add(div.InnerText)
End If
Next
y otra forma, esta. Pero solo obtiene el primero, porque no repasa cada elemento
Dim ele = webbrowser1.Document.GetElementsByTagName("td").Cast(Of HtmlElement).First(Function(el) el.GetAttribute("classname") = "Pais")
ListBox1.Items.Add(ele.InnerText)
y aunque en el ejemplo uso el control Webbrowser1 se puede crear una variable objeto como un webbrowser