找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2646

积分

0

好友

342

主题
发表于 昨天 06:07 | 查看: 4| 回复: 0

Selenium 是一款免费、开源的自动化测试工具,支持多种编程语言。无论你使用的是 Java、Python、Ruby 还是 C#,都可以利用它来完成浏览器自动化操作。本文将基于一个具体的网页监控示例,介绍如何在 C# 项目中集成 Selenium 来实现对浏览器页面的模拟操作。

实现所需知识点

除了需要基础的 HTML、JavaScript 和 CSS 知识,本例的实现还涉及以下几个关键技术点:

  • log4net:一个流行的日志记录库,用于在程序运行过程中跟踪日志,方便问题排查。本例使用它来记录操作步骤和异常信息。
  • Queue(队列):遵循先进先出 (FIFO) 原则的数据结构。本示例用它来暂存日志信息,之后再展示到界面上。相关操作包括 Enqueue(添加元素到队列末尾)和 Dequeue(移除并返回队列头部的元素)。
  • IWebDriver:这是 Selenium 中定义浏览器驱动行为的核心接口。所有对浏览器的操作都通过这个接口进行,针对不同的浏览器有不同的实现,例如 InternetExplorerDriver 对应 IE 浏览器,ChromeDriver 对应 Chrome 浏览器。
  • BackgroundWorker: .NET 中的后台工作线程组件,用于在主线程之外执行耗时操作,并通过事件来反馈执行状态。

Selenium 安装步骤

本例使用 Visual Studio 2019 作为开发环境。我们可以通过内置的 NuGet 包管理器来安装所需的 Selenium 组件。

在 VS 的 NuGet 包管理器界面中搜索并安装以下核心包:Selenium.WebDriverSelenium.Support 以及对应浏览器(如 Chrome)的驱动程序包 Selenium.WebDriver.ChromeDriver

在VS2019的NuGet包管理器中安装Selenium.WebDriver及相关组件

示例运行效果

本示例程序模拟了使用 Chrome 浏览器自动登录网站,并持续监控指定商品库存状态的过程。当商品有货时,程序会自动执行加入购物车的操作。

C# Selenium自动化程序监控网页库存的示例运行界面

核心代码解析

浏览器自动化的核心在于元素定位和事件触发。在 C#/.NET 中,我们通过 IWebDriver 接口的 FindElement 方法配合不同的定位器(如 By.Id, By.XPath)来实现。

1. 初始化浏览器驱动

首先,需要根据配置初始化对应的浏览器驱动。这里以 Chrome 为例:

//谷歌浏览器
ChromeOptions options = new ChromeOptions();
this.driver = new ChromeDriver(options);

2. 通过ID定位元素并操作

定位到页面元素后,可以执行填充文本或点击等操作。

this.driver.FindElement(By.Id("email")).SendKeys(username);
this.driver.FindElement(By.Id("password")).SendKeys(password);
//# 7. 点击登录按钮
this.driver.FindElement(By.Id("sign-in")).Click();

3. 通过XPath定位元素

对于复杂或没有ID的元素,XPath 是强大的定位方式。

string xpath1 = "//div[@class=\"product-list\"]/div[@class=\"product\"]/div[@class=\"price-and-detail\"]/div[@class=\"price\"]/span[@class=\"noStock\"]";
string txt = this.driver.FindElement(By.XPath(xpath1)).Text;

4. 完整的自动化类示例

下面的 Smoking 类展示了如何将上述操作整合起来,实现一个完整的登录、搜索、监控流程。

