【问题标题】:WebClient.DownloadString() returns string with peculiar charactersWebClient.DownloadString() 返回带有特殊字符的字符串
【发布时间】:2011-06-10 15:06:45
【问题描述】:

对于我正在构建的屏幕抓取工具,我们从网络下载的一些内容存在问题。

在下面的代码中,从 web 客户端下载字符串方法返回的字符串为少数(不是所有)网站的源下载返回了一些奇怪的字符。

我最近添加了如下的 http 标头。以前,相同的代码在没有标题的情况下被调用以达到相同的效果。我没有尝试过“Accept-Charset”标头的变体,除了基础知识之外,我对文本编码知之甚少。

我所指的字符或字符序列是:



"一个"

当您在网络浏览器中使用“查看源代码”时,看不到这些字符。这可能是什么原因造成的,我该如何解决这个问题?

string urlData = String.Empty;
WebClient wc = new WebClient();

// Add headers to impersonate a web browser. Some web sites 
// will not respond correctly without these headers
wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12");
wc.Headers.Add("Accept", "*/*");
wc.Headers.Add("Accept-Language", "en-gb,en;q=0.5");
wc.Headers.Add("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");

urlData = wc.DownloadString(uri);

【问题讨论】:

  • 我在使用 C# YUI Compessor 来缩小我的 JS 和 CSS 的 Windows 应用程序中遇到了同样的错误。它会在带有您上面提到的确切字符的文件上引发错误。我指定client.Encoding = Encoding.UTF8; 并且它仍然返回时髦的字符......另外我试图弄清楚如何处理C#YUI Compressor引发的错误,例如[ERROR] Invalid Syntax......
  • 自从我第一次遇到这个问题以来已经有一段时间了,并且从那以后对文本编码有了一些了解。为了帮助您,基本上您需要做的是尝试将来自 http 标头的编码与响应相匹配。从那里使用检测到的编码解码字节流。如果标头中未包含编码,则使用 UTF8 解码,然后在 HTML 文档中查找编码。如果 HTML 文档中仍然没有,则只剩下启发式方法。我已经阅读了各种机制,但这里没有简单的解决方案。
  • 下次有机会我会在这里发布一些代码。
  • 在我的情况下,返回的数据是 gzip 压缩的,必须先解压缩,所以我发现这个答案很有帮助:stackoverflow.com/a/34418228/74585

标签: c# asp.net .net character-encoding special-characters


【解决方案1】:

 是八位字节 EF BB BF 的 windows-1252 表示。那是the UTF-8 byte-order marker,这意味着您的远程网页是用UTF-8 编码的,但您正在阅读它,就好像它是windows-1252。 According to the docsWebClient.DownloadString 在将远程资源转换为字符串时使用Webclient.Encoding 作为其编码。将其设置为System.Text.Encoding.UTF8,理论上应该可以工作。

【讨论】:

  • 谢谢,虽然这会在其他网站上产生问题。现在我看到了一个带有问号的钻石。我想我在 http 标头中指定了一个编码,所以我应该期望从 Web 服务器返回相同的内容?
  • 无论您在标头中指定什么,Web 服务器都可以忽略它并返回任何内容。您必须准备好处理请求 UTF-8 和获取 Windows 编码。
  • 如果您不知道数据将以哪种编码方式返回,您可以放心使用WebClient.DownloadData 获取原始字节。
  • dkarp - 我是否还必须使用编码将字节流转换为可理解的内容(据我所知,无法检测到)?
  • 我实际上在网上找到了对 .net 3.5 中的一个错误的参考,并发现有。 .net 4 中具有相同代码的同一站点不会为该特定站点生成相同的字符序列。我已经尝试使用 webrequest 来代替,它确实会产生不同的结果,但不确定是否一定会更好。
【解决方案2】:

WebClient.DownloadString 的实现方式非常愚蠢。它应该从响应中的Content-Type 标头获取字符编码,但它希望开发人员事先告知预期的编码。我不知道这个类的开发者是怎么想的。

我创建了一个辅助类,它从响应的Content-Type 标头中检索编码名称:

public static class WebUtils
{
    public static Encoding GetEncodingFrom(
        NameValueCollection responseHeaders,
        Encoding defaultEncoding = null)
    {
        if(responseHeaders == null)
            throw new ArgumentNullException("responseHeaders");

        //Note that key lookup is case-insensitive
        var contentType = responseHeaders["Content-Type"];
        if(contentType == null)
            return defaultEncoding;

        var contentTypeParts = contentType.Split(';');
        if(contentTypeParts.Length <= 1)
            return defaultEncoding;

        var charsetPart =
            contentTypeParts.Skip(1).FirstOrDefault(
                p => p.TrimStart().StartsWith("charset", StringComparison.InvariantCultureIgnoreCase));
        if(charsetPart == null)
            return defaultEncoding;

        var charsetPartParts = charsetPart.Split('=');
        if(charsetPartParts.Length != 2)
            return defaultEncoding;

        var charsetName = charsetPartParts[1].Trim();
        if(charsetName == "")
            return defaultEncoding;

        try
        {
            return Encoding.GetEncoding(charsetName);
        }
        catch(ArgumentException ex) 
        {
            throw new UnknownEncodingException(
                charsetName,   
                "The server returned data in an unknown encoding: " + charsetName, 
                ex);
        }
    }
}

UnknownEncodingException 是一个自定义异常类,随意替换为InvalidOperationException 或其他任何你想要的)

那么WebClient 类的以下扩展方法就可以解决问题:

public static class WebClientExtensions
{
    public static string DownloadStringAwareOfEncoding(this WebClient webClient, Uri uri)
    {
        var rawData = webClient.DownloadData(uri);
        var encoding = WebUtils.GetEncodingFrom(webClient.ResponseHeaders, Encoding.UTF8);
        return encoding.GetString(rawData);
    }
}

所以在你的例子中你会这样做:

urlData = wc.DownloadStringAwareOfEncoding(uri);

...就是这样。

【讨论】:

  • 四年后这么好的答案?伙计,正因为如此,你值得我投票,很努力。
  • 我相信这不是真的。 DownloadString 确实使用 Content-Type HTTP 标头中的编码,查看源代码:referencesource.microsoft.com/#System/net/System/Net/…
  • 根据来源,DownloadString 尝试使用来自请求的Content-Type 标头而不是响应来获取字符编码。这就是为什么 Konamiman 的扩展工作正常,而 DownloadString 却不行
【解决方案3】:
var client = new WebClient { Encoding = System.Text.Encoding.UTF8 };

var json = client.DownloadString(url);

【讨论】:

    【解决方案4】:

    对于一些特殊的网站,例如“www.yahoo.com”,它们都不适用于我。我解决问题的唯一方法是将DownloadString 更改为OpenRead 并使用UserAgent 标头,如示例代码。然而,像“www.varzesh3.com”这样的一些网站并没有使用任何方法!

    WebClient client = new WebClient()    
    client.Headers.Add(HttpRequestHeader.UserAgent, "");
    var stream = client.OpenRead("http://www.yahoo.com");
    StreamReader sr = new StreamReader(stream);
    s = sr.ReadToEnd();
    

    【讨论】:

      【解决方案5】:

      就我而言,我删除了与语言、字符集等相关的标题 除了用户代理和 cookie 。成功了..

       // try commenting
       //wc.Headers.Add("Accept-Language", "en-gb,en;q=0.5");
       //wc.Headers.Add("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-28
        • 2014-08-03
        • 2020-07-12
        • 2012-03-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多