整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

并发抓取网页H1标题大揭秘!C#打造高效信息提取工具!

果你有一个包含多个网址的列表,需要快速收集每个网页上的H1标题。快速、准确地从海量网页中提取关键信息。今天这个代码就太有用了!


实现的关键:

定义了一个URL列表,并初始化一个ConcurrentBag对象用于存储H1标题。
利用HttpClient发送异步HTTP请求,并通过Task.WhenAll方法并发处理所有网址。
在抓取过程中,如果遇到网络异常,程序会自动尝试最多三次重新获取,
每次重试之间加入递增延迟以避免过频请求导致的问题。

它使用了HtmlAgilityPack库来解析HTML,
System.Collections.Concurrent库中的ConcurrentBag来存储H1标题,
以及System.Net.Http库中的HttpClient来发送HTTP请求。

核心代码部分包括两个方法:

FetchH1HeadingsAsync负责单个URL的异步抓取,若出现异常则进行重试;
这是一个异步方法,用于抓取单个URL的H1标题。
方法接受一个URL、一个HttpClient实例和一个ConcurrentBag作为参数。
该方法尝试最多三次抓取H1标题。如果第一次尝试失败,
它会等待一段时间(根据尝试次数递增)然后重试。
如果三次尝试都失败,它会输出一条错误消息。
在每次尝试中,它使用HttpClient发送GET请求以获取URL的内容,
然后调用ProcessHtmlContent方法解析HTML并提取H1标题。

        // 异步方法,用于获取单个URL的H1标题,支持重试机制
        static async Task FetchH1HeadingsAsync(string url, HttpClient client, ConcurrentBag<string> headings)
        {
            for (int attempt = 1; attempt <= 3; attempt++) // 最多尝试3次抓取
            {
                try
                {
                    string htmlContent = await client.GetStringAsync(url);
                    ProcessHtmlContent(htmlContent, headings); // 解析HTML并添加标题
                    return;
                }
                catch (HttpRequestException) when (attempt < 3)
                {
                    await Task.Delay(TimeSpan.FromSeconds(attempt * 2)); // 每次失败后增加延时再重试
                }
            }

            Console.WriteLine($"尝试三次后仍无法从{url}获取内容。");
        }


ProcessHtmlContent将接收到的HTML内容解析成HtmlDocument对象,
并从中提取所有H1标签对应的文本内容作为标题。
        // 解析HTML内容并提取所有H1标题,存入线程安全集合
        static void ProcessHtmlContent(string htmlContent, ConcurrentBag<string> headings)
        {
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(htmlContent);

            foreach (var node in doc.DocumentNode.SelectNodes("//h1"))
            {
                if (node != null)
                {
                    headings.Add(node.InnerText.Trim()); // 添加标题前去除空白字符
                }
            }
        }


这个案例演示的技术方案具有很高的实用价值,在互联网信息采集、搜索引擎优化分析等领域可以发挥极为重要的作用。

通过高效的并发异步处理和线程安全数据结构,可以大大减少信息抓取的时间成本,并有优异的性能表现。

这是一个非常好的实现方式,H1也可以改成别的,效果杠杠的,需要完整代码或者编译成实用工具的可以关注留言给我。

平时做Code Review的时候,经常会看到这样的代码:

someArray.map( x => console.log(x) )

我都会要求修改成forEach,但经常会遇到有人反问:为什么要修改,map也没报错,没问题啊。

今天就想聊一下这个问题。

其实上面的这个代码,还是会有很多人认为应该修改成forEach的,但理由并不一定对。因为提到forEach和map的区别时,很多人会直接不假思索地说:

一个有返回值,一个没有返回值。

这个答案对吗?对,但完全没有触及本质。

真正的答案是:这两个方法其实毫无关系,各自用在各自不同的场景之下,只是看起来能够实现相似的功能而已。

forEach,其实就是for的一个语法糖:

const callback = f
const target = arr
for(let i = 0; i < target.length; i++) {
  f(target[i])
}

for代码块中,可以做任何事情,这只是一个按照顺序遍历的过程。

map来自于数学中的概念“函数”,也就是我们初中时候就学过的“一一映射”:把一个集合中的每一个值,按照某种变换过程,映射到另一个集合中。比如把所有的奇数加一,映射成所有的偶数。这个过程在语义上来说并不是“遍历”、“迭代”,而是所有值同时按照箭头方向直接变换,形成一个新的集合。

这个过程中,是不关心过程的,只关心从x到y的这个结果。而这个y很重要。所以,map必须有一个变量来接受新生成的这个y的集合。

同时,因为x到y的映射过程和第三者无关,所以在map的内部,不应该出现任何副作用(给函数外部的其他作用域中的变量赋值、输入、输出,包括网络请求等操作称为副作用)


换句话说:在学过map之前,大家都是用for和forEach来实现的功能,现在就应该还用for/forEach,只有在纯粹的把一个集合映射到另一个集合这一种场景下,大家才应该用map。


