偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

C#網(wǎng)頁內(nèi)容智能提?。簭腍TML混沌到結(jié)構(gòu)化信息的AI之路

開發(fā) 前端
如果你正在開發(fā)內(nèi)容聚合、信息監(jiān)控或知識管理系統(tǒng),這個方案絕對值得深入研究和應(yīng)用。覺得有用請轉(zhuǎn)發(fā)給更多同行,讓AI賦能更多C#開發(fā)者!

在這個信息爆炸的時代,程序員們經(jīng)常需要從各種網(wǎng)頁中提取有價值的內(nèi)容。傳統(tǒng)的爬蟲要么暴力抓取全部內(nèi)容,要么需要針對每個網(wǎng)站手寫復(fù)雜的解析規(guī)則。如果告訴你,現(xiàn)在可以讓AI自動分析網(wǎng)頁結(jié)構(gòu),精準(zhǔn)定位主要內(nèi)容區(qū)域,你會不會覺得這就是你一直在尋找的解決方案?

本文將帶你深入了解如何結(jié)合Semantic Kernel、HtmlAgilityPackAI模型,構(gòu)建一個智能的網(wǎng)頁內(nèi)容提取和總結(jié)工具。這不僅是一次技術(shù)實踐,更是探索AI在傳統(tǒng)爬蟲領(lǐng)域的創(chuàng)新應(yīng)用。

傳統(tǒng)爬蟲的三大痛點

痛點一:網(wǎng)頁結(jié)構(gòu)千變?nèi)f化

每個網(wǎng)站的HTML結(jié)構(gòu)都不同,新聞網(wǎng)站、技術(shù)博客、電商平臺的內(nèi)容區(qū)域完全不一樣。傳統(tǒng)方案需要為每種網(wǎng)站類型編寫專門的提取規(guī)則。

痛點二:反爬蟲機(jī)制越來越復(fù)雜

現(xiàn)代網(wǎng)站普遍部署了sophisticated的反爬蟲策略:動態(tài)加載、驗證碼、頻率限制、User-Agent檢測等等。

痛點三:內(nèi)容質(zhì)量參差不齊

即使成功抓取到內(nèi)容,如何從海量信息中提取真正有價值的部分,依然是個技術(shù)難題。

AI驅(qū)動的智能解決方案

核心思路:三步走策略

第一步:獲取網(wǎng)頁的HTML框架結(jié)構(gòu)(去除具體內(nèi)容,保留標(biāo)簽結(jié)構(gòu))

第二步:讓AI分析HTML結(jié)構(gòu),智能識別主體內(nèi)容區(qū)域

第三步:根據(jù)AI推薦的選擇器精準(zhǔn)提取內(nèi)容,并進(jìn)行智能總結(jié)

這個方案的精妙之處在于:我們不是讓AI處理完整的HTML內(nèi)容,而是讓它分析結(jié)構(gòu)化的框架,這樣既提高了準(zhǔn)確性,又大大降低了token消耗。

代碼實戰(zhàn):構(gòu)建智能提取工具

項目準(zhǔn)備

首先安裝必要的NuGet包:

dotnet add package HtmlAgilityPack
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI

核心架構(gòu)設(shè)計

整個工具分為三個核心模塊:

// 主程序流程
static async Task Main(string[] args)
{
    // 1. 初始化AI服務(wù)
    var kernel = InitializeSemanticKernel();

    // 2. 獲取HTML框架結(jié)構(gòu)
    var (html, htmlStructure) = await WebContentHelper.GetHtmlStructureAsync(url);

    // 3. AI分析結(jié)構(gòu),推薦選擇器
    var recommendedSelector = await AnalyzeHtmlStructure(kernel, htmlStructure, url);

    // 4. 提取內(nèi)容并總結(jié)
    var content = WebContentHelper.ExtractContentBySelector(url, recommendedSelector, html);
    var summary = await SummarizeContent(kernel, content, url, recommendedSelector);
}

