尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

C#网络爬虫实战:企业内网数据采集与自动化导出

发布时间:2026/9/26 9:50:34

资讯中心
01
ARTICLE

C#网络爬虫实战:企业内网数据采集与自动化导出

C#网络爬虫实战:企业内网数据采集与自动化导出
简介这是一份面向C#初学者与进阶开发者的学习型网络爬虫项目源码聚焦Web数据采集核心能力训练适用于课程设计、毕业设计及实际工具开发场景。资源包含867个文件主体为413个C#源码文件.cs辅以34个项目配置文件.csproj、7个解决方案文件.sln、22个文本说明.txt及59个HTML示例页完整呈现从HTTP请求、HtmlAgilityPack解析、多线程调度到本地存储的全流程实现压缩包仅5.15MB轻量易部署。已有156人学习下载项目结构规范按Models、Services、Utilities、Configs等模块分层组织内置单元测试Tests目录与构建脚本.build文件并涵盖代理配置、异常日志、反爬基础策略等工程化实践要点可直接运行、调试与二次扩展。1. 为什么用 C# 写网络爬虫不是“反常识”而是一线工业场景里的务实选择很多人看到“C# 网络爬虫”第一反应是Python 不香吗Scrapy、Requests、BeautifulSoup 三件套一敲就跑C# 做爬虫是不是大炮打蚊子——这其实是把技术选型和场景割裂开了。真实产线里大量 Windows 桌面端系统、MES 上位机、工控数据采集平台、企业内网报表生成工具底层就是 .NET Framework 或 .NET 6 的 WinForms/WPF 应用它们要从内部 OA、ERP、PLM 系统比如用 ASP.NET MVC 构建的老旧后台抓取结构化数据或定时拉取 OPC UA 服务器暴露的 XML 接口、JSON-RPC 端点、甚至带 Form 表单验证的 Web 页面这时候硬塞 Python 脚本进去得搭环境、配 PATH、处理 DLL 依赖、跨进程通信……反而比直接用HttpClientHtmlAgilityPackSystem.Text.Json写一个可双击运行的.exe更慢、更不可靠。这个标题里的“基于 C# 网络爬虫软件程序设计”本质不是教你怎么写个通用爬虫框架而是解决一类具体问题在已有 .NET 技术栈的封闭/半封闭企业环境中用最小侵入方式把网页、API、XML/JSON 接口的数据稳定、可维护、可调试地“搬”进本地数据库或 Excel 报表里。它不追求高并发、分布式、反爬对抗但必须扛住 IE 内核兼容性、证书错误、302 跳转链断裂、Cookie 失效、CSRF Token 动态刷新这些“玄学”问题。源代码包.zip里真正值钱的从来不是那几行GetAsync()而是CookieContainer的生命周期管理、HttpClientHandler的 SSL/TLS 版本锁定、HtmlDocument解析时对 malformed HTML 的容错策略——这些才是你上线后不被半夜电话叫醒的关键。适合谁不是刚学 C# 的学生照着《C# 程序设计》抄控制台输出而是✅ 已接手一个用 WinForms 做的设备监控软件老板说“把车间日报页的数据自动导出到 Excel”✅ 正在开发上位机需要从西门子 S7-1500 的 Web ServerHTTP 接口读取诊断日志✅ 维护一套老 OA 系统其审批流状态只在网页表格里显示没提供 API但必须每天 8:00 同步到 SQL Server。——这类需求C# 爬虫不是“能用”而是“最省心”。2. 从零启动用 .NET 6 搭建可调试、可部署的爬虫核心骨架2.1 为什么选 .NET 6 而非 .NET Framework三个硬约束决定的很多老项目还在用 .NET Framework 4.7.2但新写的爬虫程序我一律推荐从 .NET 6 开始。这不是赶时髦而是三个现实约束逼出来的TLS 版本兼容性大量企业内网系统尤其是老旧 Java WebLogic 或 IIS 7.5 部署的后台只支持 TLS 1.2而 .NET Framework 默认启用 TLS 1.0/1.1强行调用会抛AuthenticationException。.NET 6 默认禁用 TLS 1.0/1.1且可通过AppContext.SetSwitch(System.Net.Http.UseTransportSecurity, true)精确控制。HttpClient 生命周期管理Framework 下HttpClient长期滥用导致 socket 耗尽是经典翻车现场.NET 6 内置IHttpClientFactory配合 DI 容器自动复用连接池避免SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space。JSON 解析性能与易用性System.Text.Json在 .NET 6 中已完全成熟序列化速度比 Newtonsoft.Json 快 2–3 倍且原生支持JsonSerializerOptions.PropertyNamingPolicy JsonNamingPolicy.CamelCase对接主流 REST API 无需额外转换层。提示如果你必须兼容 Framework比如客户明确要求 .NET 4.6.1请跳过本节直接看第 4 章的兼容方案但请做好心理准备——你要手动管理ServicePointManager.SecurityProtocol和HttpClient实例池。2.2 创建最小可运行项目命令行 依赖注入 配置驱动我们不建 WinForms 界面先用控制台项目验证核心逻辑。这样便于单元测试、CI/CD 集成也方便后期包装成 Windows Service。dotnet new console -n CrawlerCore -f net6.0 cd CrawlerCore dotnet add package HtmlAgilityPack --version 1.11.40 dotnet add package Microsoft.Extensions.DependencyInjection --version 6.0.0 dotnet add package Microsoft.Extensions.Configuration.Json --version 6.0.0 dotnet add package Microsoft.Extensions.Logging.Console --version 6.0.0关键不是装包而是初始化顺序HttpClient必须通过IHttpClientFactory获取不能new HttpClient()配置必须从appsettings.json加载不能硬编码 URL 和超时日志必须用ILoggerT方便后期接入 Serilog 或写入文件。Program.cs主入口精简版保留 DI 核心using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; var host Host.CreateDefaultBuilder(args) .ConfigureServices((context, services) { // 1. 注册配置读 appsettings.json services.ConfigureCrawlerOptions(context.Configuration.GetSection(Crawler)); // 2. 注册 HttpClientFactory关键 services.AddHttpClientWebClientService(client { client.BaseAddress new Uri(context.Configuration[Crawler:BaseUrl] ?? https://example.com/); client.Timeout TimeSpan.FromSeconds(30); }) .ConfigurePrimaryHttpMessageHandler(() new HttpClientHandler { // 强制 TLS 1.2绕过老旧服务器的协议协商失败 SslProtocols System.Security.Authentication.SslProtocols.Tls12, // 自动处理 Cookie登录态维持必需 UseCookies true, CookieContainer new CookieContainer() }); // 3. 注册业务服务 services.AddScopedWebClientService(); services.AddScopedDataProcessor(); }) .Build(); // 执行爬取逻辑 var crawler host.Services.GetRequiredServiceWebClientService(); await crawler.RunAsync();appsettings.json示例注意生产环境务必用appsettings.Production.json分离敏感配置{ Logging: { LogLevel: { Default: Information } }, Crawler: { BaseUrl: https://intranet.oa.company/, LoginUrl: /login, DataUrl: /report/daily?date{0}, TimeoutSeconds: 30, MaxRetry: 3 } }参数说明BaseUrl是根域名所有请求路径拼接在此之后DataUrl中的{0}是日期占位符后续用string.Format()替换MaxRetry不是 HttpClient 内置重试而是业务层手动循环——因为某些 503 错误需等待 2 秒再试而Polly策略太重此处用简单 while 循环更可控。2.3 核心服务类WebClientService —— 封装登录、请求、解析三步闭环爬虫不是发个 GET 就完事。真实场景中90% 的目标网站需要先 POST 登录表单获取 Cookie再用该 Cookie 访问受保护页面。WebClientService就是干这个的它持有IHttpClientFactory创建的客户端封装了登录态维持、错误重试、HTML 解析入口。public class WebClientService { private readonly IHttpClientFactory _httpClientFactory; private readonly ILoggerWebClientService _logger; private readonly CrawlerOptions _options; public WebClientService(IHttpClientFactory httpClientFactory, ILoggerWebClientService logger, IOptionsCrawlerOptions options) { _httpClientFactory httpClientFactory; _logger logger; _options options.Value; } public async Task RunAsync() { try { // Step 1: 登录POST 表单 await LoginAsync(); // Step 2: 获取数据页GET 带日期参数 var today DateTime.Today.ToString(yyyy-MM-dd); var dataUrl string.Format(_options.DataUrl, today); var html await GetHtmlAsync(dataUrl); // Step 3: 解析 HTML 表格 var rows ParseTable(html); _logger.LogInformation(成功解析 {Count} 行数据, rows.Count); // Step 4: 保存此处简化为打印实际调 DataProcessor foreach (var row in rows.Take(5)) Console.WriteLine($[{row.Date}] {row.Machine}: {row.Status}); } catch (Exception ex) { _logger.LogError(ex, 爬取流程异常终止); throw; } } private async Task LoginAsync() { var client _httpClientFactory.CreateClient(); var loginUri new Uri(_options.BaseUrl _options.LoginUrl); // 构造登录表单常见字段username/password/__RequestVerificationToken var content new FormUrlEncodedContent(new Dictionarystring, string { [username] admin, [password] 123456, [__RequestVerificationToken] await GetAntiForgeryToken(client, loginUri) }); var response await client.PostAsync(loginUri, content); response.EnsureSuccessStatusCode(); // 若返回 401/403此处直接抛异常 _logger.LogInformation(登录成功Cookie 已自动保存); } private async Taskstring GetAntiForgeryToken(HttpClient client, Uri loginUri) { // 先 GET 登录页提取隐藏字段 __RequestVerificationToken var html await client.GetStringAsync(loginUri); var doc new HtmlDocument(); doc.LoadHtml(html); var tokenNode doc.DocumentNode.SelectSingleNode(//input[name__RequestVerificationToken]); return tokenNode?.GetAttributeValue(value, ) ?? ; } private async Taskstring GetHtmlAsync(string url) { var client _httpClientFactory.CreateClient(); var fullUrl new Uri(_options.BaseUrl url); for (int i 0; i _options.MaxRetry; i) { try { var response await client.GetAsync(fullUrl); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } catch (HttpRequestException ex) when (ex.StatusCode System.Net.HttpStatusCode.ServiceUnavailable i _options.MaxRetry - 1) { _logger.LogWarning(第 {Retry} 次请求 {Url} 失败{Delay}s 后重试, i 1, url, 2); await Task.Delay(TimeSpan.FromSeconds(2)); } } throw new InvalidOperationException($重试 {_options.MaxRetry} 次后仍无法访问 {url}); } private ListReportRow ParseTable(string html) { var doc new HtmlDocument(); doc.LoadHtml(html); var rows new ListReportRow(); var table doc.DocumentNode.SelectSingleNode(//table[iddaily-report]); // 定位目标表格 if (table null) throw new InvalidOperationException(未找到 ID 为 daily-report 的表格); foreach (var rowNode in table.SelectNodes(tr).Skip(1)) // 跳过表头 { var cells rowNode.SelectNodes(td); if (cells?.Count 4) continue; // 至少 4 列日期、设备、状态、备注 rows.Add(new ReportRow { Date cells[0].InnerText.Trim(), Machine cells[1].InnerText.Trim(), Status cells[2].InnerText.Trim(), Remark cells[3].InnerText.Trim() }); } return rows; } } public class CrawlerOptions { public string BaseUrl { get; set; } ; public string LoginUrl { get; set; } /login; public string DataUrl { get; set; } /report/daily?date{0}; public int MaxRetry { get; set; } 3; } public class ReportRow { public string Date { get; set; } ; public string Machine { get; set; } ; public string Status { get; set; } ; public string Remark { get; set; } ; }逻辑说明LoginAsync()先 GET 登录页提取 CSRF Token再 POST 表单——这是绕过 ASP.NET MVC 防伪机制的标准做法GetHtmlAsync()带重试逻辑但只对503 Service Unavailable重试其他错误如 404、401 直接失败避免无限循环ParseTable()用 XPath 定位表格Skip(1)跳过表头SelectNodes(td)提取单元格——HtmlAgilityPack对 malformed HTML如trtdxxx/tr/td有极强容错能力比正则或System.Xml更稳所有异常都经由_logger记录方便排查是网络问题、登录失效还是 HTML 结构变更。3. 解析与落地从 HTML 表格到结构化数据的三道关卡3.1 HtmlAgilityPack 的 XPath 写法避坑指南为什么//table/tr/td总是空新手常犯的错误是看到网页源码里有tabletrtd数据/td/tr/table就写doc.DocumentNode.SelectNodes(//table/tr/td)结果返回null。原因有三现象原因解决方案SelectNodes返回空集合HTML 未闭合标签如trtdxxx/td缺/tr导致 DOM 树解析失败用doc.OptionFixNestedTags true启用自动修复默认 falseInnerText取到空字符串td内含span或divInnerText只取直接文本节点改用cell.InnerText.Trim().Replace(\r\n, ).Replace(\t, )或cell.DescendantsAndSelf().Where(x x.NodeType HtmlNodeType.Text).Select(x x.InnerText).Aggregate((a,b)ab)XPath 匹配不到元素浏览器开发者工具看到的 HTML 是 JS 渲染后的而HttpClient获取的是原始 HTML先用File.WriteAllText(debug.html, html)保存响应内容用浏览器打开确认结构正确写法示例带容错private ListReportRow ParseTable(string html) { var doc new HtmlDocument(); doc.OptionFixNestedTags true; // 关键修复嵌套错误 doc.LoadHtml(html); // 方式1用 ID 精确定位最稳 var table doc.DocumentNode.SelectSingleNode(//table[iddaily-report]); // 方式2用 class 名若无 ID但 class 唯一 // var table doc.DocumentNode.SelectSingleNode(//table[contains(class, report-table)]); if (table null) { // 备用方案找第一个 table仅用于调试生产环境必须明确 table doc.DocumentNode.SelectSingleNode(//table); _logger.LogWarning(未找到指定 ID 表格降级使用第一个 table); } var rows new ListReportRow(); var trNodes table?.SelectNodes(tr); if (trNodes null || trNodes.Count 0) return rows; foreach (var tr in trNodes.Skip(1)) // Skip(1) 跳过表头 { var tds tr.SelectNodes(td); if (tds null || tds.Count 4) continue; // 安全提取文本递归取所有 Text 节点并拼接 string GetText(HtmlNode node) string.Join(, node.DescendantsAndSelf() .Where(x x.NodeType HtmlNodeType.Text) .Select(x x.InnerText)) .Trim() .Replace(\r, ).Replace(\n, ).Replace(\t, ); rows.Add(new ReportRow { Date GetText(tds[0]), Machine GetText(tds[1]), Status GetText(tds[2]), Remark GetText(tds[3]) }); } return rows; }注意GetText()函数是血泪经验——某次客户 OA 系统升级把td正常/td改成tdspan classstatus-ok正常/span/td旧代码tds[2].InnerText就返回空加了这层递归提取才救回。3.2 JSON/API 接口解析当目标系统提供 REST 而非 HTML 时不是所有系统都只有 HTML 页面。越来越多的 MES、SCADA 上位机开始暴露 JSON 接口如/api/v1/machines/status。此时HtmlAgilityPack退场System.Text.Json登场。假设接口返回{ code: 0, msg: success, data: [ { machineId: M001, status: RUNNING, lastUpdate: 2024-05-20T08:30:15Z, temperature: 65.2 } ] }解析代码强类型 错误处理public class ApiResponseT { public int Code { get; set; } public string Msg { get; set; } ; public ListT Data { get; set; } new(); } public class MachineStatus { public string MachineId { get; set; } ; public string Status { get; set; } ; public DateTime LastUpdate { get; set; } public double Temperature { get; set; } } // 在 WebClientService 中新增方法 private async TaskListMachineStatus GetApiDataAsync() { var client _httpClientFactory.CreateClient(); var apiUri new Uri(_options.BaseUrl /api/v1/machines/status); try { var json await client.GetStringAsync(apiUri); var response JsonSerializer.DeserializeApiResponseMachineStatus(json, new JsonSerializerOptions { PropertyNameCaseInsensitive true }); if (response.Code ! 0) throw new InvalidOperationException($API 返回错误码 {response.Code}: {response.Msg}); return response.Data; } catch (JsonException ex) { _logger.LogError(ex, JSON 解析失败原始响应{RawJson}, await client.GetStringAsync(apiUri)); throw; } }参数说明PropertyNameCaseInsensitive true是必备开关否则lastUpdate字段无法映射到LastUpdate属性DateTime默认解析 ISO 8601 字符串如2024-05-20T08:30:15Z无需额外配置若遇到2024-05-20 08:30:15这种格式需自定义JsonConverterDateTime。3.3 数据落地不写数据库先搞定 Excel 导出一线最刚需客户不要中间过程只要最终 Excel。EPPlus是 .NET 生态最成熟的 Excel 库支持 .xlsx 且无需 Office 依赖。dotnet add package EPPlus --version 6.2.12添加导出方法DataProcessor类public class DataProcessor { private readonly ILoggerDataProcessor _logger; public DataProcessor(ILoggerDataProcessor logger) { _logger logger; } public void ExportToExcel(ListReportRow data, string outputPath) { ExcelPackage.LicenseContext LicenseContext.NonCommercial; // 开源版限制生产请购商业许可 using var package new ExcelPackage(); var worksheet package.Workbook.Worksheets.Add(日报); // 写表头 worksheet.Cells[1, 1].Value 日期; worksheet.Cells[1, 2].Value 设备编号; worksheet.Cells[1, 3].Value 运行状态; worksheet.Cells[1, 4].Value 备注; // 写数据从第2行开始 for (int i 0; i data.Count; i) { worksheet.Cells[i 2, 1].Value data[i].Date; worksheet.Cells[i 2, 2].Value data[i].Machine; worksheet.Cells[i 2, 3].Value data[i].Status; worksheet.Cells[i 2, 4].Value data[i].Remark; } // 自动列宽 worksheet.Cells.AutoFitColumns(); // 保存 File.WriteAllBytes(outputPath, package.GetAsByteArray()); _logger.LogInformation(已导出 {Count} 行数据至 {Path}, data.Count, outputPath); } }关键细节LicenseContext.NonCommercial是开源版强制要求若用于商业系统必须购买 EPPlus 商业许可约 $299/年AutoFitColumns()避免中文列宽过窄但大数据量时会变慢可改用worksheet.Column(i).Width 20;手动设宽GetAsByteArray()比SaveAs()更安全——后者可能因文件被占用抛IOException前者内存操作无风险。4. 避坑 / 常见问题 / 排查C# 爬虫上线后被叫醒的 4 个深夜电话4.1 现象程序运行几天后突然报SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space原因HttpClient实例未复用每次new HttpClient()都新建 TCP 连接Windows 默认每个进程最多 1024 个 socket 句柄。爬虫每分钟请求 10 次2 小时就耗尽。解决✅ 严格使用IHttpClientFactory见第 2 章绝不new HttpClient()✅ 若必须手动创建如特殊证书验证用static readonly HttpClient单例并设置Timeout❌ 禁止在using (var client new HttpClient())中使用——Dispose()会关闭连接池。4.2 现象登录成功但后续请求返回 401 Unauthorized原因目标网站使用SameSiteStrict的 Cookie而 .NET 6 默认HttpClientHandler的UseCookies true无法处理跨域跳转中的 Strict Cookie。常见于登录后重定向到/dashboard时 Cookie 丢失。解决在HttpClientHandler初始化时显式设置CookieContainer并禁用AutomaticDecompression有时 gzip 压缩干扰 Cookie 解析var handler new HttpClientHandler { UseCookies true, CookieContainer new CookieContainer(), AutomaticDecompression DecompressionMethods.None // 关键 };4.3 现象HtmlAgilityPack解析出的InnerText包含大量\r\n\t且中文乱码显示为 原因网页响应头未声明charsetHttpClient.GetStringAsync()默认用 UTF-8 解码但网页实际是 GB2312 或 UTF-8 with BOM。解决不用GetStringAsync()改用GetByteArrayAsync() 手动解码private async Taskstring GetHtmlAsync(string url) { var client _httpClientFactory.CreateClient(); var fullUrl new Uri(_options.BaseUrl url); var response await client.GetAsync(fullUrl); response.EnsureSuccessStatusCode(); var bytes await response.Content.ReadAsByteArrayAsync(); // 尝试从响应头获取 charset var charset response.Content.Headers.ContentType?.CharSet ?? utf-8; // 若响应头无 charset用 HtmlAgilityPack 自动探测 if (string.IsNullOrEmpty(charset)) { var htmlStr Encoding.Default.GetString(bytes); // 先用系统默认编码粗略读 var doc new HtmlDocument(); doc.LoadHtml(htmlStr); var metaCharset doc.DocumentNode.SelectSingleNode(//meta[charset]); charset metaCharset?.GetAttributeValue(charset, utf-8) ?? utf-8; } return Encoding.GetEncoding(charset).GetString(bytes); }4.4 现象程序在服务器上运行正常在客户现场 Win10 机器上报HttpRequestException: The SSL connection could not be established原因客户机器 TLS 1.2 未启用尤其 Win7/Win10 旧版或证书链不完整自签名证书未导入受信任根证书。解决在Program.cs最顶部Main方法第一行强制启用 TLS 1.2// 必须放在 Main() 第一行 AppContext.SetSwitch(System.Net.Http.UseTransportSecurity, true); ServicePointManager.SecurityProtocol SecurityProtocolType.Tls12;注意ServicePointManager是全局静态类必须在任何HttpClient创建前设置否则无效。5. 进阶技巧让爬虫从“能跑”变成“敢放生产”的 3 个硬核习惯5.1 用IHostedService封装为 Windows Service告别双击运行控制台程序适合调试但生产环境必须是 Windows Service——开机自启、崩溃自恢复、日志集中管理。.NET 6的IHostedService是最佳实践。新建CrawlerHostedService.cspublic class CrawlerHostedService : IHostedService, IDisposable { private readonly ILoggerCrawlerHostedService _logger; private readonly WebClientService _crawler; private Timer _timer; public CrawlerHostedService(ILoggerCrawlerHostedService logger, WebClientService crawler) { _logger logger; _crawler crawler; } public Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation(爬虫服务启动); // 每天 8:00 执行一次可按需调整 _timer new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromHours(24)); return Task.CompletedTask; } private async void DoWork(object state) { try { _logger.LogInformation(开始执行每日爬取任务); await _crawler.RunAsync(); _logger.LogInformation(每日爬取任务完成); } catch (Exception ex) { _logger.LogError(ex, 每日爬取任务异常); } } public Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation(爬虫服务停止); _timer?.Change(Timeout.Infinite, 0); return Task.CompletedTask; } public void Dispose() { _timer?.Dispose(); } }注册服务Program.cs中services.AddHostedServiceCrawlerHostedService();发布为 Service# 发布自包含 exe dotnet publish -c Release -r win-x64 --self-contained true -o ./publish # 安装为服务管理员权限 sc create CrawlerService binPath C:\path\to\publish\CrawlerCore.exe start auto sc start CrawlerService # 查看日志事件查看器 → Windows 日志 → 应用程序优势无需用户登录系统启动即运行sc命令可远程管理比 Task Scheduler 更可靠异常直接写入 Windows Event Log运维可统一监控。5.2 添加断点续爬与幂等校验防止重复抓取和数据错乱客户最怕的不是爬不到而是“今天爬了 100 条明天又爬 100 条数据库里存了 200 条重复”。解决方案每次爬取前先查数据库是否存在当天数据存在则跳过。在WebClientService.RunAsync()开头加入// 检查今日数据是否已存在假设用 SqlServer var connectionString Serverlocalhost;DatabaseCrawlerDB;Trusted_Connectiontrue;; using var conn new SqlConnection(connectionString); await conn.OpenAsync(); var cmd new SqlCommand(SELECT COUNT(1) FROM DailyReports WHERE ReportDate date, conn); cmd.Parameters.AddWithValue(date, DateTime.Today); var count (int)await cmd.ExecuteScalarAsync(); if (count 0) { _logger.LogInformation(今日数据已存在跳过爬取); return; }进阶若目标系统无日期参数如只提供最新一页则用MD5(html)作为指纹存库对比指纹决定是否更新——避免 HTML 微小变动如时间戳触发误更新。5.3 日志分级与告警把“半夜电话”变成“邮件通知”Microsoft.Extensions.Logging默认只输出到 Console生产必须接入持久化。最轻量方案写入文件 邮件告警。安装包dotnet add package Serilog.Sinks.File --version 5.0.0 dotnet add package Serilog.Sinks.Email --version 3.0.0配置Program.csLog.Logger new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.File(logs/crawler-.log, rollingInterval: RollingInterval.Day) .WriteTo.Email(new EmailConnectionInfo { FromEmail crawlercompany.com, ToEmail admincompany.com, MailServer smtp.company.com, NetworkCredentials new NetworkCredential(crawler, pwd), Port 25 }, restrictedToMinimumLevel: LogEventLevel.Error) // 仅错误发邮件 .CreateLogger(); var host Host.CreateDefaultBuilder(args) .UseSerilog() // 替换默认 logger ...效果正常日志写入logs/crawler-20240520.log按天滚动LogError自动触发邮件标题含Crawler Error: [异常类型]正文含堆栈运维不用守着屏幕微信收到邮件就知道哪台机器的爬虫挂了。我做 C# 爬虫五年从给车间老师傅写“一键导出日报”小程序到给集团 ERP 做跨系统数据同步管道踩过的最大坑不是代码写错而是低估了企业环境的“不标准”——IE 兼容模式、自签名证书、没有 API 只有 HTML、登录页每年换一次 CSRF Token 生成逻辑……所以现在我的原则是✅ 所有 HTTP 请求必带try-catch和ILogger✅ 所有 HTML 解析必先File.WriteAllText(debug.html, html)保存原始响应✅ 所有生产部署必用IHostedService Windows Service Serilog✅ 所有客户交付物必附README.md写明“如何修改登录账号”“如何调整爬取时间”“日志在哪查”。不是代码越炫越高级而是让运维同事能自己改密码、重启服务、看日志定位问题这才是真正的“可维护”。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。