【问题标题】:Kanji characters from WebClient html different from actual Kanji in websiteWebClient html 中的汉字字符与网站中的实际汉字不同
【发布时间】:2018-04-15 20:20:11
【问题描述】:

所以,我正在尝试从名为Kanji-A-Day.com 的网站获取部分文本,但我遇到了问题。

你看,我正试图从网站上获取每日汉字,我能够将 HTML 缩小到我想要的范围,但字符似乎不同..?

What it looks like

What it should look like

更奇怪的是,第二张图片的结果是我直接从网站复制粘贴生成的,所以不是字体问题。

这是我用来获取角色的代码:

public void UpdateDailyKanji() // Called at the initialization of a new main form
{
    string kanji;
    using (WebClient client = new WebClient()) // Grab the string 
        kanji = client.DownloadString("http://www.kanji-a-day.com/level4/index.php"); 
    // Trim the HTML to just the Kanji
    kanji = kanji.Remove(0, kanji.IndexOf(@"<div class=""glyph"">") + 19);
    kanji = kanji.Remove(kanji.IndexOf("</div>")-2);
    kanji = kanji.Trim();
    Text_DailyKanji.Text = kanji; // Set the Kanji
}

有人知道这里发生了什么吗?我猜这是一些 Unicode 的东西,但我不太了解它。

提前致谢。

【问题讨论】:

  • 该页面使用charset=EUC-JP 编码(编码日语 (EUC) 代码页 51932,标题名称:euc-jp → Windows 代码页 932)。您必须对生成的文本进行相应的编码,并使用可以处理 Unicode 的字体(嗯,这不是问题)。
  • 所以,我对编码知之甚少,但我假设您是在告诉我将“汉字”转换为 Unicode?如果我错了,请纠正我。
  • 不完全是。您必须将生成的字符串(可能编码为 iso-8859-1 (CodePage 1252))转换为日语 (EUC) unicode 编码。您可以使用 MemoryStream 并将 WebClient 结果复制到其中,然后使用 StreamReader 重新编码流字节。如果你愿意,我可以发布一个例子。
  • @Sergey.quixoticaxis.Ivanov 如果未定义编码,则使用本地系统 CodePage。当源流是外部的时,这不应该发生,特别是如果它来自 Web,Unicode 代码点映射的事实标准是 UTF-8。这就是通常的处理方式,除非指定了特定的字符集(映射)(服务器端或内部 html 元标记)。这就是为什么我说它应该表示为 UTF-8。应该是,但不能假设,因为可能会指定不同的映射,并且在解码时必须考虑这一点。
  • @Sergey.quixoticaxis.Ivanov 我不把它当作一个论点,而是一个讨论。我很感激你指出我的描述不是那么清楚,所以我有机会尝试做一个更好的。而且我不认为自己是一个很好的解释者。我可能不是。所以,谢谢你的评论。

标签: c# html unicode webclient system.net


【解决方案1】:

您尝试以字符串形式下载的页面使用charset=EUC-JP 编码,也称为Japanese (EUC)(代码页51932)。这在页眉中明确设置。

为什么WebClient.DownloadString返回的字符串使用了错误的编码器?

MSDN 文档说明了这一点:

此方法检索指定的资源。下载后 资源,该方法使用编码中指定的编码 属性将资源转换为字符串。

因此,您必须事先知道将使用什么编码并指定它,设置WebClient.Encoding 属性。

要验证这一点,请检查.NET Reference Source for the WebClient.DownloadString 方法:

try {
    WebRequest request;
    byte [] data = DownloadDataInternal(address, out request);
    string stringData = GetStringUsingEncoding(request, data);
    if(Logging.On)Logging.Exit(Logging.Web, this, "DownloadString", stringData);
    return stringData;
    } finally {
        CompleteWebClientState();
    }

编码是使用 Request 设置设置的,而不是 Response 设置。
结果是,下载的字符串使用默认的 CodePage 进行编码。

你现在可以做的是:

  • 页面下载两次,第一次检查WebClient编码和Html页面编码是否不匹配。
  • 使用在底层 WebResponse 中设置的正确编码重新编码字符串。
  • 不要使用WebClient,直接使用HttpClient或者WebRequest。或者,如果您喜欢这个工具,可以创建一个自定义 WebClient 类来以更直接的方式处理 WebRequest/WebResponse。

这是一种执行重新编码任务的方法:
WebClient 返回的字符串转换为字节数组并传递给MemoryStream,然后使用StreamReader 重新编码,编码从Content-Type: charset 响应头中检索。

编辑:
现在使用Reflection 从底层HttpWebResponse 获取页面Encoding。这应该避免在解析远程响应定义的原始CharacterSet 时出错。

using System.IO;
using System.Net;
using System.Reflection;
using System.Text;

public string WebClient_DownLoadString(Uri uri)
{
    using (var client = new WebClient())
    {
        // If Windows 7 - Windows Server 2008 R2
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

        client.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.BypassCache);
        client.Headers.Add(HttpRequestHeader.Accept, "ext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        client.Headers.Add(HttpRequestHeader.AcceptLanguage, "en-US,en;q=0.8");
        client.Headers.Add(HttpRequestHeader.KeepAlive, "keep-alive");

        string result = client.DownloadString(uri);

        var flags = BindingFlags.Instance | BindingFlags.NonPublic;
        using (var response = (HttpWebResponse)client.GetType().GetField("m_WebResponse", flags).GetValue(client))
        {
            var pageEncoding = Encoding.GetEncoding(wc_response.CharacterSet);
            byte[] bytes = client.Encoding.GetBytes(result);
            using (var ms = new MemoryStream(bytes, 0, bytes.Length))
            using (var reader = new StreamReader(ms, pageEncoding))
            {
                ms.Position = 0;
                return reader.ReadToEnd();
            };
        };
    }
}

现在您的代码应该能够以正确的形式获取日文字符。

Uri uri = new Uri("http://www.kanji-a-day.com/level4/index.php", UriKind.Absolute);
string kanji = WebClient_DownLoadString(uri);

kanji = kanji.Remove(0, kanji.IndexOf("<div class=\"glyph\">") + 19);
kanji = kanji.Remove(kanji.IndexOf("</div>")-2);
kanji = kanji.Trim();

Text_DailyKanji.Text = kanji;

【讨论】:

  • 对不起,如果您厌倦了我,但它似乎不起作用。当然,我还没有对你的代码进行太多的逆向工程,因为它在这里太晚了,所以我必须在明天回复你,尝试自己诊断问题。暂时不用担心。 (此外,如果您好奇,1 级汉字现在显示为一个大点)。
  • 当然,在发布之前我已经测试过这段代码。看到你使用的字体可以处理这个字符集。 Segoe UI 和 Arial 都可以。当您再次有空时,请联系我,我们会解决这个问题。
  • @IIRawCodeII 尝试完全按照它的方式实现这个方法,所以如果某些东西没有按预期工作,我们就在同一个页面上。另请参阅编辑,因为我同时修改了一些代码。
  • 好的,现在看来可以工作了?我没有从昨天更改代码,所以我不知道是什么原因,但它有效。非常感谢!
  • @IIRawCodeII 很高兴你成功了。但是,请参阅编辑。我进行了一些更改,以确保更好地“理解”原始编码。
猜你喜欢
  • 2017-07-23
  • 2015-05-12
  • 1970-01-01
  • 2011-11-12
  • 2012-05-29
  • 2018-08-22
  • 2014-01-07
  • 2015-12-07
  • 2011-12-28
相关资源
最近更新 更多