AI插件系統(tǒng):讓AI成為你的結(jié)構(gòu)分析專家

這里是整個方案的核心創(chuàng)新點——AI插件系統(tǒng)

// HTML結(jié)構(gòu)分析插件
var htmlAnalysisPrompt = @"
你是專業(yè)的網(wǎng)頁結(jié)構(gòu)分析專家。請分析以下HTML框架結(jié)構(gòu),找出最可能包含主體文章內(nèi)容的元素選擇器。

## HTML框架結(jié)構(gòu):
{{$htmlStructure}}

請分析HTML結(jié)構(gòu),找出主體內(nèi)容區(qū)域。常見的主體內(nèi)容通常位于:
- article 標(biāo)簽
- main 標(biāo)簽  
- 帶有 id 或 class 包含 content、article、post、main、body 等關(guān)鍵詞的div

只返回一個最佳的CSS選擇器,不要其他解釋文字。
";

var htmlAnalyzer = kernel.CreateFunctionFromPrompt(
    promptTemplate: htmlAnalysisPrompt,
    executionSettings: new OpenAIPromptExecutionSettings
    {
        MaxTokens = 200,
        Temperature = 0.3  // 低溫度保證結(jié)果穩(wěn)定
    },
    functionName: "AnalyzeHtmlStructure"
);

關(guān)鍵技術(shù)點

  • 使用低溫度(0.3)確保AI給出穩(wěn)定、準(zhǔn)確的選擇器推薦
  • 限制輸出長度(200 tokens),避免AI輸出冗余信息
  • 明確指示AI只返回選擇器,不要解釋文字

反爬蟲策略:模擬真實瀏覽器行為

