【问题标题】:Characters in string changed after downloading HTML from the internet从 Internet 下载 HTML 后,字符串中的字符发生了变化
【发布时间】:2011-02-11 15:21:02
【问题描述】:

使用以下代码,我可以从互联网上下载文件的 HTML:

WebClient wc = new WebClient();

// ....

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

但是,有时文件包含“有趣”的字符,例如 ééâ†フシギダネフシギダãƒ

我认为这可能与不同的 unicode 类型或其他东西有关,因为每个字符都变成了 2 个新字符,也许每个字符都被分成两半,但我在这方面的知识很少。你觉得哪里不对?

【问题讨论】:

  • 服务器可能在Content-Type 标头中返回错误的编码。
  • 您应该阅读this article 以对Unicode 有一些基本的了解。例如,它将涵盖某些项目显示为两个的所有原因。但重要的是,它将帮助您了解需要了解的有关 Unicode 的基础知识。
  • 这个非常肯定的 UTF-8 HTML 在 ISO-8859-1 或其他单字节编码中查看。

标签: c# .net string unicode webclient


【解决方案1】:

这是一个包装好的下载类,它支持 gzip 并检查编码头和元标记以便正确解码。

实例化类,并调用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;
    }
}

【讨论】:

  • 我去年为一个天蓝色项目写的东西 :) 很高兴它对你有用。
  • 感谢分享这个米凯尔。我用过,发现编码检测有问题。如果标头包含charset,则不应检查元标记,因为优先规则明确指出,如果发生冲突,标头具有最高优先级。 goo.gl/5q0Yg
  • 为了解决这个问题,我创建了一个在SetEncodingFromHeader 中设置的encodingFoundInHeader 布尔字段,如果为true,则阻止调用CheckMetaCharSetAndReEncode
  • 这可能是个好主意,但我发现元标记通常比标题更正确。我希望这更容易,并且是 100% 的方法 :)
  • 很棒的答案,但知道(令人困惑)HTTP 'deflate' isn't actually deflate (RFC 1951), but rather zlib (RFC 1950)。除非 .NET DeflateStream 非常宽松,否则它不会正确解压缩 zlib 流(另一方面,服务器也可能错误地发送原始 deflate 流!)。我发现最好不支持 HTTP deflate(作为客户端),以避免歧义。
【解决方案2】:

试试这个

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

我总是删除最后一个“Slash”,它一直工作到现在就像一个魅力。但我也可能是一个危险

【讨论】:

  • 网址末尾的斜杠与编码无关。
【解决方案3】:

由于我不允许发表评论(声誉不足),我将不得不发布一个额外的答案。我经常使用 Mikael 的优秀课程,但我遇到了一个尝试查找字符集元信息的正则表达式的实际问题。这个

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

失败了

<meta charset="UTF-8"/>

而这个

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

没有。

谢谢,米凯尔。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-09
    • 2019-07-12
    • 1970-01-01
    相关资源
    最近更新 更多