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

news2024/9/20 10:52:47

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

文章目录

  • 1. 背景
  • 2. 网页抓取
  • 3. 编码转换
  • 4. 网页压缩处理
  • 5. 代码优化
    • 5.1 更换 Html 解析库
    • 5.2 对于轮子的优化
  • 6. 最后

1. 背景

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

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

2. 网页抓取

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

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 编码读取后,通过正则解析出网页的实际的字符编码,并根据需要进行转换。

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,觉得提示的也还挺有用的,最后加了一堆:

// 设置请求头
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 开发,你给我优雅一些后,它果然就优雅了起来:

var handler = new HttpClientHandler  
{  
    AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate  | System.Net.DecompressionMethods.Brotli
};  
var httpClient = new HttpClient(handler);

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

5. 代码优化

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

/// <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 ,这个库我简单测试了一下,无需配置可以直接识别网页编码,看起来是比 HtmlAgilityPack 好用一些。另外,其还支持输出 Javascript、Linq 语法、ID 和 Class 选择器、动态添加节点、支持 Xpath 语法。

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

在这里插入图片描述

5.2 对于轮子的优化

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

  1. 对于实际的使用,使用静态的 HttpClient 实例,而不是为每个请求创建一个新的 HttpClient 实例。这可以避免不必要的资源浪费。可以将其及其配置移到一个单独的帮助类中如:HttpClientHelper,并在需要时访问它。

  2. 这里我们单独写了一个函数,在其中使用了额外的编码注册 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance),在实际使用中,应该将其放在程序启动时执行。这样,只需在程序启动时注册一次编码提供程序,而不是每次调用方法时都注册。

  3. 其他一些写法上的优化,如 switch 和方法命名等。

6. 最后

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/709212.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#,数值计算——哈夫曼编码与数据压缩技术(Huffman Coding and Compression of Data)源代码

1 霍夫曼编码导论 霍夫曼编码是一种基于数据集中符号频率的无损数据压缩形式。它是一种前缀编码方案&#xff0c;这意味着编码的数据不包含任何冗余比特。霍夫曼编码广泛应用于各种应用&#xff0c;如图像和视频压缩、数据传输和数据存储。 2 霍夫曼编码的优点 以下是霍夫曼编…

[LeetCode周赛复盘] 第 326 场周赛20230702

[LeetCode周赛复盘] 第 326 场周赛20230702 一、本周周赛总结6909. 最长奇偶子数组1. 题目描述2. 思路分析3. 代码实现 6916. 和等于目标值的质数对1. 题目描述2. 思路分析3. 代码实现 6911. 不间断子数组1. 题目描述2. 思路分析3. 代码实现 6894. 所有子数组中不平衡数字之和…

【小沐学Unity3d】Unity播放视频(VideoPlayer组件)

文章目录 1、简介2、脚本播放示例3、界面播放示例3.1 2d界面全屏播放3.2 2d界面部分区域播放3.3 3d模型表面播放 结语 1、简介 使用视频播放器组件可将视频文件附加到游戏对象&#xff0c;然后在运行时在游戏对象的纹理上播放。 视频播放器 (Video Player) 组件: 属性功能Sourc…

Java实现OpenAI 模型训练(fine-tune)

本文章介绍如何用java实现OpenAI模型训练&#xff0c;仅供参考 提前准备工作 OpenAI KEY&#xff0c;获取方式可自行百度需要自备VPN 或 使用国外服务器转发需要训练的数据集&#xff0c;文章格式要求为JSONL&#xff0c;格式内容详见下图&#xff08;尽量不要低于500个问答&…

openai

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;人工智能。 目录 1、简介2、如何实现3、api文档 1、简介 OpenAI 提供了一个名为 OpenAI API 的库&#xff0c;用…

npm构建vite项目

基础环境 npm init vitelatest 依次输入项目名称、使用框架、使用语言。 生成的项目 进入目录&#xff0c;安装依赖&#xff0c;启动项目。 cd 0702_demo01npm installnpm run dev

网络安全进阶学习第四课——SSRF服务器请求伪造

文章目录 一、什么是SSRF&#xff1f;二、SSRF成因三、SSRF简析四、PHP存在SSRF的风险函数五、后台源码获取方式六、SSRF危害七、SSRF漏洞挖掘从WEB功能上寻找&#xff0c;从URL关键字中寻找 八、SSRF具体利用ssrf常利用的相关协议PHP伪协议读取文件端口扫描 九、SSRF存在的必要…

架构分层方法指导

