You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebView2如何在页面完全加载后执行JS下载页面首个条目

问题原因

CoreWebView2.NavigationCompleted 触发时机仅代表页面主文档初始DOM加载完成,不会等待页面内异步接口请求、前端JS动态渲染的内容加载完毕。你访问的穆迪搜索结果页是典型的前端动态渲染页面,事件触发时搜索结果列表的DOM节点还未插入页面,此时执行querySelector自然返回null。
除此之外你的现有代码还有两个会导致逻辑失效的问题:

  • 每次点击按钮都会重复绑定NavigationCompleted事件,后续触发时逻辑会重复执行多次
  • ExecuteScriptAsync是异步返回Task<string>的方法,你没有await就直接把返回的Task对象拼接到跳转地址,就算DOM加载完成也会得到错误的跳转路径
可靠实现方案

不要依赖原生导航完成事件判断动态内容加载状态,最稳定的方案是轮询检测目标元素是否存在,同时修正现有代码的逻辑问题:

核心实现逻辑

在导航到搜索页后,循环执行JS检测目标结果链接是否渲染完成,设置合理超时避免死循环,确认元素存在后再获取链接地址执行跳转,初始化阶段一次性绑定事件避免重复触发。
修正后的完整可运行代码如下:

using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using System;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        private bool _navEventBinded = false;
        public Form1()
        {
            InitializeComponent();
            InitializeAsyc();
        }

        async void InitializeAsyc()
        {
            await webView21.EnsureCoreWebView2Async(null);
            // 初始化阶段只绑定一次事件,避免重复绑定
            if (!_navEventBinded)
            {
                webView21.CoreWebView2.NavigationCompleted += CoreWebView2_NavigationCompleted;
                // 配置自动下载逻辑,跳转文件直链时自动保存
                webView21.CoreWebView2.DownloadStarting += (s, downloadArgs) =>
                {
                    var savePath = System.IO.Path.Combine(
                        Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), 
                        "Downloads", 
                        downloadArgs.ResultFilePath);
                    downloadArgs.ResultFilePath = savePath;
                    downloadArgs.Handled = true;
                };
                _navEventBinded = true;
            }
            webView21.CoreWebView2.Navigate("https://www.moodys.com");
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            var checkLoginJs = "document.documentElement.innerText.indexOf('Sign In or Register')>-1";
            string loginRes = await webView21.CoreWebView2.ExecuteScriptAsync(checkLoginJs);

            if (loginRes == "true")
            {
                MessageBox.Show("Please login Moody's");
                return;
            }
            
            MessageBox.Show("Login successful");
            webView21.CoreWebView2.Navigate("https://www.moodys.com/search?keyword=Annual%20default%20study%20excel&searchfrom=GS");
        }

        private async void CoreWebView2_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
        {
            // 只处理搜索页的加载逻辑,其他页面导航完成直接跳过
            if (!webView21.Source.AbsoluteUri.Contains("/search?keyword=Annual%20default%20study%20excel"))
                return;

            // 最长等待10秒加载结果
            var timeoutAt = DateTime.Now.AddSeconds(10);
            string firstEntryLink = string.Empty;
            while (DateTime.Now < timeoutAt)
            {
                var detectJs = @"
                (function(){
                    const targetNode = document.querySelector('.result-details > .result-detail:nth-child(1) a');
                    return targetNode ? targetNode.getAttribute('href') : '';
                })()";
                var jsResult = await webView21.CoreWebView2.ExecuteScriptAsync(detectJs);
                // WebView2返回的字符串是JSON序列化格式,需要去掉前后包裹的双引号
                firstEntryLink = jsResult.Trim('"');
                if (!string.IsNullOrEmpty(firstEntryLink))
                    break;
                // 每200毫秒检测一次,避免占用过高资源
                await Task.Delay(200);
            }

            if (string.IsNullOrEmpty(firstEntryLink))
            {
                MessageBox.Show("Search result load timeout, please retry");
                return;
            }

            // 兼容相对路径/绝对路径两种href格式
            var targetUrl = firstEntryLink.StartsWith("http") 
                ? firstEntryLink 
                : $"https://www.moodys.com{firstEntryLink}";
            webView21.CoreWebView2.Navigate(targetUrl);
        }
    }
}
注意事项
  • 不要尝试监听DOMContentLoaded或window.load事件替代轮询,这两个事件触发时机和NavigationCompleted基本一致,都不会等待异步渲染的内容加载,轮询检测目标元素是适配所有动态站点兼容性最高的方案
  • 轮询间隔建议设置为200-500毫秒,超时时间可根据站点实际响应速度调整到5-15秒区间
  • 所有ExecuteScriptAsync返回的字符串结果都带有JSON序列化包裹的双引号,使用前必须做去引号处理,否则会出现路径拼接错误

内容的提问来源于stack exchange,提问作者Jason Wong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 06:06:56