2009-05-25 11 views
8

Tentativo di analizzare un documento HTML ed estrarre alcuni elementi (eventuali collegamenti a file di testo).Analisi del documento HTML: espressione regolare o LINQ?

La strategia corrente consiste nel caricare un documento HTML in una stringa. Quindi trova tutte le istanze di link a file di testo. Potrebbe essere qualsiasi tipo di file, ma per questa domanda, è un file di testo.

L'obiettivo finale è disporre di un elenco di oggetti stringa IEnumerable. Quella parte è facile, ma l'analisi dei dati è la domanda.

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

Gli approcci iniziali sono:

  • carico la stringa in un documento XML, e attaccano in modo LINQ to XML.
  • creare un'espressione regolare, a cercare una stringa che inizia con href=, e termina con .txt

la domanda che:

  • cosa sarebbe quello sguardo regex come? Sono un principiante regex e questo fa parte del mio apprendimento regex.
  • quale metodo useresti per estrarre un elenco di tag?
  • quale sarebbe il modo più performante?
  • quale metodo sarebbe il più leggibile/manutenibile?


Aggiornamento: Complimenti per Matthew su suggerimento HTML Agility pacchetto. Ha funzionato bene! Anche il suggerimento XPath funziona. Vorrei poter contrassegnare entrambe le risposte come "La risposta", ma ovviamente non posso. Sono entrambe valide soluzioni al problema.

Ecco un'app per console C# che utilizza la regex suggerita da Jeff. Legge bene la stringa e non include alcun href che non è terminato con .txt. Con l'esempio fornito, NON include correttamente il file .txt.snarg nei risultati (come previsto nella funzione di stringa 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

Sei aperto all'utilizzo di un parser HTML Open Source? – Jeff

+0

@JD: assolutamente! Come suggerito da Matthew, l'HTML Agility Pack sembra degno di uno sguardo. Stavi per suggerire questo o quello? –

+1

@Philoushka Stavo per suggerire HTML Agility Pack ... si scatena. – Jeff

risposta

1

mi sento di raccomandare regex. Perché?

  • flessibile (caso-insensibilità, facile da aggiungere nuove estensioni di file, gli elementi di controllo , etc.)
  • veloce di scrivere
  • veloce per eseguire

espressioni Regex non sarà difficile da leggere, fino a quando è possibile scrivere espressioni regolari.

usando questo come l'espressione regolare:

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

Spiegazione:

  • Ha parentesi attorno al nome file, che si tradurrà in un "gruppo catturati" a cui è possibile accedere a dopo che ogni incontro è stato trovato.
  • Deve sfuggire "". utilizzando il carattere di espressione regolare , una barra rovesciata.
  • Ha per adattarsi a qualsiasi carattere eccetto virgolette: [^ "] finché non trova
    il" .txt"

si traduce in una stringa di escape come questa:

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

Poi si può iterare le partite:

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: questo è un eccellente esempio di codice. Grazie per l'input! –

+4

Questo corrisponderà a .txt ovunque nell'href, quando l'OP ha esplicitamente detto "termina con". A mio avviso, l'espressione regolare è inappropriata qui. –

+0

@Matthew: No, verrà abbinato solo a un finale HREF con (.txt ") .Non credo che HREF contenga citazioni nel mezzo. –

12

Nessuno dei due. Caricalo in un MLDocument (X/HT) e usa XPath, che è un metodo standard per manipolare XML e molto potente. Le funzioni da osservare sono SelectNodes e SelectSingleNode.

Poiché si utilizza apparentemente HTML (non XHTML), è necessario utilizzare HTML Agility Pack. La maggior parte dei metodi e delle proprietà corrispondono alle classi XML correlate.

applicazione di esempio utilizzando 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 mi ha dato ciò di cui avevo bisogno in circa 5 minuti di implementazione. È arrivato con campioni e fonte. Complimenti a Simon Mourier! –

+0

Ora è disponibile anche il supporto per "LINQ to HTML" nel pacchetto Agility. –

0

alternativa al suggerimento di Matthew Flaschen, DOM (ad es. ? Se si soffre di una X scoppio L allergia)

Si ottiene una cattiva reputazione a volte - credo perché le implementazioni sono divertenti a volte, e le interfacce COM nativi sono un po 'ingombrante, senza un po' di (minore) di smart aiutanti, ma L'ho trovato un modo robusto, stabile e intuitivo/esplorabile per analizzare e manipolare l'HTML.

+2

stai davvero suggerendo di usare il parser HTML di IE da .NET tramite l'interoperabilità COM? .... –

+0

oh aspetta, ha detto "C#" .... In tal caso, noooo. – peterchen

0

REGEX non è veloce, in realtà è più lento del roba di analisi delle stringhe nativo in .NET. Non credermi, vedi di persona.

Nessuno degli esempi precedenti è più veloce di andare direttamente al DOM.

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