工作中的需求是多种多样,是否有遇到过这样的需求:一个H5表单,用户通过多渠道入单,期望帮用户的下单请求提交至这个H5上。
这种通常就是在二级服务商、代理服务商的服务商会遇到这样的需求,源头供应商没有接口的能力,提供到一个页面,支持提交表单数据。
这种场景,我们可以怎样提交呢?
- 纯人工肯定没毛病,收集到用户信息后,替用户向上游提交下单请求。可是量大就不现实了。
- 最终肯定还是期望自动化,既保障用户隐私又提升效率,这也是智能化的趋势。
那我们可以想到的就是一些辅助自动化的工具,模拟人的动作,又不需要人来干预,其中有Selenium、Appnium。这些原理都类似,利用自动化测试的思想,来模拟进行表单提交。
以上案例是基于H5网页的,那么使用Selenium是再合适不过了。
请注意,以上Selenium是在多方认同恰当的业务流程中,进行模拟填单提交的自动化操作,不可用于不合规的牟利,目前非加白合规的流程也都会有反爬虫的措施来抵御使用Selenium自动化的一些操作。
一、从0入门Selenium
python有丰富的类库,与Selenium结合紧密,由于本人相对熟悉JAVA,以下样例主要通过java进行实现。
Selenium官方GITHUB:https://github.com/SeleniumHQ/seleniumhq.github.io
以下先介绍一些基本操作:
1.1 打开URL
ChromeOptions、ChromeDriver的详细参数可参看官网:
ChromeOptions option = new ChromeOptions();
WebDriver driver = new ChromeDriver();
driver.get("https://www.selenium.dev/selenium/web/web-form.html");
1.2 获取页面标题
String title = driver.getTitle();
1.3 配置超时等待策略
有时候页面加载需要的时间不确定,为了确保接下来获取页面元素的步骤没有问题,通常会配置一个等待时间,让页面元素有足够的时间渲染,避免脚本的异常
driver.manage().timeouts().implicitlyWait(Duration.ofMillis(500));
1.4 查找页面元素
在使用API的时候会发现,有几种可以获取页面元素的方法,具体参照实际情况
源码中By的方法有:
public static By id(String id) {
return new By.ById(id);
}
public static By linkText(String linkText) {
return new By.ByLinkText(linkText);
}
public static By partialLinkText(String partialLinkText) {
return new By.ByPartialLinkText(partialLinkText);
}
public static By name(String name) {
return new By.ByName(name);
}
public static By tagName(String tagName) {
return new By.ByTagName(tagName);
}
public static By xpath(String xpathExpression) {
return new By.ByXPath(xpathExpression);
}
public static By className(String className) {
return new By.ByClassName(className);
}
public static By cssSelector(String cssSelector) {
return new By.ByCssSelector(cssSelector);
}
1.4.1 根据ClassName获取元素
一个页面,在特殊或者关键的DIV上,规范非低代码的平台上都会有特殊class标识,可能要定义操作或者样式
<div class="code-toolbar">
</div>
可以通过以下方法获取上面这个div
WebElement vegetable = driver.findElement(By.className("code-toolbar"));
1.4.2 根据ID获取获取页面元素
和ClassName类似,当一个页面元素没有一个单独定位的class属性,那么可以看一下是否有ID属性
<ul class="nav nav-tabs" id="tabs-1" role="tablist"></ul>
以上这个class经观察,有但不是唯一标识,我想唯一获取这个元素,那么看到ID是一个唯一的属性,那么就可以通过ID获取
WebElement fruits = driver.findElement(By.id("tabs-1"));
1.4.3 根据name获取页面元素
name和ID、ClassName,如果具有特殊性可以唯一标识,那也很好用的
WebElement fruits = driver.findElement(By.name("tabsname"));
1.4.4 根据TagName获取页面元素
有一种需要获取下拉框内的全部元素内容,或者页面的一个input框元素,可以使用TagName
List<WebElement> elements = driver.findElements(By.tagName("li"));
for (WebElement element : elements) {
System.out.println("text:" + element.getText());
}
1.4.5 根据CSS标识获取元素
有时候页面元素存在嵌套,不希望通过ID或者ClassName层层获取,为了更加高效,可是使用CSS选择器或XPath选择器,来定位页面元素 css选择器中针对ID和ClassName存在简写
- #表示id属性定位器,后面紧跟id属性值
- 英文的. 表示class定位器,后面紧跟class属性值
- 子代选择器用>表示,这个是直接隶属的关系
- 毗邻元素选择器用+表示
这边CSS的组合写法和XPath的写法相对复杂一些,后面单独列一期
1.4.6 获取某元素的子元素
WebElement element = driver.findElement(By.tagName("div"));
// 获取这个div下面的全部的p标签内容
List<WebElement> elements = element.findElements(By.tagName("p"));
for (WebElement e : elements) {
System.out.println(e.getText());
}
1.5 操作页面元素
1.5.1 输入框设置内容
比如我们定位了百度输入框,需要在输入框中写入内容。表单且是text类型输入框的可以填入,如果是不可编辑的则无法定位修改该元素。
driver.findElement(By.name("q")).sendKeys("q" + Keys.ENTER);
写了一个q并且键入一个ENTER
1.5.2 输入清空
一个可清空、可编辑的表单文本输入框,可以选中后进行清空。
WebElement searchInput = driver.findElement(By.name("q"));
searchInput.sendKeys("selenium");
searchInput.clear();
1.5.3 表单提交
Selenium4开始将不再实现
1.5.4 元素点击
通常提交表单都有标识性的按钮实现,所以也可以定位点击提交按钮来实现表单的提交
WebElement agreeCheck = driver.findElement(By.id("agree"));
agreeCheck.click();
1.5.5 下拉选择
需要是Select选择框可以进行以下操作,有些下拉框是原生的ul\li的,不可使用
<select name="selectomatic">
<option selected="selected" id="non_multi_option" value="one">One</option>
<option value="two">Two</option>
<option value="four">Four</option>
<option value="still learning how to count, apparently">Still learning how to count, apparently</option>
</select>
WebElement selectElement = driver.findElement(By.name("selectomatic"));
Select select = new Select(selectElement);
List<WebElement> optionList = select.getOptions();//单选
List<WebElement> selectedOptionList = select.getAllSelectedOptions();//多选
这种情况,我们通常想操控这个下拉框选择某一个元素,可以通过以下方式选择,如果某一个元素被禁用了,就不可被选中。
//根据文本选择
select.selectByVisibleText("Four");
//根据值选择
select.selectByValue("two");
//根据排序选择
select.selectByIndex(3);
1.5.6 屏幕滚动
有时候点开的页面需要通过滚动的方式,让页面元素能够展示并且获取。
//定位一个页面元素,让它滚动到视线内
WebElement element = driver.findElement(By.className("btnPay"));
driver.executeScript("arguments[0].scrollIntoView();", element);
//指定滚动一定像素
String js="var q=document.documentElement.scrollTop=10000";
driver.executeScript(js);
此外还有很多的操作场景,比如自动化文件上传下载、模拟鼠标操作、模拟键盘输入、滚轮滚动、页面截图等等
具体可以参看官方文档: https://www.selenium.dev/documentation/webdriver/getting_started/
官方也是极力推荐大家往Selenium4上面迁移,会带来一些改变,但会有更多的操作特性
二、使用案例
以下通过一个实例,来介绍使用Selenium的一些细节。
2.1 明确本地版本
需要保证浏览器(具体是Chrome/Chromium, Firefox, Internet Explorer, Edge, and Safari. 它是都能支持的,具体看自己情况)
比如我本地是Chrome,那我首先确认我的chrome版本
打开浏览器,输入:chrome://version,就能看到了
Google Chrome 110.0.5481.177 (正式版本) (x86_64)
修订版本 f34f7ab2d4ca4ad498ef42aeba4f4eb2c1392d63-refs/branch-heads/5481@{#1239}
2.2 下载指定驱动
如果本地能够找到chrome驱动的可以直接使用,此外还可以根据目前自己的版本去官方下载:https://chromedriver.chromium.org/downloads
比如我已经明确自己是110版本,那么根据指引:If you are using Chrome version 110, please download ChromeDriver 110.0.5481.77
2.3 加载驱动的几种方法
2.3.1 Selenium v4.6 使用Selenium Manager
2.3.2 Driver Management Software
WebDriverManager.chromedriver().setup();
WebDriver driver = new ChromeDriver();
2.3.3 通过本地环境配置chromedriver
echo 'export PATH=$PATH:/path/to/driver' >> ~/.bash_profile
source ~/.bash_profile
2.3.4 通过写死驱动的路径
System.setProperty("webdriver.chrome.driver","/path/to/chromedriver");
ChromeDriver driver = new ChromeDriver();
2.4 Java 引入依赖
具体取决于各个环节的版本
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.8.0</version>
</dependency>
2.5 撰写代码
public static void main(String[] args) {
System.setProperty("webdriver.chrome.driver",
"/Users/chenzy/Downloads/chromedriver");
ChromeOptions option = new ChromeOptions();
option.addArguments("--start-maximized");
ChromeDriver driver = new ChromeDriver(option);
driver.manage().timeouts().pageLoadTimeout(3, TimeUnit.SECONDS);
driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
try {
driver.get("https://www.baidu.com/");
WebElement payCode = driver.findElement(By.id("lg"));
int x = payCode.getLocation().x;
int y = payCode.getLocation().y;
// Get entire page screenshot
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
BufferedImage fullImg = ImageIO.read(screenshot);
// Get the location of element on the page
Point point = payCode.getLocation();
// Get width and height of the element
int eleWidth = payCode.getSize().getWidth();
int eleHeight = payCode.getSize().getHeight();
// Crop the entire page screenshot to get only element screenshot
BufferedImage eleScreenshot = fullImg.getSubimage(point.getX(), point.getY(),
eleWidth, eleHeight);
ImageIO.write(eleScreenshot, "png", screenshot);
// Copy the element screenshot to disk
File screenshotLocation = new File("~/Downloads/GoogleLogo_screenshot.png");
FileUtils.copyFile(screenshot, screenshotLocation);
} catch (Exception e) {
e.printStackTrace();
}
}