通过AHK调用selenium获取浏览器页面上的题目、选项等元素的文本,并将文本存入excel建立本地题库。脚本包含自动匹配excel题库答题和截取文本建立题库两个部分。
适用:定期重复答题、有固定的题库、答题完成后可查看答案(或手动在excel题库中填入答案文本)、网页各类元素有规整的class、Xpath、id等属性(用于selenium元素定位)
由于答题系统千差万别,本文章仅分享思路和代码片段。
萌新小白陋作,大佬见笑。
基本思路
建立selenium com
Driver := ComObjCreate("Selenium.edgeDriver") ;
答题阶段
Sleep 1000
questions := driver.findElements(By.Class("question-titles"))
questions_count := questions.count
answers := driver.findElements(By.Class("question-select-area"))
answers_click_array :=driver.findElements(By.Class("question-select-item-label"))
answers_click_count := 0
loop %questions_count% { ;题目抓取并处理
Sleep 1000 ;太快了不好
questions_text := questions.item[A_Index].Attribute("innerText")
answers_text := answers.item[A_Index].Attribute("innerText")
StringReplace , questions_text, questions_text, 【判断题】, ,1 ;文本处理
StringReplace , questions_text, questions_text, 【单选题】, ,1
StringReplace , questions_text, questions_text, 【多选题】, ,1
StringReplace , questions_text, questions_text, %A_Index%、 , ,
文本用题目和脚本的class进行定位 questions := driver.findElements(By.Class(“*****”))将所有题目元素定位,选项同理。questions.count 计算题目数量后执行文本处理循环,用StringReplace 将题目文本中的多余部分去除(按需),
if questions_judge{ ;如果匹配成功
ROWS_now := questions_judge + 1 ;最后一行的下一行开始
Columns_now := 1 ;把选项写入excel
loop Parse,answers_text, %A_Tab%`n , %A_Space%%A_Tab% ;文本处理
{
if A_LoopField{
worksheet.cells(ROWS_now ,Columns_now).Value := A_LoopField ;写入选项文本
Columns_now ++
}
}
Columns_now := (Columns_now-1)/2 ;计算选项数量
answers_array_begin := worksheet.cells(questions_judge, 2)
answers_array_finish := worksheet.cells(questions_judge, 20)
answers_array := worksheet.range(answers_array_begin,answers_array_finish)
answers_real_count := objexcel.WorksheetFunction.counta(answers_array) ;计算正确答案数量
answers_rows := questions_judge + 1
loop %answers_real_count%{ ;按照正确答案匹配选项并点击
n := A_Index + 1 ;正确答案在题目左边一格开始
answers_real := worksheet.cells(questions_judge, n).Value ;取正确答案的文本
answers_judge := worksheet.range(worksheet.cells(answers_rows, 1),worksheet.cells(answers_rows, 20)).find(answers_real).Column ;正确答案和选项文本匹配,返回列数
answers_click := (answers_judge/2) ;列数/2 得1234 对应ABCD
answers_num := answers_click_count + answers_click ;相对定位,每道题A选项的answer序号加上一步匹配到的1234 对应本题的ABCD答案
answers_click_array.item[answers_num].Click()
Sleep 500 ;太快了不好
}
}
如果匹配成功,将已有的正确答案文本和选项注意匹配并点击选项,此处采用相对定位方法,因页面同时加载5道题,一次性用driver.findElements(By.Class(“question-select-item-label”))定位到的选项元素有10-30个,已知每道题分别有几个选项,所以可以将它们排序,得出每道题的第一个选项所在序号如:(如果有更方便的定位办法那更好)
第一题 | 第二题 | 第三题 | 第四题 | 第五题 | ||||||||||||
A | B | C | D | A | B | A | B | C | D | A | B | C | A | B | C | D |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
- 如果匹配失败,直接选A
else { ;如果没匹配上,计算选项数量,默认选A
Columns_now := 1
loop Parse,answers_text, %A_Tab%`n , %A_Space%%A_Tab% ;文本处理
{
if A_LoopField{
Columns_now ++
}
}
Columns_now := (Columns_now-1)/2 ;计算选项数量
answers_click_begin := answers_click_count +1
answers_click_array.item[answers_click_begin].Click
}
answers_click_count += Columns_now ;每道题起始的选项编号
Sleep 500 ;太快了不好
Driver.findElementBylinktext("下一题").Click()
Sleep 500
}
建立EXCEL本地题库
每道题占两行,第一行第一格存题目,第二格开始存答案,第二行依次存入选项ABCD和选项文本,这么建立题库的目的主要是为了方便人工查阅题库(如果需要)
;EXCEL启动套件
objexcel:
objexcel := ComObjCreate("Excel.Application")
file_excel = Q&A.xlsx
file_excel2 = %A_ScriptDir%\%file_excel%
worksheet := objexcel.workbooks.open(file_excel2).sheets("sheet1")
if %excel_yon%
objexcel.visible := true ;是否显示EXCEL窗口
return
创建excel com 对象,读取Q&A.xlsx 题库文件
questions := driver.findElements(By.Class("question-titles"))
questions_count := questions.count
answers := driver.findElements(By.Class("question-select-area"))
answers_click_array :=driver.findElements(By.Class("question-select-item-label"))
answers_click_count := 0
loop %questions_count% { ;题目抓取并处理
Sleep 1000 ;太快了不好
questions_text := questions.item[A_Index].Attribute("innerText")
answers_text := answers.item[A_Index].Attribute("innerText")
StringReplace , questions_text, questions_text, 【判断题】, ,1 ;文本处理
StringReplace , questions_text, questions_text, 【单选题】, ,1
StringReplace , questions_text, questions_text, 【多选题】, ,1
StringReplace , questions_text, questions_text, %A_Index%、 , ,
文本用题目和脚本的class进行定位 questions := driver.findElements(By.Class(“*****”))将所有题目元素定位,选项同理。questions.count 计算题目数量后执行文本处理循环,用StringReplace 将题目文本中的多余部分去除(按需),
questions_judge := worksheet.range("a1:a65536").find(questions_text).row ;与题库匹配
if !questions_judge{ ;如果匹配失败
worksheet.cells(rowNum,1).Value := questions_text
ROWS_now := rowNum + 1
Columns_now := 1
loop Parse,Rightanswers_text, %A_Tab%`n , %A_Space%%A_Tab% ;写入正确答案,文本处理
{
if A_Index = 1 ;取出正确答案的结合文本,只有`n前的第一段是有用内容
{
Rightanswers_textmix := A_LoopField
}
}
Rightanswers_text_find_pis := rowNum ;正确答案的行,和题目同一行
loop Parse,answers_text, %A_Tab%`n , %A_Space%%A_Tab% ;写入选项答案
{
if A_LoopField{
worksheet.cells(ROWS_now,Columns_now).Value := A_LoopField
Columns_now ++
}
}
rowNum += 2
; Columns_now := (Columns_now-1)/2 ;计算选项数量
n := 2
loop Parse,Rightanswers_textmix, ";" ;分离正确答案
{
Rightanswers_text_find := worksheet.range(worksheet.cells(ROWS_now, 1),worksheet.cells(ROWS_now, Columns_now)).find(A_LoopField).Column ;从选项文本中匹配正确答案文本,返回列数
Rightanswers_text_find_X := Rightanswers_text_find + 1
worksheet.cells(Rightanswers_text_find_pis,n).Value := worksheet.cells(ROWS_now,Rightanswers_text_find_X).Value
n ++
}
}
}
return
worksheet.range(“a1:a65536”).find(questions_text).row 在题库题目所在列寻找刚才处理好的题目文本,如果匹配失败,将题目文本加入到题库中,并将正确答案同时写入;如果匹配成功,则说明题库中已存在该题,结束此段。
总结
脚本中用到的函数和方法:
selenium部分
创建com
Driver := ComObjCreate("Selenium.edgeDriver") ;
两种定位写法:
1. Driver.findElementByClass("proj-exams")
2. by := ComObjCreate("Selenium.By")
examresult := driver.findElement(By.Class("warn-getIntegral"))
定位元素的方式
element := driver.findElementByName("用name屬性尋找")
element := driver.findElementByID("用id屬性尋找")
element := driver.findElementByTag("用HTML標籤尋找")
element := driver.findElementByClass("用class屬性尋找")
element := driver.findElementByCss("用CSS選擇器尋找")
element := driver.findElementByLinkText("用超連結的文字尋找")
element := driver.findElementByPartialLinkText("用超連結的部份文字尋找")
element := driver.findElementByXPath("用XPATH尋找")
click点击元素;attribute获取元素的某个属性
Driver.findElementByClass("proj-result-exams").Click()
examresult := driver.findElement(By.Class("warn-getIntegral")).Attribute("innerText")
获取某一class元素的集合 .count计算数量 .item按顺序取出元素
driver.findElements(By.Class("question-titles"))
questions.item[A_Index].Attribute("innerText")
设定隐式等待,在期间尝试寻找元素,超时后再报错
Driver.Timeouts.ImplicitWait := 10000
打开网页
Driver.Get(sURL)
EXCEL部分
创建com
objexcel := ComObjCreate("Excel.Application")
打开对应的文件
file_excel = Q&A.xlsx
file_excel2 = %A_ScriptDir%\%file_excel%
worksheet := objexcel.workbooks.open(file_excel2).sheets("sheet1")
执行过程中是否显示窗口
objexcel.visible := true ;是否显示EXCEL窗口
保存和退出
objexcel.activeworkbook.save
objexcel.quit
在某一范围内寻找给定文本并返回row(行)数 同理 .column是返回列数
worksheet.range("a1:a65536").find(文本).row
获取某一单元格值
worksheet.cells(ROWS_now,Columns_now).Value
定位有内容的最后一行
worksheet.range("A65536").end(3).row ;定位excel最后一行