2010-04-23 18 views
9

Korzystanie poniższy kod można pobrać kod HTML pliku z internetu:znaków w łańcuchu zmieniła po pobraniu HTML z Internetu

WebClient wc = new WebClient(); 

// .... 

string downloadedFile = wc.DownloadString("http://www.myurl.com/"); 

Jednak czasami plik zawiera „ciekawe” znaki takie jak é do é, do ↠i フシギダネ do フシギダãƒ.

Myślę, że może to mieć coś wspólnego z różnymi typami unicode lub czymś, ponieważ każda postać zostaje zmieniona na dwie nowe, być może każda postać jest podzielona na pół, ale mam bardzo małą wiedzę w tej dziedzinie. Jak myślisz, co jest nie tak?

+1

Serwer prawdopodobnie zwraca błędne kodowanie w nagłówku 'Content-Type'. – dtb

+4

Powinieneś przeczytać [ten artykuł] (http://www.joelonsoftware.com/articles/Unicode.html), aby uzyskać podstawową wiedzę na temat Unicode. Omówimy wszystkie powody, dla których niektóre elementy pojawiają się jako dwa, na przykład. Ale co najważniejsze, pomoże ci zrozumieć podstawy, które musisz znać o Unikodzie. –

+1

To całkiem na pewno HTML UTF-8 widziany w ISO-8859-1 lub innym kodowaniu jednobajtowym. –

Odpowiedz

43

Oto zawinięta klasa pobierania, która obsługuje gzip i sprawdza kodowanie nagłówka i metatagów w celu ich prawidłowego odkodowania.

Utwórz instancję klasy i zadzwoń pod numer GetPage().

public class HttpDownloader 
{ 
    private readonly string _referer; 
    private readonly string _userAgent; 

    public Encoding Encoding { get; set; } 
    public WebHeaderCollection Headers { get; set; } 
    public Uri Url { get; set; } 

    public HttpDownloader(string url, string referer, string userAgent) 
    { 
     Encoding = Encoding.GetEncoding("ISO-8859-1"); 
     Url = new Uri(url); // verify the uri 
     _userAgent = userAgent; 
     _referer = referer; 
    } 

    public string GetPage() 
    { 
     HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url); 
     if (!string.IsNullOrEmpty(_referer)) 
      request.Referer = _referer; 
     if (!string.IsNullOrEmpty(_userAgent)) 
      request.UserAgent = _userAgent; 

     request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate"); 

     using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 
     { 
      Headers = response.Headers; 
      Url = response.ResponseUri; 
      return ProcessContent(response); 
     } 

    } 

    private string ProcessContent(HttpWebResponse response) 
    { 
     SetEncodingFromHeader(response); 

     Stream s = response.GetResponseStream(); 
     if (response.ContentEncoding.ToLower().Contains("gzip")) 
      s = new GZipStream(s, CompressionMode.Decompress); 
     else if (response.ContentEncoding.ToLower().Contains("deflate")) 
      s = new DeflateStream(s, CompressionMode.Decompress); 

     MemoryStream memStream = new MemoryStream(); 
     int bytesRead; 
     byte[] buffer = new byte[0x1000]; 
     for (bytesRead = s.Read(buffer, 0, buffer.Length); bytesRead > 0; bytesRead = s.Read(buffer, 0, buffer.Length)) 
     { 
      memStream.Write(buffer, 0, bytesRead); 
     } 
     s.Close(); 
     string html; 
     memStream.Position = 0; 
     using (StreamReader r = new StreamReader(memStream, Encoding)) 
     { 
      html = r.ReadToEnd().Trim(); 
      html = CheckMetaCharSetAndReEncode(memStream, html); 
     }    

     return html; 
    } 

    private void SetEncodingFromHeader(HttpWebResponse response) 
    { 
     string charset = null; 
     if (string.IsNullOrEmpty(response.CharacterSet)) 
     { 
      Match m = Regex.Match(response.ContentType, @";\s*charset\s*=\s*(?<charset>.*)", RegexOptions.IgnoreCase); 
      if (m.Success) 
      { 
       charset = m.Groups["charset"].Value.Trim(new[] { '\'', '"' }); 
      } 
     } 
     else 
     { 
      charset = response.CharacterSet; 
     } 
     if (!string.IsNullOrEmpty(charset)) 
     { 
      try 
      { 
       Encoding = Encoding.GetEncoding(charset); 
      } 
      catch (ArgumentException) 
      { 
      } 
     } 
    } 

    private string CheckMetaCharSetAndReEncode(Stream memStream, string html) 
    { 
     Match m = new Regex(@"<meta\s+.*?charset\s*=\s*""?(?<charset>[A-Za-z0-9_-]+)""?", RegexOptions.Singleline | RegexOptions.IgnoreCase).Match(html);    
     if (m.Success) 
     { 
      string charset = m.Groups["charset"].Value.ToLower() ?? "iso-8859-1"; 
      if ((charset == "unicode") || (charset == "utf-16")) 
      { 
       charset = "utf-8"; 
      } 

      try 
      { 
       Encoding metaEncoding = Encoding.GetEncoding(charset); 
       if (Encoding != metaEncoding) 
       { 
        memStream.Position = 0L; 
        StreamReader recodeReader = new StreamReader(memStream, metaEncoding); 
        html = recodeReader.ReadToEnd().Trim(); 
        recodeReader.Close(); 
       } 
      } 
      catch (ArgumentException) 
      { 
      } 
     } 

     return html; 
    } 
} 
+1

Hej, to po prostu działa. Dzięki. –

+1

Coś, co napisałem w zeszłym roku dla projektu lazurowego :) Cieszę się, że to może być użyteczne dla ciebie. –

+0

Dzięki za podzielenie się tym Mikaelem. Użyłem go i znalazłem problem z wykrywaniem kodowania. Jeśli nagłówki zawierają 'charset' nie powinien sprawdzać metatagu, ponieważ zasady pierwszeństwa jasno stwierdzają, że w przypadku konfliktu nagłówek ma najwyższy priorytet. http://goo.gl/5q0Yg – Diadistis

-4

Spróbuj

string downloadedFile = wc.DownloadString("http://www.myurl.com"); 

ja zawsze dobrze usunąć ostatni "Slash" i pracował aż do teraz jak czar. Ale mogę też być niebezpieczny

+5

Ukośnik na końcu adresu URL nie ma nic wspólnego z kodowaniem. –

2

Ponieważ nie wolno mi komentować (niewystarczająca reputacja), będę musiał wysłać dodatkową odpowiedź. Wielokrotnie używam świetnej klasy Mikaela, ale napotkałem praktyczny problem z wyrażeniem regularnym, które próbuje znaleźć meta-informacje zestawu znaków. Ten

Match m = new Regex(@"<meta\s+.*?charset\s*=\s*(?<charset>[A-Za-z0-9_-]+)", RegexOptions.Singleline | RegexOptions.IgnoreCase).Match(html); 

nie na tym

<meta charset="UTF-8"/> 

niniejsza

Match m = new Regex(@"<meta\s+.*?charset\s*=\s*""?(?<charset>[A-Za-z0-9_-]+)""?", RegexOptions.Singleline | RegexOptions.IgnoreCase).Match(html); 

nie.

Dzięki, Mikael.

+0

Dziękuję. Zmieniono mój kod :) –

Powiązane problemy