基于C#与Selenium的动态网站爬取:无头Chrome加载判断与加载屏实现
体育博彩赛事爬取问题解决方案
问题背景
为大学项目开发体育博彩网站爬取应用,需获取当日足球、网球、篮球及手球的赛事与联赛信息。当前使用Selenium做页面导航和滚动爬取,耗时且资源占用高;改用Headless Chrome后,无法判断页面是否完全加载,同时需要实现爬取完成前的加载屏。
一、Headless Chrome页面加载判断优化
原代码仅依赖document.readyState判断页面加载,无法覆盖动态滚动加载的内容。以下是针对性优化方案:
核心优化点
- 显式等待核心元素:不再仅等待页面状态,改为等待页面中第一个赛事分组标题(
group-header__details-title)加载完成,确保核心渲染结构就绪。 - 滚动加载结束判断:用「连续两次滚动后赛事分组数量无变化」替代滚动位置判断,避免因页面高度计算误差导致的提前终止;同时增加滚动后的固定等待,保证动态内容渲染完成。
修改后的爬取函数
private static List<string> ScrapeLeagues(string url) { List<string> leagues = new List<string>(); Dictionary<string, double> pozitii = new Dictionary<string, double>(); IWebElement parent; string echipa1, echipa2; var timeout = 15000; var wait = new WebDriverWait(webDriver, TimeSpan.FromMilliseconds(timeout)); webDriver.Url = url; // 等待核心分组元素加载,替代单一的readyState判断 wait.Until(d => d.FindElements(By.ClassName("group-header__details-title")).Count > 0); // 初始抓取分组与赛事 CaptureLeaguesAndMatches(leagues, pozitii); int previousLeagueCount = leagues.Count; int consecutiveNoChange = 0; const int maxConsecutiveNoChange = 2; // 连续2次无新增则判定加载完成 while (consecutiveNoChange < maxConsecutiveNoChange) { // 平滑滚动到最后一个赛事元素 ((IJavaScriptExecutor)webDriver).ExecuteScript( "const elements = document.getElementsByClassName('event-row-container'); " + "if(elements.length > 0) elements[elements.length-1].scrollIntoView({behavior: 'smooth', block: 'end'});"); // 等待滚动后动态内容加载 Thread.Sleep(1000); // 可根据目标网站加载速度调整 wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete")); // 抓取新增的分组与赛事 CaptureLeaguesAndMatches(leagues, pozitii); // 更新判断状态 if (leagues.Count == previousLeagueCount) { consecutiveNoChange++; } else { consecutiveNoChange = 0; previousLeagueCount = leagues.Count; } } return leagues; } // 提取重复抓取逻辑为独立方法,提高代码复用性 private static void CaptureLeaguesAndMatches(List<string> leagues, Dictionary<string, double> pozitii) { var divs = webDriver.FindElements(By.ClassName("group-header__details-title")); foreach (IWebElement div in divs) { string leagueTitle = div.GetAttribute("title"); if (!pozitii.ContainsKey(leagueTitle)) { leagues.Add(leagueTitle); // 获取赛事分组的父容器 parent = (IWebElement)((IJavaScriptExecutor)webDriver) .ExecuteScript("return arguments[0].parentNode.parentNode.parentNode;", div); var meciuri = parent.FindElements(By.ClassName("event-row-container")); _evenimente.Add(new Eveniment(leagueTitle, meciuri.Count)); foreach (IWebElement meci in meciuri) { echipa1 = meci.FindElement(By.ClassName("event-summary__competitors-team1")).GetAttribute("title"); echipa2 = meci.FindElement(By.ClassName("event-summary__competitors-team2")).GetAttribute("title"); var cote = meci.FindElements(By.ClassName("e2e-odd-current-value")); if (cote.Count == 3) { _evenimente.Last().AdaugaMeci(echipa1, echipa2, cote[0].GetAttribute("innerHTML"), cote[1].GetAttribute("innerHTML"), cote[2].GetAttribute("innerHTML")); } else if (cote.Count == 2) { _evenimente.Last().AdaugaMeci(echipa1, echipa2, cote[0].GetAttribute("innerHTML"), cote[1].GetAttribute("innerHTML")); } } pozitii.Add(leagueTitle, Convert.ToDouble(div.Location.Y)); } } }
二、WinForms加载屏实现
利用异步线程分离爬取任务与UI线程,避免主窗体卡顿,同时在爬取过程中显示加载提示:
步骤1:创建加载窗口
新建WinForms窗口LoadingForm,添加一个Label控件显示文本(如「正在爬取赛事数据,请稍候...」),可配合ProgressBar实现动画效果。
步骤2:修改主窗体代码
private async void Form1_Load(object sender, EventArgs e) { using (var loadingForm = new LoadingForm()) { loadingForm.Show(); // 显示加载屏 try { // 异步执行爬取任务,避免阻塞UI线程 await Task.Run(() => { webProcessor = WebsiteProcessor.getInstance().Result; // 根据业务需求调用ScrapeLeagues,传入对应体育项目的URL // var footballLeagues = ScrapeLeagues("足球赛事页面URL"); // var tennisLeagues = ScrapeLeagues("网球赛事页面URL"); }); evenimente = WebsiteProcessor._evenimente; // 将爬取数据绑定到主窗体控件(如DataGridView) // dataGridViewEvents.DataSource = evenimente; } catch (Exception ex) { MessageBox.Show($"爬取失败:{ex.Message}"); } finally { loadingForm.Close(); // 爬取完成后关闭加载屏 } } }
内容的提问来源于stack exchange,提问作者sergiutzuu
相关产品推荐
相关产品推荐

