前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊一聊.NET的网页抓取和编码转换

聊一聊.NET的网页抓取和编码转换

作者头像
桑榆肖物
发布2023-08-22 10:33:32
2030
发布2023-08-22 10:33:32
举报
文章被收录于专栏:桑榆肖物

在本文中,你会了解到两种用于 HTML 解析的类库。另外,我们将讨论关于网页抓取,编码转换和压缩处理的知识,以及如何在 .NET 中实现它们,最后进行优化和改进。

1. 背景

有了 Copilot 的加持,可以让我们快速的完成开发任务,并在极短的时间内完成小工具的开发。谁能想到现如今,写的代码注释却是为了给 AI 看,甚至不需要写注释,AI 都能猜的懂你的意图。如今代码本身更是不值钱了,只有产品才能体现它的价值。

因为平时会看小说作为娱乐消遣,习惯使用本地纯文本的阅读器,这就涉及到小说的下载,有的网站是提供有 TXT 的直接下载,但有的小说网站就没有提供。当然我也有用过 uncle-novel[1] 这样类似的工具,用起来也还是很不错的,但总感觉有些不是很顺手。

2. 网页抓取

在.NET中,HtmlAgilityPack[2] 库是经常使用的 HTML 解析工具,为解析 DOM 提供了足够强大的功能支持,经常用于网页抓取分析任务。

代码语言:javascript
复制
var web = new HtmlWeb();
var doc = web.Load(url);

在我写的小工具中也使用了这个工具库,小工具用起来也是顺手,直到前几天抓取一个小说时,发现竟出现了乱码,这才意识到之前抓取的网页均是 UTF-8 的编码,今次这个是 GBK 的。

虽然 HtmlAgilityPack 提供了 AutoDetectEncoding 功能,也是默认开启状态,但是似乎实际效果并没有起效。通过使用 HttpClient 拿到htmlStream 后喂给 HtmlDocument 启用 OptionReadEncoding 也是一样。

3. 编码转换

既如此,那就直接用 HttpClient 抓了再说,虽然解析还是逃不过 HtmlAgilityPack。对于 GBK 的支持,这里则需要引入System.Text.Encoding.CodePages 包。

对于抓取的网页内容我们先读取 bytes 然后以 UTF-8 编码读取后,通过正则解析出网页的实际的字符编码,并根据需要进行转换。

代码语言:javascript
复制
var client = new HttpClient();
var response = await client.GetAsync(url);
var bytes = await response.Content.ReadAsByteArrayAsync();
var htmldoc = Encoding.UTF8.GetString(bytes);
var match = Regex.Match(htmldoc, "<meta.*?charset=\"?(?<charset>.*?)\".*?>", RegexOptions.IgnoreCase);

4. 网页压缩处理

在使用 HttpClient 抓取网页时,最好是加入个请求头进行伪装一番,Copilot 也是真的省事,注释“设置请求头”一写直接回车,都不用去搜浏览器 UA 的。说起搜索,基本上搜索除了要被搜索引擎的广告折磨外,也有可能被某些吸引人的热搜转移精力,然后就没有然后了……

不过,这次回车可能敲多了,把我敲坑里了。本来只是想加个 UA,觉得提示的也还挺有用的,最后加了一堆:

代码语言:javascript
复制
// 设置请求头
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
    "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36");
client.DefaultRequestHeaders.Add("Accept", "*/*");
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
client.DefaultRequestHeaders.Add("Accept-Language", "zh-CN,zh;q=0.9");
client.DefaultRequestHeaders.Add("Connection", "keep-alive");

然后我测试一番,发现我的代码就不能跑了,人麻了,该不是网站有什么高深的防火墙吧:

压缩导致乱码

调试了半天,才想起来,莫不是因为加入了压缩的请求头吧?

注释掉再次测试,果然是它。哎,本想着你好我好大家好,加上压缩,这抓的速度更快,对面也省流量。

不过,注释是不可能注释掉的,遇到问题就解决问题,直接问 GPT 就是了。大段大段复杂的解决方法,解压缩的方式这里就不说了。当我告诉 GPT 我用的最新的 .NET 开发,你给我优雅一些后,它果然就优雅了起来:

代码语言:javascript
复制
var handler = new HttpClientHandler  
{  
    AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate  | System.Net.DecompressionMethods.Brotli
};  
var httpClient = new HttpClient(handler);

毕竟 HttpClient 是支持自动处理压缩的。可以使用 HttpClientHandler 来启用自动解压缩功能,确实比去找官方文档[3]方便的多。

5. 代码优化

通过前面的调整,我们基本已经写好了核心代码。当然,优化的空间还是很大的,这里我们可以直接请 GPT4 来帮忙处理:

