C# Selenium使用及诸多事项-I

Selenium的介绍

Selenium是一种web自动化测试的工具框架,现支持的浏览器足够的多。一般都是用于web的自动测试用,但同时,这种自动测试会使得经过特殊设计的程序(或脚本)来达成超出测试范畴的目的,即网络爬虫。基本上Selenium作为爬虫使用时,就像是人类自己打开某个网页并做某个事情,这极大加大一些安全人员对反机器人登录注册等问题的工作量。这样设计的爬虫因为直接使用现存的浏览器内核,所以支持的页面也非常多,甚至能够利用它来做翻译爬虫(即利用设计好的Uri或者特定页面的某个输入来达到目的)
爬取谷歌翻译示例(从中文到英文)
而这种操作,一般上来说通过以前的纯粹Http响应(C#常用HtmlAgilityPack类库)会因为版本过低等原因无法读取到目标内容。

C# Selenium的使用

C#的Selenium其实和其他语言体系是差不多的,对比python和C#的区别在于,C#是编译类语言,要想实现脚本化需要自行实现脚本化。如此对比来说,python的灵活性更好,但某些情况也许必须要C#来编写Selenium,这时难道是让C#执行一个python解释器来运行python下的内容吗?也许可行,但现在可以考虑纯粹地直接用C#代码来编写。

C#的Selenium的安装

在需要Selenium的项目上右键,点击【管理NuGet程序包】,
在浏览项搜索selenium,一般直接安装Selenium.Support即可(单纯用Selenium.WebDriver也可以):
Selenium的安装
同时再在项目的(.cs)上加上

using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Edge;//此可以按需添加,比如常用的还有Chrome(记得装上chromedriver)

一般使用上建议自行按照需求再度设计一个类。在先前的翻译示例中所使用的SeleniumEntry是这样的:

/// <summary>
/// 简单的使用Edge的selenium来访问指定网页,并且从网页中按xpath提取文本。by:csdn_xfese
/// </summary>
public class SeleniumEntry {
    public string address;//目标Uri
    public string get_text;//从目标页面的指定XPath中提取到的文本
    public string get_xpath;//从目标页面所要提取对象文本的XPath
    private readonly int millisecondsTimeout;//毫秒为单位的等待延迟
    /// <summary>
    /// SeleniumEntry的基本构造函数。
    /// </summary>
    /// <param name="millisecondsTimeout">以毫秒为单位的等待时长(默认5000即5秒)</param>
    public SeleniumEntry(int millisecondsTimeout = 5000) {
        this.millisecondsTimeout = millisecondsTimeout;
        address = string.Empty;
        get_text = null;
        get_xpath = string.Empty;
    }
    /// <summary>
    /// 打开Edge(并最小化)爬取目标页面的某个对象的文本。
    /// </summary>
    public void Spider() {
        var web = new EdgeDriver();                              //新建Edge(这一步将打开浏览器),像Edge这类的会打开浏览器的窗口(包括任务栏都会看见)
        web.Manage().Window.Minimize();                          //最小化Edge
        web.ExecuteScript($"window.location.href = '{address}'");//这一步用JavaScript代码来打开网页(这样是不会阻塞线程的)
        System.Threading.Thread.Sleep(millisecondsTimeout);      //因为不会阻塞线程,所以这里设定一个等待时间,超过后无论页面加载得全不全都会继续执行
        //web.Url = address;                                     //这样的方式将会阻塞线程等待页面完全加载完毕(!!)
                                                                 //如果超过设定的时间(默认60s)就会抛出异常
        //web.Navigate().GoToUrl(address);                       //同上
        try {
            get_text = web.FindElementByXPath(get_xpath).Text;   //这里就是简单的通过XPath读取文本(这个文本类似于选中复制,而不是html结构文本)
        } catch (Exception) { }                                  //有很多可能的异常会抛出所以视情况再决定如何catch
        web.Quit();                                              //关闭Edge进程,如果不执行这一句,浏览器的进程会一直打开着,需手动关闭.
    }
}

Selenium的应用基本同上,注意到所有的WebDriver都继承于RemoteWebDriver类,该类位于OpenQA.Selenium.Remote名称空间中。因此,可以用RemoteWebDriver作为函数参数的输入类型以代表所有的浏览器。

SeleniumEntry类的分析

基本的分析在该类的代码处已经完全写明了,需要注意几个点,启动有个重要问题放后面。就启动而言,由于Win10下就算没有Chrome等浏览器都可以直接用Edge,所以测试起来很方便。
实际上的ChromeDriver也只需要下载一个Chrome本身,确定好版本号,再去找 chromedriver(阿里站点),下载与所安装的Chrome版本完全一致的chromedriver,这也并不是非常困难的事情。
启动ChromeDriver可以这样,将chromedriver.exe放于C盘根目录都可以了。

var web = new ChromeDriver(@"C:\");//构造函数的参数是指chromedriver.exe所在目录,不是文件地址
1.访问页面线程阻塞问题

很多人都可能发现web.Uri = xxxweb.Navigate().GoToUri(xxx)会阻塞线程,这是由于有些网页它不能立刻就加载完毕,会等等很长时间,甚至达到60秒后突然抛出个异常。
这里的解决办法就是任何对页面的操作行为尽可能地用JavaScript代码方式操作,即用web.ExecuteScript()
包括新建标签页等操作都是js代码来做很有效。
对于web.Uri = xxxweb.Navigate().GoToUri(xxx)我也有一套方案可选:

public void UriAccess() {
    var web = new EdgeDriver();                                    //新建Edge(这一步将打开浏览器),像Edge这类的会打开浏览器的窗口(包括任务栏都会看见)
    web.Manage().Window.Minimize();                                //最小化Edge
    web.Manage().Timeouts().PageLoad = TimeSpan.FromMilliseconds(millisecondsTimeout);//设定页面加载的等待时长上限,如超过该时长将抛出异常.
    try {
        web.Url = address;                                         //阻塞线程以等待页面完全加载完毕,如果超过设定的时间(默认60s)就会抛出异常
        //web.Navigate().GoToUrl(address);
    } catch (WebDriverTimeoutException) { }                        //忽略超时所抛异常
    try {
        get_text = web.FindElementByXPath(get_xpath).Text;         //这里就是简单的通过XPath读取文本(这个文本类似于选中复制,而不是html结构文本)
    } catch (Exception) { }                                        //有很多可能的异常会抛出所以视情况再决定如何catch
    web.Quit();                                                    //关闭Edge进程,如果不执行这一句,浏览器的进程(包括WebDriver进程)会一直打开着,需手动关闭.
}

通过web.Manage().Timeouts().PageLoad = TimeSpan.FromMilliseconds(millisecondsTimeout)来修改页面加载时长上限,该值默认是60s。
而后通过web.Uri = addressweb.Navigate().GoToUri(address)访问指定页面,同时使用try-catch块来处理加载过长时所抛异常(一般都直接忽略所抛的OpenQA.Selenium.WebDriverTimeoutException)。

2.获取内容时所可能发生的问题

内容获取的时候一般都会有点问题,可以知道,由于页面未能完全加载所发生的问题就是一个重要原因。其次,从selenium中获取内容时这个页面加载的衡量是没有确立标准的,有交互的页面可能这个永远处于“加载”状态,所以,需要利用其它方式来判断什么时候进一步执行代码。
SeleniumEntry确立的标准就是时间,给定一个限定的时间,超过才继续进行。
也可以利用内容来确立,比如给定一个时间差(或者不需要),当特定内容能够获取时就继续运行,并给定一个循环检测的次数,如此不断循环等等。
不过这些确立标准也有难度,如果在网络问题等其他原因下,原网页无法正常访问,那么对于内容的检测就没那么容易了。首先
Selenium并没有提供获取页面响应状态码的API!!
如此只能视情况自由决定,可以选择循环检测,同时循环条件一般都是取一定的时间差,如果实在不行就尝试刷新,刷新三次都如此之后就不再考虑。比如:

public void Access() {
    var web = new EdgeDriver();
    web.Manage().Window.Minimize();
    web.ExecuteScript($"window.location.href = '{address}'");
    for (int refresh_count = 0, refresh_count_max = 3; refresh_count < refresh_count_max; refresh_count++) {
        for (int get_count = 0, 
        		 delta_time = 200, 
        		 get_count_max = millisecondsTimeout / delta_time + 1;
	        get_count < get_count_max;
	        get_count++
	    ){
            try {
                get_text = web.FindElementByXPath(get_xpath).Text;
                get_count = get_count_max;
            } catch (InvalidSelectorException) {
                System.Threading.Thread.Sleep(delta_time);
            } catch (WebDriverException) {
                System.Threading.Thread.Sleep(delta_time);
            }
        }
        if(get_text == null) {
            web.ExecuteScript("window.location.reload()");
        }
    }
    web.Quit();
}
3.迟言的启动问题

一般的启动其实关键就是新建一个RemoteWebDriver

var web = new EdgeDriver();

同时相当于

RemoteWebDriver web = new EdgeDriver();

如果观察其参数可以发现其参数其实在默认地使用EdgeDriverService,而默认的EdgeDriverServiceEdgeDriverService.CreateDefaultService()
如果同时运行两个如此的Selenium程序会如何?实际上,并不是运行两个浏览器,而是后启动的那个会抛出异常。所以提倡用完之后尽可能使用web.Quit()。而实际使用的时候,可以将它放于类的析构函数,如果不如此,比如你会发生异常,导致程序非正常关闭了,注意,如此你的各种WebDriver进程将一直塞在你的进程列表里,因此设计类时务必使用类的构造和析构函数来操作,SeleniumEntry是个反面例子。对于某些多页面任务建议的方式就是利用多标签页,而不是多浏览器。多个同一浏览器需要使用Selenium Grid,这里并不考虑这种方式。另外可以同时使用EdgeDriverChromeDriver来进行多线程操作,具体原因是EdgeDriver的会话等操作都是单线程的,多线程交互可能会导致各种未知的错误。但完全不同的两个核心(Driver)就没有这种问题。

Selenium的多标签页操作

多标签页目前我只有通过JavaScript代码来实现的方法,准确说,如果不通过JavaScript,实际上是个极其困难的行为,即使你打算让某个页面接受【Ctrl + T】的键盘指令也未必能保证其新建标签页。

①新建标签页

这里提供先新建标签页再操作的方式:

public void NewTab(RemoteWebDriver web) {
    string home_handle = web.CurrentWindowHandle;
    web.ExecuteScript("window.open('about:blank', '_blank')");
    web.SwitchTo().Window(web.WindowHandles.Last());
}

首先,第一句是为了保存第一标签页的句柄,这个句柄是长文本

一些句柄字符串值示例
CDwindow-10D1EC0A3B99BE14DB01B27C365C6225 (来自Chrome)
CDwindow-8AD6AF9F996E96D73C6436C21093CA8E(来自Chrome)
47EE65B4-4086-43E5-AA72-B9003A16C9BF(来自Edge)
ED264614-FB8C-4566-B84A-B8A80D585AD3(来自Edge)

所以不同浏览器可能不一样,但都是一个表示句柄的文本。

第二句正是在浏览器新建一个标签页(但这时web.CurrentWindowHandle并没有转换到新建的Tab的句柄上)
第三句的写法正式地将页面句柄转换到新标签页上,这里也许可以考虑把所有的新创建句柄用合理的数据结构来保存,比如List<>HashSet<>等,以加快后续操作。

这里体现了,所有的RemoteWebDriver都是只对CurrentWindowHandle所指的标签页下的内容进行操作,如果你尝试隔页操作,必然引发异常(OpenQA.Selenium.StaleElementReferenceException)。
因此每次要操作某个页面下的对象时(即使先前已经保存了这个对象),必须要通过web.SwitchTo().Window(handle)来转到该对象所在标签页的句柄上。不过,如果页面刷新了的话提取了的那个对象也可能发生WebDriverException等异常。

②删除标签页
public static void Delete(RemoteWebDriver web, string handle) {
    web.SwitchTo().Window(handle);
    web.ExecuteScript("window.opener=null;window.open('','_self');window.close()");
    web.SwitchTo().Window(web.WindowHandles.First());
}

可以看到几乎差不多的操作,第一句的原因由前述,必须要让CurrentWindowHandle指向要删除的handle,最后一句为了保证web的各种操作能顺利,重新指回一个可以操作的标签页上。

其实可以通过观察浏览器的实际操作效果发现,一旦关闭了某个标签页,好像是会自动地换到一个存在的标签页上,但是这并不会顺利地操作CurrentWindowHandle只要如此,一旦对web做一些诸如FindElement的操作都会抛异常(OpenQA.Selenium.StaleElementReferenceException)。因此,必须显式地使用web.SwitchTo().Window(handle)。实际上,新建标签页也是一样的。

完结

好了,这一份是根据我实际的对C#操作Selenium所发生的诸多问题得到解决后的总结。也许未来会提供关于基于登录机制和交互等操作的C# Selenium教程,也可能实现将 C# 本身作为脚本语言(类似Unity)来完成的脚本式.Net Selenium教程。

  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C# Selenium是一种用于自动化Web应用程序的工具。它使用Selenium WebDriver库来与浏览器进行交互,并可以进行各种Web自动化任务,如模拟用户操作、填写表单、点击按钮等。 在使用C# Selenium时,你需要引用一些核心库,包括Selenium.RC、Selenium.Support和Selenium.WebDriver。这些库提供了必要的功能和接口,使你能够编写C#代码来控制浏览器行为。 相比于Selenium RC,Selenium WebDriver的编程接口更加直观易懂,也更加简练。它提供了更多的内置方法和功能,使得编写自动化脚本更加方便。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C# Selenium使用诸多事项-I](https://blog.csdn.net/m0_37667916/article/details/104358336)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [C# 使用Selenium](https://blog.csdn.net/yangyong1250/article/details/128892399)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值