private static void SetBrowserHeaders(HttpClient client)
{
    client.DefaultRequestHeaders.Clear();
    client.DefaultRequestHeaders.Add("User-Agent",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");

    client.DefaultRequestHeaders.Add("Accept",
        "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");

    // 隨機(jī)Referer策略
    var referers = new[] { 
        "https://www.google.com/", 
        "https://www.bing.com/", 
        "https://www.baidu.com/"
    };
    client.DefaultRequestHeaders.Add("Referer", 
        referers[new Random().Next(referers.Length)]);

    // 關(guān)鍵:自動解壓縮
    handler.AutomaticDecompression = DecompressionMethods.GZip | 
                                   DecompressionMethods.Deflate | 
                                   DecompressionMethods.Brotli;
}

HTML結(jié)構(gòu)清理:保留骨架,移除噪音

這是另一個技術(shù)亮點——智能HTML結(jié)構(gòu)提取

private static async Task<string> ProcessHtmlContent(string html)
{
    var doc = new HtmlDocument();
    doc.LoadHtml(html);

    // 移除腳本和樣式
    var scriptsAndStyles = doc.DocumentNode.SelectNodes("http://script | //style");
    scriptsAndStyles?.ToList().ForEach(node => node.Remove());

    // 清理文本節(jié)點,保留結(jié)構(gòu)
    CleanTextNodes(doc.DocumentNode);

    string cleanHtml = doc.DocumentNode.OuterHtml;
    // 壓縮空白字符
    cleanHtml = Regex.Replace(cleanHtml, @">\s+<", "><");

    return cleanHtml;
}

private static void CleanTextNodes(HtmlNode node)
{
    if (node.NodeType == HtmlNodeType.Text)
    {
        // 用占位符替代具體文本內(nèi)容
        if (!string.IsNullOrWhiteSpace(node.InnerText))
        {
            node.InnerHtml = "[TEXT]";
        }
    }
    // 保留重要屬性:id, class
    var importantAttrs = new[] { "id", "class", "role" };
    node.Attributes.Where(attr => !importantAttrs.Contains(attr.Name.ToLower()))
                   .ToArray()
                   .ToList()
                   .ForEach(attr => node.Attributes.Remove(attr));
}

智能內(nèi)容提取:從選擇器到文本

public static string ExtractContentBySelector(string url, string cssSelector, string html)
{
    var doc = new HtmlDocument();
    doc.LoadHtml(html);

    HtmlNode targetNode = null;

    // 智能解析CSS選擇器
    cssSelector = cssSelector.Trim();
    if (cssSelector.StartsWith("#"))
    {
        string id = cssSelector.Substring(1);
        targetNode = doc.DocumentNode.SelectSingleNode($"http://*[@id='{id}']");
    }
    elseif (cssSelector.StartsWith("."))
    {
        string className = cssSelector.Substring(1);
        targetNode = doc.DocumentNode.SelectSingleNode($"http://*[contains(@class, '{className}')]");
    }
    else
    {
        targetNode = doc.DocumentNode.SelectSingleNode($"http://{cssSelector}");
    }

    if (targetNode == null) return null;

    // 清理干擾元素
    var tagsToRemove = new[] { "script", "style", "nav", "header", "footer", "aside" };
    foreach (var tag in tagsToRemove)
    {
        targetNode.SelectNodes($".//{tag}")?.ToList().ForEach(node => node.Remove());
    }

    return CleanTextContent(targetNode.InnerText);
}

完整例子

using HtmlAgilityPack;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel;
using System.Text.RegularExpressions;
using System;
using System.Text;
using System.Net;
using System.Net.Http;

namespace AppAiWeb
{
    class Program
    {

        static async Task Main(string[] args)
        {
            Console.OutputEncoding = System.Text.Encoding.UTF8;
            Console.WriteLine("?? 智能網(wǎng)頁內(nèi)容總結(jié)工具 - AI自動識別內(nèi)容區(qū)域");
            Console.WriteLine("請輸入要總結(jié)的網(wǎng)頁URL:");
            var url = Console.ReadLine() ?? "";

            if (string.IsNullOrWhiteSpace(url))
            {
                Console.WriteLine("? URL不能為空");
                return;
            }

            // 初始化 Semantic Kernel
            Console.WriteLine("?? 正在初始化AI服務(wù)...");
            var kernelBuilder = Kernel.CreateBuilder();
            kernelBuilder.AddOpenAIChatCompletion(
                modelId: "deepseek-chat",
                apiKey: Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY") ?? "sk-XXXXX",
                endpoint: new Uri("https://api.deepseek.com/v1")
            );
            var kernel = kernelBuilder.Build();

            // 注冊所有插件
            RegisterWebAnalysisPlugins(kernel);

            try
            {
                // 第一步:獲取HTML框架結(jié)構(gòu)
                Console.WriteLine("?? 正在獲取網(wǎng)頁HTML框架...");
                var (html, htmlStructure) = await WebContentHelper.GetHtmlStructureAsync(url);

                if (string.IsNullOrWhiteSpace(htmlStructure))
                {
                    Console.WriteLine("? 無法獲取網(wǎng)頁結(jié)構(gòu)");
                    return;
                }

                Console.WriteLine($"?? 【HTML框架長度】:{htmlStructure.Length} 字符");
                Console.WriteLine($"?? 【HTML結(jié)構(gòu)預(yù)覽(前300字)】\n{htmlStructure.Substring(0, Math.Min(300, htmlStructure.Length))}...\n");

                // 第二步:AI分析HTML結(jié)構(gòu),識別主體內(nèi)容區(qū)域
                Console.WriteLine("?? AI正在分析HTML結(jié)構(gòu),識別主體內(nèi)容區(qū)域...");
                var structureAnalyzer = kernel.Plugins["WebAnalysisPlugin"]["AnalyzeHtmlStructure"];
                var structureArgs = new KernelArguments
                {
                    { "htmlStructure", htmlStructure },
                    { "url", url }
                };

                var selectorResult = await kernel.InvokeAsync(structureAnalyzer, structureArgs);
                string recommendedSelector = selectorResult.GetValue<string>();

                Console.WriteLine($"?? AI推薦的內(nèi)容選擇器:{recommendedSelector}");

                // 第三步:根據(jù)AI推薦的選擇器提取內(nèi)容
                Console.WriteLine("?? 正在提取主體內(nèi)容...");
                string mainContent = WebContentHelper.ExtractContentBySelector(url, recommendedSelector, html);

                if (string.IsNullOrWhiteSpace(mainContent))
                {
                    Console.WriteLine("? 無法根據(jù)推薦選擇器提取內(nèi)容,嘗試備用方案...");
                    mainContent = WebContentHelper.ExtractContentBySelector(url, "body", html);
                }

                Console.WriteLine($"?? 【提取內(nèi)容長度】:{mainContent.Length} 字符");
                Console.WriteLine($"?? 【內(nèi)容預(yù)覽(前500字)】\n{mainContent.Substring(0, Math.Min(500, mainContent.Length))}...\n");

                // 第四步:AI總結(jié)內(nèi)容
                Console.WriteLine("?? 正在生成內(nèi)容摘要...");
                var summarizer = kernel.Plugins["WebAnalysisPlugin"]["SummarizeContent"];
                var summaryArgs = new KernelArguments
                {
                    { "content", mainContent },
                    { "url", url },
                    { "selector", recommendedSelector }
                };

                var summarization = await kernel.InvokeAsync(summarizer, summaryArgs);

                Console.WriteLine($"\n?? 【AI內(nèi)容總結(jié)結(jié)果】\n");
                Console.WriteLine(newstring('=', 50));
                Console.WriteLine(summarization.GetValue<string>());
                Console.WriteLine(newstring('=', 50));

            }
            catch (Exception ex)
            {
                Console.WriteLine($"? 處理過程中出現(xiàn)錯誤:{ex.Message}");
                Console.WriteLine($"詳細(xì)錯誤:{ex.StackTrace}");
            }

            Console.WriteLine("\n? 總結(jié)完成!按任意鍵退出...");
            Console.ReadKey();
        }

        // 注冊網(wǎng)頁分析相關(guān)的所有插件
        static void RegisterWebAnalysisPlugins(Kernel kernel)
        {
            // 插件1:分析HTML結(jié)構(gòu),識別主體內(nèi)容區(qū)域
            var htmlAnalysisPrompt = @"
你是專業(yè)的網(wǎng)頁結(jié)構(gòu)分析專家。請分析以下HTML框架結(jié)構(gòu),找出最可能包含主體文章內(nèi)容的元素選擇器。

## 網(wǎng)頁URL: {{$url}}

## HTML框架結(jié)構(gòu):
{{$htmlStructure}}

請分析HTML結(jié)構(gòu),找出主體內(nèi)容區(qū)域。常見的主體內(nèi)容通常位于:
- article 標(biāo)簽
- main 標(biāo)簽  
- 帶有 id 或 class 包含 content、article、post、main、body 等關(guān)鍵詞的div
- 最大的內(nèi)容容器

分析步驟:
1. 查看是否有 <article> 或 <main> 標(biāo)簽
2. 查看是否有明顯的內(nèi)容相關(guān)的 id 或 class
3. 分析頁面結(jié)構(gòu),找出最可能的主體內(nèi)容區(qū)域

請只返回一個最佳的CSS選擇器,格式如下幾種之一:
- article
- main  
- #post_detail
- .article-content
- #content
- .post-content
- div.main-content

只返回選擇器,不要其他解釋文字。
";

            var htmlAnalyzer = kernel.CreateFunctionFromPrompt(
                promptTemplate: htmlAnalysisPrompt,
                executionSettings: new OpenAIPromptExecutionSettings
                {
                    MaxTokens = 200,
                    Temperature = 0.3
                },
                functionName: "AnalyzeHtmlStructure",
                description: "分析HTML結(jié)構(gòu),識別主體內(nèi)容區(qū)域的CSS選擇器"
            );

            // 插件2:內(nèi)容總結(jié)
            var contentSummaryPrompt = @"
你是專業(yè)的內(nèi)容分析師,請對以下網(wǎng)頁內(nèi)容進(jìn)行深度分析和總結(jié):

## 來源網(wǎng)址: {{$url}}
## 使用的選擇器: {{$selector}}

## 網(wǎng)頁內(nèi)容:
{{$content}}

請按以下格式提供詳細(xì)的內(nèi)容分析:

?? **文章標(biāo)題/主題識別**
[識別文章的核心主題]

?? **核心要點** (3-5個關(guān)鍵點)
? 要點1:[具體內(nèi)容]
? 要點2:[具體內(nèi)容]  
? 要點3:[具體內(nèi)容]

?? **內(nèi)容分類**
類型:[技術(shù)文檔/新聞資訊/教程指南/產(chǎn)品介紹/其他]
領(lǐng)域:[相關(guān)行業(yè)或技術(shù)領(lǐng)域]

?? **關(guān)鍵信息提取**
? 重要數(shù)據(jù)/時間:[提取關(guān)鍵數(shù)據(jù)]
? 人物/機(jī)構(gòu):[相關(guān)人物或組織]
? 技術(shù)要點:[技術(shù)相關(guān)的核心信息]

?? **內(nèi)容摘要** (200字以內(nèi))
[用簡潔的語言概括整篇內(nèi)容的精髓]

?? **價值評估**
? 信息價值:[高/中/低]
? 實用性:[評估實際應(yīng)用價值]
? 時效性:[內(nèi)容的時效性評估]

?? **結(jié)論**
[一句話總結(jié)這篇文章的核心觀點或價值]
";

            var contentSummarizer = kernel.CreateFunctionFromPrompt(
                promptTemplate: contentSummaryPrompt,
                executionSettings: new OpenAIPromptExecutionSettings
                {
                    MaxTokens = 1500,
                    Temperature = 0.7
                },
                functionName: "SummarizeContent",
                description: "對提取的網(wǎng)頁內(nèi)容進(jìn)行深度分析和總結(jié)"
            );

            // 插件3:快速摘要
            var quickSummaryPrompt = @"
請用最簡潔的語言(不超過100字)總結(jié)以下內(nèi)容的核心要點:

內(nèi)容:{{$content}}

要求:直接輸出要點,不要格式化標(biāo)記。
";

            var quickSummarizer = kernel.CreateFunctionFromPrompt(
                promptTemplate: quickSummaryPrompt,
                executionSettings: new OpenAIPromptExecutionSettings
                {
                    MaxTokens = 300,
                    Temperature = 0.5
                },
                functionName: "QuickSummary",
                description: "生成簡潔的內(nèi)容摘要"
            );

            kernel.ImportPluginFromFunctions("WebAnalysisPlugin", [htmlAnalyzer, contentSummarizer, quickSummarizer]);
        }
    }

    // 網(wǎng)頁內(nèi)容處理輔助類
    publicstaticclass WebContentHelper
    {

        publicstatic async Task<(string, string)> GetHtmlStructureAsync(string url)
        {
            try
            {
                // URL 格式化
                if (!Regex.IsMatch(url, @"^https?://", RegexOptions.IgnoreCase))
                    url = "https://" + url;

                Console.WriteLine($"?? 正在訪問:{url}");

                using var handler = new HttpClientHandler()
                {
                    // 自動處理cookie
                    UseCookies = true,
                    CookieContainer = new System.Net.CookieContainer(),

                    // SSL證書驗證
                    ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,

                    // 關(guān)鍵:自動解壓縮
                    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli
                };

                using var client = new HttpClient(handler);


                SetBrowserHeaders(client);


                // 設(shè)置超時時間
                client.Timeout = TimeSpan.FromSeconds(60); // 增加到60秒

                Console.WriteLine("?? 嘗試標(biāo)準(zhǔn)請求...");

                try
                {
                    // 嘗試標(biāo)準(zhǔn)GET請求
                    var response = await client.GetAsync(url);

                    if (response.IsSuccessStatusCode)
                    {
                        var html = await response.Content.ReadAsStringAsync();
                        Console.WriteLine($"? 標(biāo)準(zhǔn)請求成功,HTML長度:{html.Length}");
                        return (html, await ProcessHtmlContent(html));
                    }
                    else
                    {
                        Console.WriteLine($"?? 標(biāo)準(zhǔn)請求響應(yīng)碼:{response.StatusCode},嘗試其他方法...");
                    }
                }
                catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
                {
                    Console.WriteLine("?? 標(biāo)準(zhǔn)請求超時,嘗試分段請求...");
                }
                catch (HttpRequestException ex)
                {
                    Console.WriteLine($"?? 標(biāo)準(zhǔn)請求失?。簕ex.Message},嘗試其他方法...");
                }
                return ("", "");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"? 獲取HTML結(jié)構(gòu)失?。簕ex.Message}");
                return ("", "");
            }
        }

        // 處理HTML內(nèi)容的獨立方法
        privatestatic async Task<string> ProcessHtmlContent(string html)
        {
            await Task.Delay(100); // 小延遲,模擬處理時間

            Console.WriteLine($"?? 獲取到原始HTML,長度:{html.Length} 字符");

            // 使用 HtmlAgilityPack 解析并清理
            var doc = new HtmlDocument();
            doc.LoadHtml(html);

            // 移除script和style標(biāo)簽
            var scriptsAndStyles = doc.DocumentNode.SelectNodes("http://script | //style");
            if (scriptsAndStyles != null)
            {
                foreach (var node in scriptsAndStyles)
                {
                    node.Remove();
                }
            }

            // 移除注釋
            var comments = doc.DocumentNode.SelectNodes("http://comment()");
            if (comments != null)
            {
                foreach (var comment in comments)
                {
                    comment.Remove();
                }
            }

            // 清理所有文本節(jié)點,但保留標(biāo)簽結(jié)構(gòu)和重要屬性
            CleanTextNodes(doc.DocumentNode);

            // 獲取清理后的HTML結(jié)構(gòu)
            string cleanHtml = doc.DocumentNode.OuterHtml;

            // 進(jìn)一步清理和格式化
            cleanHtml = Regex.Replace(cleanHtml, @">\s+<", "><"); // 移除標(biāo)簽間空白
            cleanHtml = Regex.Replace(cleanHtml, @"\s+", " ");    // 合并多余空白

            // 限制長度
            if (cleanHtml.Length > 5000)
            {
                Console.WriteLine($"?? HTML結(jié)構(gòu)過長({cleanHtml.Length}字符),截取前5000字符");
                cleanHtml = cleanHtml.Substring(0, 5000) + "...[HTML結(jié)構(gòu)已截斷]";
            }

            return cleanHtml;
        }

        // 內(nèi)容提取方法
        public static string ExtractContentBySelector(string url, string cssSelector, string html)
        {
            // 使用相同的反爬蟲策略
            try
            {
                var doc = new HtmlDocument();
                doc.LoadHtml(html);

                // 解析CSS選擇器并查找對應(yīng)元素
                HtmlNode targetNode = null;

                try
                {
                    cssSelector = cssSelector.Trim();

                    if (cssSelector.StartsWith("#"))
                    {
                        string id = cssSelector.Substring(1);
                        targetNode = doc.DocumentNode.SelectSingleNode($"http://*[@id='{id}']");
                    }
                    elseif (cssSelector.StartsWith("."))
                    {
                        string className = cssSelector.Substring(1);
                        targetNode = doc.DocumentNode.SelectSingleNode($"http://*[contains(@class, '{className}')]");
                    }
                    else
                    {
                        targetNode = doc.DocumentNode.SelectSingleNode($"http://{cssSelector}");
                    }
                }
                catch
                {
                    Console.WriteLine($"?? 選擇器解析失敗,嘗試標(biāo)簽選擇器:{cssSelector}");
                    targetNode = doc.DocumentNode.SelectSingleNode($"http://{cssSelector}");
                }

                if (targetNode == null)
                {
                    Console.WriteLine($"? 未找到匹配選擇器的元素:{cssSelector}");
                    return null;
                }

                Console.WriteLine($"? 成功找到目標(biāo)元素:{cssSelector}");

                // 清理不需要的標(biāo)簽
                var tagsToRemove = new[] { "script", "style", "nav", "header", "footer", "aside", "iframe", "noscript" };
                foreach (var tag in tagsToRemove)
                {
                    var nodes = targetNode.SelectNodes($".//{tag}");
                    if (nodes != null)
                    {
                        foreach (var node in nodes.ToArray())
                        {
                            node.Remove();
                        }
                    }
                }

                // 提取文本內(nèi)容
                string textContent = targetNode.InnerText ?? "";

                // 清理格式
                textContent = System.Net.WebUtility.HtmlDecode(textContent);
                textContent = Regex.Replace(textContent, @"\s+", " ");
                textContent = Regex.Replace(textContent, @"^\s+|\s+$", "", RegexOptions.Multiline);
                textContent = textContent.Trim();

                // 限制長度
                if (textContent.Length > 8000)
                {
                    Console.WriteLine($"?? 內(nèi)容過長({textContent.Length}字符),截取前8000字符");
                    textContent = textContent.Substring(0, 8000) + "...[內(nèi)容已截斷]";
                }

                Console.WriteLine($"? 成功提取內(nèi)容,長度:{textContent.Length} 字符");
                return textContent;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"? 內(nèi)容提取失?。簕ex.Message}");
                return null;
            }
        }

        // 設(shè)置瀏覽器請求頭的輔助方法
        private static void SetBrowserHeaders(HttpClient client)
        {
            client.DefaultRequestHeaders.Clear();
            client.DefaultRequestHeaders.Add("User-Agent",
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0");

            client.DefaultRequestHeaders.Add("Accept",
                "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8");

            client.DefaultRequestHeaders.Add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
            client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
            client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");

            // 隨機(jī)Referer
            var referers = new[] { "https://www.google.com/", "https://www.bing.com/", "https://www.baidu.com/" };
            client.DefaultRequestHeaders.Add("Referer", referers[new Random().Next(referers.Length)]);

            client.DefaultRequestHeaders.Add("Sec-Fetch-Dest", "document");
            client.DefaultRequestHeaders.Add("Sec-Fetch-Mode", "navigate");
            client.DefaultRequestHeaders.Add("Sec-Fetch-Site", "cross-site");
            client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
        }

        // 遞歸清理文本節(jié)點,保留標(biāo)簽結(jié)構(gòu)
        private static void CleanTextNodes(HtmlNode node)
        {
            if (node.NodeType == HtmlNodeType.Text)
            {
                // 保留標(biāo)簽結(jié)構(gòu)信息,但清空文本內(nèi)容
                if (!string.IsNullOrWhiteSpace(node.InnerText))
                {
                    node.InnerHtml = "[TEXT]"; // 用占位符表示這里有文本
                }
            }
            else
            {
                // 保留重要屬性:id, class, 標(biāo)簽名
                var attributesToKeep = new[] { "id", "class", "role", "data-role" };
                var attributesToRemove = node.Attributes
                    .Where(attr => !attributesToKeep.Contains(attr.Name.ToLower()))
                    .ToArray();

                foreach (var attr in attributesToRemove)
                {
                    node.Attributes.Remove(attr);
                }

                // 遞歸處理子節(jié)點
                foreach (var child in node.ChildNodes.ToArray())
                {
                    CleanTextNodes(child);
                }
            }
        }

    }
}