using OpenQA.Selenium;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Chrome;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AiSmoking.Core
{
    public class Smoking
    {
        /// <summary>
        /// 是否正在运行
        /// </summary>
        private bool running = false;

        /// <summary>
        /// 驱动
        /// </summary>
        private IWebDriver driver = null;

        /// <summary>
        /// # 无货
        /// </summary>
        private string no_stock = "Currently Out of Stock";

        /// <summary>
        ///   # 线程等待秒数
        /// </summary>
        private int wait_sec = 2;

        private Dictionary<string, string> cfg_info;

        private string work_path = string.Empty;

        /// <summary>
        /// 构造函数
        /// </summary>
        public Smoking()
        {

        }

        /// <summary>
        /// 带参构造函数
        /// </summary>
        /// <param name="cfg_info"></param>
        /// <param name="work_path"></param>
        public Smoking(Dictionary<string, string> cfg_info,string work_path)
        {
            this.cfg_info = cfg_info;
            this.work_path = work_path;
            this.wait_sec = int.Parse(cfg_info["wait_sec"]);
            //# 如果小于2,则等于2
            this.wait_sec = (this.wait_sec < 2 ? 2 : this.wait_sec);
            this.wait_sec = this.wait_sec * 1000;
        }

        /// <summary>
        /// 开始跑
        /// </summary>
        public void startRun()
        {
            //"""运行起来"""
            try
            {
                this.running = true;
                string url = this.cfg_info["url"];
                string username = this.cfg_info["username"];
                string password = this.cfg_info["password"];
                string item_id = this.cfg_info["item_id"];
                if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(item_id))
                {
                    LogHelper.put("配置信息不全,请检查config.cfg文件是否为空,然后再重启");
                    return;
                }
                if (this.driver == null)
                {
                    string explorer = this.cfg_info["explorer"];
                    if (explorer == "Chrome")
                    {
                        //谷歌浏览器
                        ChromeOptions options = new ChromeOptions();
                        this.driver = new ChromeDriver(options);
                    }
                    else
                    {
                        //默认IE
                        var options = new InternetExplorerOptions();
                        //options.AddAdditionalCapability.('encoding=UTF-8')
                        //options.add_argument('Accept= text / css, * / *')
                        //options.add_argument('Accept - Language= zh - Hans - CN, zh - Hans;q = 0.5')
                        //options.add_argument('Accept - Encoding= gzip, deflate')
                        //options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko')
                        //# 2. 定义浏览器驱动对象
                        this.driver = new InternetExplorerDriver(options);
                    }
                }
                this.run(url, username, password, item_id);
            }
            catch (Exception e)
            {
                LogHelper.put("运行过程中出错,请重新打开再试"+e.StackTrace);
            }
        }

        /// <summary>
        /// 运行
        /// </summary>
        /// <param name="url"></param>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="item_id"></param>
        private void run(string url, string username, string password, string item_id)
        {
            //"""运行起来"""
            //# 3. 访问网站
            this.driver.Navigate().GoToUrl(url);
            //# 4. 最大化窗口
            this.driver.Manage().Window.Maximize();
            if (this.checkIsExists(By.LinkText("账户登录")))
            {
                //# 判断是否登录:未登录
                this.login(username, password);
            }
            if (this.checkIsExists(By.PartialLinkText("欢迎回来")))
            {
                //# 判断是否登录:已登录
                LogHelper.put("登录成功,下一步开始工作了");
                this.working(item_id);
            }
            else
            {
                LogHelper.put("登录失败,请设置账号密码");
            }
        }

        /// <summary>
        /// 停止运行
        /// </summary>
        public void stopRun()
        {
            //"""停止"""
            try
            {
                this.running = false;
                //# 如果驱动不为空,则关闭
                //self.close_browser_nicely(self.__driver)
                if (this.driver != null)
                {
                    this.driver.Quit();
                    //# 关闭后切要为None,否则启动报错
                    this.driver = null;
                }
            }
            catch (Exception e)
            {
                //print('Stop Failure')
            }
            finally
            {
                this.driver = null;
            }
        }

        private void login(string username, string password)
        {
            //# 5. 点击链接跳转到登录页面
            this.driver.FindElement(By.LinkText("账户登录")).Click();
            //# 6. 输入账号密码
            //# 判断是否加载完成
            if (this.checkIsExists(By.Id("email")))
            {
                this.driver.FindElement(By.Id("email")).SendKeys(username);
                this.driver.FindElement(By.Id("password")).SendKeys(password);
                //# 7. 点击登录按钮
                this.driver.FindElement(By.Id("sign-in")).Click();
            }
        }

        /// <summary>
        /// 工作状态
        /// </summary>
        /// <param name="item_id"></param>
        private void working(string item_id)
        {
            while (this.running)
            {
                try
                {
                    //# 正常获取信息
                    if (this.checkIsExists(By.Id("string")))
                    {
                        this.driver.FindElement(By.Id("string")).Clear();
                        this.driver.FindElement(By.Id("string")).SendKeys(item_id);
                        this.driver.FindElement(By.Id("string")).SendKeys(Keys.Enter);
                    }
                    //# 判断是否查询到商品
                    string xpath = "//div[@class=\"specialty-header search\"]/div[@class=\"specialty-description\"]/div[@class=\"gt-450\"]/span[2] ";
                    if (this.checkIsExists(By.XPath(xpath)))
                    {
                        int count = int.Parse(this.driver.FindElement(By.XPath(xpath)).Text);
                        if (count < 1)
                        {
                            Thread.Sleep(this.wait_sec);
                            LogHelper.put("没有查询到item id =" + item_id + "对应的信息");
                            continue;
                        }
                    }
                    else
                    {
                        Thread.Sleep(this.wait_sec);
                        LogHelper.put("没有查询到item id2 =" + item_id + "对应的信息");
                        continue;
                    }
                    //# 判断当前库存是否有货

                    string xpath1 = "//div[@class=\"product-list\"]/div[@class=\"product\"]/div[@class=\"price-and-detail\"]/div[@class=\"price\"]/span[@class=\"noStock\"]";
                    if (this.checkIsExists(By.XPath(xpath1)))
                    {
                        string txt = this.driver.FindElement(By.XPath(xpath1)).Text;
                        if (txt == this.no_stock)
                        {
                            //# 当前无货
                            Thread.Sleep(this.wait_sec);
                            LogHelper.put("查询一次" + item_id + ",无货");
                            continue;
                        }
                    }
                    //# 链接path1
                    string xpath2 = "//div[@class=\"product-list\"]/div[@class=\"product\"]/div[@class=\"imgDiv\"]/a";
                    //# 判断是否加载完毕
                    //# this.waiting((By.CLASS_NAME, "imgDiv"))
                    if (this.checkIsExists(By.XPath(xpath2)))
                    {
                        this.driver.FindElement(By.XPath(xpath2)).Click();
                        Thread.Sleep(this.wait_sec);
                        //# 加入购物车
                        if (this.checkIsExists(By.ClassName("add-to-cart")))
                        {
                            this.driver.FindElement(By.ClassName("add-to-cart")).Click();
                            LogHelper.put("加入购物车成功,商品item-id:" + item_id);
                            break;
                        }
                        else
                        {
                            LogHelper.put("未找到加入购物车按钮");
                        }
                    }
                    else
                    {
                        LogHelper.put("没有查询到,可能是商品编码不对,或者已下架");
                    }
                    Thread.Sleep(this.wait_sec);
                }
                catch (Exception e)
                {
                    Thread.Sleep(this.wait_sec);
                    LogHelper.put(e);
                }
            }
        }

        /// <summary>
        /// 判断是否存在
        /// </summary>
        /// <param name="by"></param>
        /// <returns></returns>
        private bool checkIsExists(By by)
        {
            try
            {
                int i = 0;
                while (this.running && i < 3)
                {
                    if (this.driver.FindElements(by).Count > 0)
                    {
                        break;
                    }
                    else
                    {
                        Thread.Sleep(this.wait_sec);
                        i = i + 1;
                    }
                }
                return this.driver.FindElements(by).Count > 0;
            }
            catch (Exception e)
            {
                LogHelper.put(e);
                return false;
            }
        }

    }
}