在《不过时的经典层架构》里讲了经典的四层架构怎样对易变性做封装。咱们实际项目中&#xff0c;如果没有足够的实践和关键性思考&#xff0c;还是很可能使用名义上科学的分类理论&#xff0c;却在按照功能进行架构分层。今天咱们就通过一些简单的指导来尽量减少这种风险。 四问…

LeetCode 75 —— 70. 爬楼梯

LeetCode 75 —— 70. 爬楼梯 一、题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法…

机器学习笔记 - 基于OpenCV和Vantage-point tree构建图像哈希搜索引擎

一、关于图像哈希 上一篇文章中,了解到了图像哈希是使用算法为图像分配唯一哈希值的过程。在深度学习普及之前,一些搜索引擎使用散列技术来索引图像。 言外之意目前的图像搜索引擎主要都是基于深度学习的技术,不过思路都是一样的,我们这里基于OpenCV提供的图像哈希技术构建…

python实现削苹果小游戏

也不用998只有199源码发你。 支付完发我邮箱发你源代码。

RISC-V处理器的设计与实现(三)—— 上板验证(基于野火征途Pro开发板)

文章目录 RISC-V处理器的设计与实现&#xff08;一&#xff09;—— 基本指令集_Patarw_Li的博客-CSDN博客 RISC-V处理器的设计与实现&#xff08;二&#xff09;—— CPU框架设计_Patarw_Li的博客-CSDN博客 RISC-V处理器的设计与实现&#xff08;三&#xff09;—— 上板验…

Gradle安装与配置(8.2)

一、下载地址 https://gradle.org/releases/ https://downloads.gradle.org/distributions/gradle-8.2-bin.zip 解压后放到合适的位置 二、配置环境变量并测试 D:\ProgramFiles\gradle-8.2\bin gradle -v 三、配置镜像 D:\ProgramFiles\gradle-8.2\init.d init.gradle&…

算法第36天:数组中出现次数超过一半的数字【摩尔投票法】

算法介绍 摩尔投票法&#xff1a;求众数的方法。 就是维护一个集合&#xff0c;然后我们遍历我们的数组&#xff0c;假如现在我们遍历到的数为x&#xff0c;当集合中都是x的话我们就将x放入集合中&#xff0c;如果我们遍历到的数为x&#xff0c;但是集合中有y&#xff0c;那么…

【VulnHub系列】West-Wlid1.1

实验信息 Kali&#xff1a;192.168.10.106 WestWild&#xff1a;192.168.104 实验过程 通过arp-scan查找目标主机&#xff0c;确定目标主机IP192.168.10.104 sudo arp-scan --interface eth0 192.168.10.0/24 探测靶机开放的端口 sudo nmap -sT --min-rate 10000 -p- 192.1…

Redis的持久化机制(1)

RDB&#xff0c;即Redis DataBase的简称。RDB是Redis默认的持久化机制 RDB持久化文件&#xff0c;速度比较快&#xff0c;而且存储的是一个二进制的文件&#xff0c;传输起来很方便 在指定的时间间隔内&#xff0c;将内存中的数据集的快照写入磁盘。默认保存在/usr/local/bin目…

122.【SpringBoot - 再刷 - 基础入门 - 01】

SpringBoot2 核心技术 (一)、SpringBoot核心技术入门1.Spring能做什么?1.1、Spring 的能力1.2、Spring的生态1.3、Spring5重大升级1.3.1、响应式编程1.3.2、内部源码设计 2.为什么用SpringBoot2.1、SpringBoot优点2.2、SpringBoot缺点 3.时代背景3.1、微服务3.2、分布式的困难…

github克隆代码加速

https://www.gitclone.com/gogs/ 只需要在正常的git clone后的URL里&#xff0c;嵌入gitclone.com即可快速clone 举例&#xff1a; #原地址 git clone https://github.com/SpringSource/Spring-framework #新地址 git clone https://gitclone.com/github.com/SpringSource/…

2023年出货量预计增长75%,谁在领跑规模化量产赛道?

2023年将成为一个分水岭&#xff0c;中国智能驾驶市场已经进入了下一个竞争周期&#xff0c;卷&#xff0c;难 成为了智驾赛道新的关键词&#xff0c;对各赛道的供应商来说&#xff0c;未来几年将是比拼规模化与降本。 对各级供应商来说&#xff0c;产品规模化量产&#xff0c…

【二叉树part07】| 530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236.二叉树的最近公共祖先

目录 &#x1f388;LeetCode530.二叉搜索树的最小绝对差 &#x1f388;LeetCode501.二叉搜索树中的众数 &#x1f388;LeetCode236.二叉树的最近公共祖先 &#x1f388;LeetCode530.二叉搜索树的最小绝对差 链接&#xff1a;530.二叉树的最小绝对差 给你一个二叉搜索树的根…