现在再回来看开头的那个例子,map有返回值是重点吗?不是。这个过程中出现了副作用,而且并没有把初始集合映射成任何新的集合(映射成了一个满是void的集合)

所以映射本身并不是重点,这就是一个普通的循环,理应直接用for/forEach,而不是选择map。


这样不知道是否解释清楚了呢?

述:你能停止 JavaScript 中的 forEach 循环吗?这是我在一次面试中被问到的一个问题,我最初的回答是,“不,我不能那样做。不幸的是,我的回答导致面试官突然结束了面试。我对结果感到沮丧,我问面试官:“为什么?真的可以在 JavaScript 中停止 forEach 循环吗?在面试官回答之前,我花了一点时间解释我对为什么我们不能直接停止 JavaScript 中的 forEach 循环的理解。我的答案正确吗?朋友们,下面的代码会输出哪些数字?它会只输出一个数字还是多个数字?是的,它将输出 '0', '1', '2', '3'。const array = [ -3, -2, -1, 0,

你能停止 JavaScript 中的 forEach 循环吗?这是我在一次面试中被问到的一个问题,我最初的回答是,“不,我不能那样做。

不幸的是,我的回答导致面试官突然结束了面试。

我对结果感到沮丧,我问面试官:“为什么?真的可以在 JavaScript 中停止 forEach 循环吗?

在面试官回答之前,我花了一点时间解释我对为什么我们不能直接停止 JavaScript 中的 forEach 循环的理解。

我的答案正确吗?

朋友们,下面的代码会输出哪些数字?

它会只输出一个数字还是多个数字?

是的,它将输出 '0', '1', '2', '3'。

const array = [ -3, -2, -1, 0, 1, 2, 3 ]  
  
array.forEach((it) => {  
  if (it >= 0) {  
    console.log(it)  
    return // or break  
  }  
})

没错!我把这段代码给面试官看,但他仍然相信我们可以停止 JavaScript 中的 forEach 循环。

为什么?

为了说服他,我不得不再次实施模拟。forEach

Array.prototype.forEach2 = function (callback, thisCtx) {  
  if (typeof callback !== 'function') {  
    throw `${callback} is not a function`  
  }  
  
  const length = this.length  
  
  let i = 0  
  while (i < length) {  
    if (this.hasOwnProperty(i)) {  
      // Note here:Each callback function will be executed once  
      callback.call(thisCtx, this[ i ], i, this)  
    }  
    i++  
  }  
}

是的,当我们使用 'forEach' 遍历一个数组时,回调将对数组的每个元素执行一次,我们没有办法过早地脱离它。

例如,在以下代码中,即使“func1”遇到 break 语句,“2”仍将输出到控制台。

const func1 = () => {  
  console.log(1)  
  return  
}  
  
const func2 = () => {  
  func1()  
  console.log(2)  
}  
func2()

停止 forEach 的 3 种方法

你很棒,但我想告诉你,我们至少有 3 种方法可以在 JavaScript 中停止 forEach。

1.# 抛出错误

找到第一个大于或等于 0 的数字后,此代码将无法继续。所以控制台只会打印出 0。

const array = [ -3, -2, -1, 0, 1, 2, 3 ]  
  
try {  
  array.forEach((it) => {  
    if (it >= 0) {  
      console.log(it)  
      throw Error(`We've found the target element.`)  
    }  
  })  
} catch (err) {  
    
}

2.# 设置数组长度为 0

请不要那么惊讶,面试官对我说。

我们还可以通过将数组的长度设置为 0 来中断 forEach。如您所知,如果数组的长度为 0,则 forEach 不会执行任何回调。

const array = [ -3, -2, -1, 0, 1, 2, 3 ]  
  
array.forEach((it) => {  
  if (it >= 0) {  
    console.log(it)  
    array.length = 0  
  }  
})

3.# 使用拼接删除数组的元素

思路与方法2相同,如果可以删除目标元素后面的所有值,则forEach将自动停止。

const array = [ -3, -2, -1, 0, 1, 2, 3 ]  
  
array.forEach((it, i) => {  
  if (it >= 0) {  
    console.log(it)  
    // Notice the sinful line of code  
    array.splice(i + 1, array.length - i)  
  }  
})

我瞪大了眼睛,我不想读这段代码。这很糟糕。

请使用或一些

我对面试官说,“哦,也许你是对的,你设法在 JavaScript 中停止了 forEach,但我认为你的老板会解雇你,因为这是一个非常糟糕的代码片段。

我不喜欢做这样的事情;这会让我的同事讨厌我。

也许我们应该使用“for”或“some”方法来解决这个问题。

1. for

const array = [ -3, -2, -1, 0, 1, 2, 3 ]  
  
for (let i = 0, len = array.length; i < len; i++) {  
  if (array[ i ] >= 0) {  
    console.log(array[ i ])  
    break  
  }  
}

2. some