2009-05-25 17 views
8

Tratando de analizar un documento HTML y extraer algunos elementos (cualquier enlace a archivos de texto).Analizando documento HTML: ¿Expresión regular o LINQ?

La estrategia actual es cargar un documento HTML en una cadena. Luego encuentre todas las instancias de enlaces a archivos de texto. Podría ser cualquier tipo de archivo, pero para esta pregunta, es un archivo de texto.

El objetivo final es tener una lista IEnumerable de objetos de cadena. Esa parte es fácil, pero analizar la información es la pregunta.

<html> 
<head><title>Blah</title> 
</head> 
<body> 
<br/> 
<div>Here is your first text file: <a href="http://myServer.com/blah.txt"></div> 
<span>Here is your second text file: <a href="http://myServer.com/blarg2.txt"></span> 
<div>Here is your third text file: <a href="http://myServer.com/bat.txt"></div> 
<div>Here is your fourth text file: <a href="http://myServer.com/somefile.txt"></div> 
<div>Thanks for visiting!</div> 
</body> 
</html> 

Los enfoques iniciales son:

  • carga de la cadena en un documento XML y lo atacan de manera LINQ to XML.
  • crear una expresión regular, para buscar una cadena que comienza con href=, y terminando con .txt

la pregunta que:

  • lo que haría que la mirada de expresiones regulares como? Soy un novato de expresiones regulares, y esto es parte de mi aprendizaje de expresiones regulares.
  • qué método usarías para extraer una lista de etiquetas?
  • cuál sería la forma más eficaz?
  • qué método sería el más legible/mantenible?


Actualización: Felicitaciones a Matthew sobre la sugerencia HTML agilidad paquete. ¡Funcionó bien! La sugerencia de XPath también funciona. Desearía poder marcar ambas respuestas como "La respuesta", pero obviamente no puedo. Ambas son soluciones válidas para el problema.

Aquí es una aplicación de consola de C# usando la expresión regular sugerido por Jeff. Lee bien la cadena, y no incluirá ningún href que no termine con .txt. Con la muestra dada, NO incluye correctamente el archivo .txt.snarg en los resultados (como se proporciona en la función de cadena HTML).

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Text.RegularExpressions; 
using System.IO; 

namespace ParsePageLinks 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      GetAllLinksFromStringByRegex(); 
     } 

     static List<string> GetAllLinksFromStringByRegex() 
     { 
      string myHtmlString = BuildHtmlString(); 
      string txtFileExp = "href=\"([^\\\"]*\\.txt)\""; 

      List<string> foundTextFiles = new List<string>(); 

      MatchCollection textFileLinkMatches = Regex.Matches(myHtmlString, txtFileExp, RegexOptions.IgnoreCase); 
      foreach (Match m in textFileLinkMatches) 
      { 
       foundTextFiles.Add(m.Groups[1].ToString()); // this is your captured group 
      } 

      return files; 
     } 

      static string BuildHtmlString() 
      { 
       return new StringReader(@"<html><head><title>Blah</title></head><body><br/> 
<div>Here is your first text file: <a href=""http://myServer.com/blah.txt""></div> 
<span>Here is your second text file: <a href=""http://myServer.com/blarg2.txt""></span> 
<div>Here is your third text file: <a href=""http://myServer.com/bat.txt.snarg""></div> 
<div>Here is your fourth text file: <a href=""http://myServer.com/somefile.txt""></div> 
<div>Thanks for visiting!</div></body></html>").ReadToEnd(); 
      }  
     } 
    } 
+0

¿Está abierto a usar un analizador HTML de código abierto? – Jeff

+0

@JD: ¡absolutamente! Como sugirió Matthew, el HTML Agility Pack suena digno de ver. ¿Ibas a sugerir eso u otro? –

+1

@Philoushka Iba a sugerir HTML Agility Pack ... se mece. – Jeff

Respuesta

1

lo recomiendo expresiones regulares. ¿Por qué?

  • flexible (mayúsculas y falta de sensibilidad, fácil de añadir nuevas extensiones de archivo, elementos para cheque, etc.)
  • rápida de escribir
  • rápido para ejecutar

expresiones expresiones regulares no serán difíciles de leer, siempre y cuando se puede escribir expresiones regulares.