代码语言:javascript
复制
/// <summary>
/// 下载网页内容,并将其他编码转换为 UTF-8 编码
/// 记得看后面的优化说明
/// </summary>
static async Task<string> GetWebHtml(string url){
    // 使用 HttpClient 下载网页内容

    var handler = new HttpClientHandler();
    // 忽略证书错误
    handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
    handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli;
    var client = new HttpClient(handler);
    // 设置请求头
    client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
        "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36");
    client.DefaultRequestHeaders.Add("Accept", "*/*");
    // 加上后不处理解压缩会乱码
    client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
    client.DefaultRequestHeaders.Add("Accept-Language", "zh-CN,zh;q=0.9");
    client.DefaultRequestHeaders.Add("Connection", "keep-alive");
    var response = await client.GetAsync(url);
    var bytes = await response.Content.ReadAsByteArrayAsync();

    // 获取网页编码 ContentType 可能为空,从网页获取
    var charset = response.Content.Headers.ContentType?.CharSet;
    if (string.IsNullOrEmpty(charset))
    {
        // 从网页获取编码信息
        var htmldoc = Encoding.UTF8.GetString(bytes);
        var match = Regex.Match(htmldoc, "<meta.*?charset=\"?(?<charset>.*?)\".*?>", RegexOptions.IgnoreCase);
        if (match.Success) charset = match.Groups["charset"].Value;
        else charset = "utf-8";
    }

    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    Encoding encoding;

    switch (charset.ToLower())
    {
        case "gbk":
            encoding = Encoding.GetEncoding("GBK");
            break;
        case "gb2312":
            encoding = Encoding.GetEncoding("GB2312");
            break;
        case "iso-8859-1":
            encoding = Encoding.GetEncoding("ISO-8859-1");
            break;
        case "ascii":
            encoding = Encoding.ASCII;
            break;
        case "unicode":
            encoding = Encoding.Unicode;
            break;
        case "utf-32":
            encoding = Encoding.UTF32;
            break;
        default:
            return Encoding.UTF8.GetString(bytes);
    }

    // 统一转换为 UTF-8 编码
    var html = Encoding.UTF8.GetString(Encoding.Convert(encoding, Encoding.UTF8, bytes));
    return html;
}

5.1 更换 Html 解析库

事情的起因是 HtmlAgilityPack 库的自动编码解析出现了问题,那么有没有其他替代的库呢?

当然,GPT4 推荐了 AngleSharp[4] ,这个库我简单测试了一下,无需配置可以直接识别网页编码,看起来是比 HtmlAgilityPack 好用一些。另外,其还支持输出 Javascript、Linq 语法、ID 和 Class 选择器、动态添加节点、支持 Xpath 语法。

总的来说,此番虽然是造了轮子,但是编程知识却是增加了嘛。

5.2 对于轮子的优化

虽然有以下要优化的地方,但是真的不如直接换轮子来的方便啊,因为换了轮子就没有下面的问题了:

1.对于实际的使用,使用静态的 HttpClient 实例,而不是为每个请求创建一个新的 HttpClient 实例。这可以避免不必要的资源浪费。可以将其及其配置移到一个单独的帮助类中如:HttpClientHelper,并在需要时访问它。2.这里我们单独写了一个函数,在其中使用了额外的编码注册 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance),在实际使用中,应该将其放在程序启动时执行。这样,只需在程序启动时注册一次编码提供程序,而不是每次调用方法时都注册。3. 其他一些写法上的优化,如 switch 和方法命名等。

6. 最后

这篇文章是我在开发 BookMaker 小工具时的一些关于网页抓取的心得,主要介绍了两个 Html 解析库,解决了编码转换和压缩的一些问题,希望对大家能有所帮助。

References

[1] uncle-novel: https://github.com/uncle-novel/uncle-novel?WT.mc_id=DT-MVP-5005195 [2] HtmlAgilityPack: https://github.com/zzzprojects/html-agility-pack?WT.mc_id=DT-MVP-5005195 [3] 官方文档: https://learn.microsoft.com/zh-cn/dotnet/api/system.net.http.httpclienthandler.automaticdecompression?WT.mc_id=DT-MVP-5005195 [4] AngleSharp: https://github.com/AngleSharp/AngleSharp?WT.mc_id=DT-MVP-5005195

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 桑榆肖物 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 背景
  • 2. 网页抓取
  • 3. 编码转换
  • 4. 网页压缩处理
  • 5. 代码优化
    • 5.1 更换 Html 解析库
      • 5.2 对于轮子的优化
        • References
    • 6. 最后
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档