實際應(yīng)用效果

性能優(yōu)化亮點

  1. Token消耗控制通過結(jié)構(gòu)化分析,將原本可能需要幾千token的完整HTML內(nèi)容,壓縮到幾百token的結(jié)構(gòu)信息
  2. 準(zhǔn)確率提升AI專注于結(jié)構(gòu)分析,而非內(nèi)容理解,大大提高了選擇器推薦的準(zhǔn)確性
  3. 通用性強(qiáng)一套代碼適用于各種網(wǎng)站結(jié)構(gòu),無需針對性適配

開發(fā)避坑指南

坑點一:HttpClient配置不當(dāng)

問題:很多網(wǎng)站返回亂碼或空內(nèi)容

解決方案:必須設(shè)置AutomaticDecompression處理gzip壓縮

坑點二:AI提示詞過于復(fù)雜

問題:AI返回冗長的分析文字而非選擇器

解決方案:明確指示"只返回選擇器,不要其他解釋文字"

坑點三:內(nèi)容長度限制

問題:超長內(nèi)容導(dǎo)致API調(diào)用失敗

解決方案:合理設(shè)置內(nèi)容截斷(HTML結(jié)構(gòu)5000字符,正文內(nèi)容8000字符)

總結(jié)與思考

這個智能網(wǎng)頁內(nèi)容提取工具展現(xiàn)了AI + 傳統(tǒng)技術(shù)結(jié)合的強(qiáng)大威力:

核心創(chuàng)新:讓AI分析網(wǎng)頁結(jié)構(gòu)而非內(nèi)容,實現(xiàn)了精準(zhǔn)度和效率的完美平衡

技術(shù)融合:HtmlAgilityPack處理HTML解析,Semantic Kernel驅(qū)動AI能力,HttpClient處理網(wǎng)絡(luò)請求

實用價值:一套代碼適配所有網(wǎng)站,大幅降低了網(wǎng)頁內(nèi)容提取的開發(fā)成本

思考題

  1. 如何進(jìn)一步優(yōu)化AI提示詞,讓選擇器推薦更加精準(zhǔn)?
  2. 面對動態(tài)加載的SPA應(yīng)用,這套方案還需要哪些改進(jìn)?

如果你正在開發(fā)內(nèi)容聚合、信息監(jiān)控或知識管理系統(tǒng),這個方案絕對值得深入研究和應(yīng)用。覺得有用請轉(zhuǎn)發(fā)給更多同行,讓AI賦能更多C#開發(fā)者!

責(zé)任編輯:武曉燕 來源: 技術(shù)老小子
相關(guān)推薦

2025-06-27 08:14:05

2021-12-12 08:37:18

結(jié)構(gòu)化數(shù)據(jù)非結(jié)構(gòu)化數(shù)據(jù)數(shù)據(jù)

2018-04-03 14:00:03

結(jié)構(gòu)化數(shù)據(jù)非結(jié)構(gòu)化數(shù)據(jù)數(shù)據(jù)庫

2024-05-27 00:32:45

2024-02-19 08:19:25

結(jié)構(gòu)化綁定C++17C++

2025-10-22 18:04:52

2024-05-10 12:52:01

2010-07-16 11:24:23

IBM企業(yè)內(nèi)容管理

2025-01-10 13:56:28

2025-10-31 11:26:48

2023-12-25 15:00:18

結(jié)構(gòu)化布線光纖

2023-01-04 11:04:32

2010-11-08 10:20:18

2019-09-25 08:35:21

非結(jié)構(gòu)化數(shù)據(jù)人工智能數(shù)據(jù)科學(xué)

2012-02-08 15:54:05

ibmdw

2009-08-27 16:03:31

從c#到c++

2024-12-30 11:07:37

政務(wù)人工智能技術(shù)

2025-07-23 08:00:00

點贊
收藏

51CTO技術(shù)棧公眾號