web-dev-qa-db-ja.com

WebClient.DownloadString()は、特殊な文字を含む文字列を返します

私が作成しているスクリーンスクレイピングツールのためにWebからダウンロードしているコンテンツに問題があります。

以下のコードでは、Webクライアントのダウンロード文字列メソッドから返された文字列は、いくつか(すべてではない)のWebサイトのソースダウンロードに対して奇妙な文字を返します。

最近、以下のようにhttpヘッダーを追加しました。以前は、ヘッダーなしで同じコードが同じ効果のために呼び出されていました。 「Accept-Charset」ヘッダーのバリエーションを試したことはありません。基本以外のテキストエンコーディングについてはあまり知りません。

私が参照する文字または文字シーケンスは次のとおりです。

"ï"¿"

そして

"Â"

これらの文字は、Webブラウザーで「ソースの表示」を使用する場合は表示されません。これの原因は何ですか?また、どのように問題を修正できますか?

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);
62
gb2d

は、オクテットEF BB BFのwindows-1252表現です。これは TF-8バイトオーダーマーカー です。これは、リモートWebページがUTF-8でエンコードされているが、windows-1252のように読んでいることを意味します。 ドキュメントによればWebClient.DownloadStringは、リモートリソースを文字列に変換するときに、エンコードとして Webclient.Encoding を使用します。 System.Text.Encoding.UTF8に設定すると、理論的には動作するはずです。

99
dkarp

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

...以上です。

48
Konamiman
var client = new WebClient { Encoding = System.Text.Encoding.UTF8 };

var json = client.DownloadString(url);
12
Sanket Patel

私の場合、返されたデータはgzipで圧縮されており、最初に解凍する必要があったため、この回答は役に立ちました。

https://stackoverflow.com/a/34418228/74585

1
Matthew Lock

私の場合、ユーザーエージェントと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");
0
bh_earth0

「www.yahoo.com」などの特別なWebサイトでは、それらのどれも機能しませんでした。問題を解決する唯一の方法は、DownloadStringOpenReadに変更し、サンプルコードのような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();
0
Siamak Ferdos