5. 日志辅助类

为了记录程序运行状态,我们还需要一个简单的日志帮助类 LogHelper。它内部使用了 log4net 进行持久化日志记录,并同时维护一个内存队列用于实时显示。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net;

[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace AiSmoking.Core
{
    /// <summary>
    /// 日志帮助类
    /// </summary>
    public static class LogHelper
    {
        /// <summary>
        /// 日志实例
        /// </summary>
        private static ILog logInstance = LogManager.GetLogger("smoking");

        private static Queue<string> queue = new Queue<string>(2000);

        public static void put(string msg)
        {
            queue.Enqueue(msg);
            WriteLog(msg, LogLevel.Info);
        }

        public static void put(Exception ex)
        {
            WriteLog(ex.StackTrace, LogLevel.Error);
        }

        public static string get()
        {
            if (queue.Count > 0)
            {
                return queue.Dequeue();
            }
            else
            {
                return string.Empty;
            }
        }

        public static void WriteLog(string message, LogLevel level)
        {
            switch (level)
            {
                case LogLevel.Debug:
                    logInstance.Debug(message);
                    break;
                case LogLevel.Error:
                    logInstance.Error(message);
                    break;
                case LogLevel.Fatal:
                    logInstance.Fatal(message);
                    break;
                case LogLevel.Info:
                    logInstance.Info(message);
                    break;
                case LogLevel.Warn:
                    logInstance.Warn(message);
                    break;
                default:
                    logInstance.Info(message);
                    break;
            }
        }

    }

    public enum LogLevel
    {
        Debug = 0,
        Error = 1,
        Fatal = 2,
        Info = 3,
        Warn = 4
    }
}

总结

通过上述步骤,我们展示了如何在 C# 项目中利用 Selenium 完成一次完整的浏览器自动化操作。从环境配置、元素定位、事件触发到状态监控和日志记录,这是一个典型的 Web 自动化测试 和 RPA(机器人流程自动化)应用场景。当然,实际应用中还需要考虑更完善的异常处理、动态页面等待机制以及反爬策略等。希望这个例子能为你上手 Selenium 提供清晰的路径。如果你对 C# 自动化开发有更多兴趣,欢迎到 云栈社区 的.NET板块交流探讨。




上一篇:信贷领域核心术语全解析:从业务场景到风控贷后的95个关键概念
下一篇:开源 Aegis.rs:基于 Rust 的毫秒级 LLM 本地安全代理实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 10:24 , Processed in 0.696111 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表