usando esto como la expresión regular:

href="([^"]*\.txt)"

Explicación:

  • Tiene paréntesis alrededor del nombre de archivo , lo que resultará en un "grupo capturado" que puede acceder al después de encontrar cada coincidencia.
  • Tiene que escapar de la "." mediante el uso del carácter de escape de expresiones regulares, una barra diagonal inversa.
  • que tiene que coincidir con cualquier carácter excepto comillas dobles: [^ "] hasta que encuentra
    el" .txt"

que se traduce en una cadena escapada así:

string txtExp = "href=\"([^\\\"]*\\.txt)\" 

a continuación, puede iterar sobre sus Partidos:

Matches txtMatches = Regex.Matches(input, exp, RegexOptions.IgnoreCase); 
foreach(Match m in txtMatches) { 
    string filename = m.Groups[1]; // this is your captured group 
} 
+1

@Jeff: esta es una excelente muestra de código. ¡Gracias por la aportación! –

+4

Eso coincidirá con .txt en cualquier parte de la href, cuando el OP dijo explícitamente que "termina con". En mi opinión, regex es inapropiado aquí. –

+0

@Matthew: No, solo coincidirá con un HREF que termine con (.txt "). No creo que HREF contenga comillas en el medio. –

12

Ninguno. Cargarlo en un MLDocument (X/HT) y usar XPath, que es un método estándar de manipulación de XML y muy potente. Las funciones a mirar son SelectNodes y SelectSingleNode.

Puesto que usted está utilizando aparentemente HTML (no XHTML), se debe utilizar HTML Agility Pack. La mayoría de los métodos y propiedades coinciden con las clases XML relacionadas.

aplicación de muestras mediante XPath:

HtmlDocument doc = new HtmlDocument(); 
    doc.Load(new StringReader(@"<html> 
<head><title>Blah</title> 
</head> 
<body> 
<br/> 
<div>Here is your first text file: <a href=""http://myServer.com/blah.txt""></div> 
<span>Here is your second text file: <a href=""http://myServer.com/blarg2.txt""></span> 
<div>Here is your third text file: <a href=""http://myServer.com/bat.txt""></div> 
<div>Here is your fourth text file: <a href=""http://myServer.com/somefile.txt""></div> 
<div>Thanks for visiting!</div> 
</body> 
</html>")); 
     HtmlNode root = doc.DocumentNode; 
     // 3 = ".txt".Length - 1. See http://stackoverflow.com/questions/402211/how-to-use-xpath-function-in-a-xpathexpression-instance-programatically 
     HtmlNodeCollection links = root.SelectNodes("//a[@href['.txt' = substring(., string-length(.)- 3)]]"); 
    IList<string> fileStrings; 
    if(links != null) 
    { 
     fileStrings = new List<string>(links.Count); 
     foreach(HtmlNode link in links) 
     fileStrings.Add(link.GetAttributeValue("href", null)); 
    } 
    else 
     fileStrings = new List<string>(0); 
+2

@Matthew: HTML Agility Pack me dio lo que necesitaba en aproximadamente 5 minutos de implementación. Venía con muestras y fuente. Felicitaciones a Simon Mourier! –

+0

También ahora hay algo de soporte para "LINQ to HTML" en el paquete Agility. –

0

Como alternativa a la sugerencia de Matthew Flaschen, DOM (p. ? Si usted sufre de una X brote L alergia)

Se pone una mala reputación a veces - supongo que porque implementaciones son divertida a veces, y las interfaces COM nativas son un poco difícil de manejar sin un poco de (menor) ayudantes inteligentes, pero He encontrado una manera robusta, estable e intuitiva/explorable de analizar y manipular HTML.

+2

¿En realidad está sugiriendo que use el analizador HTML de IE de .NET a través de la interoperabilidad COM? .... –

+0

oh espera, dijo "C#" ... En ese caso, noooo. – peterchen

0

REGEX no es rápido, de hecho es más lento que las cosas nativas de análisis de cadenas en .NET. No me creas, compruébalo por ti mismo.

Ninguno de los ejemplos anteriores es más rápido que ir directamente al DOM.

HTMLDocument doc = wb.Document; 
var links = doc.Links; 
Cuestiones relacionadas