Leer datos de una tabla html

Iniciado por okik, 13 Junio 2015, 13:26 PM

0 Miembros y 1 Visitante están viendo este tema.

okik

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):
Código (html4strict) [Seleccionar]
<html>
<div id="contenedor-portadilla">
<table class="TablaPaises" id="calsificacion_completa">
<thead>
<tr class="Estadistica">
<th colspan="2" class="sinfondo">&nbsp;</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




ivancea96

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).

fran800m

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,

Eleкtro

#3
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:

Código (vbnet) [Seleccionar]
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:

Código (vbnet) [Seleccionar]
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!








okik

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

okik

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.


Código (vbnet) [Seleccionar]
     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
     

Eleкtro

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!








okik

#7
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

Eleкtro

#8
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!








okik

#9
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".


Código (vbnet) [Seleccionar]
       
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

Código (vbnet) [Seleccionar]
    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