“为了填写 wj 星,我又拿起了 Python。 讲真,Python可做的 Java 也可以,不过主要在于 Java 的依赖生态不及 Python 完善,所以可能会更耗时一些,但是不代表 Java 比 Python 弱噢 ”
学校发了个表单,让填写一下
出于好奇和学习的心态,我想试下能不能自动填写表单;首先我想的是 JavaScript,因为毕竟是网页,所以首选的还是 JS,之后我创建了个 TEST 的表单进行测试,比较每次请求的路径与参数。
首先 WJ星 的页面里打开控制台后会进入 debug 模式,我们需要 deactivate 掉断点才可以继续操作。Firefox 操作后会造成页面卡死,伤心,我这么爱它它却让我去找 Chrome...
image-20210309221127176
起初因为手速比较慢,只能拿到投票完之后的url:https://www.wjx.cn/wjx/join/complete.aspx?activityid=109478233&joinid=108761857258&sojumpindex=1&comsign=135A7EC8CFEF9AFBE716B45BC4422FF56E5CB4F0&s=&njqj=1
;经过多次比对,发现其中的 joinid
comsign
两个字段会发生变化,我以为这个对上后会发现生成所依赖的接口或者文件,这样之后通过生成这两个字段,循环就能反复刷票了。
后来发现自己 too simple 了,这条接口根本改不了投票的结果,也就是不作数!
之后提高手速,拿到了发送请求的接口地址:https://www.wjx.cn/joinnew/processjq.ashx? 一堆参数
,如下所示
image-20210309220804399
其中的核心参数是 jqparam
以及 jqsign
。具体怎么生成的呢?参考一篇52上的分析,走了一遍。
参考文章:对问卷星参数jqparam的分析和探索 https://www.52pojie.cn/forum.php?mod=viewthread&tid=1361387&highlight=%CE%CA%BE%ED%D0%C7 我这里就不再赘述了。
简言之就是使用插件重定向了 WJ星 远程访问的接口,然后本地去生成 jqparam,我试了试效果不大行。
折腾完前边的之后发现走不过去,那就模拟点击吧~
于是我想到了 Selenium
,大家现在也都在上人工智能的课,不管听没听,至少电脑上都该有个Python的环境吧~
pip install selenium # 下载selenium到本地
之后我们就有了基本,但是想要实现模拟点击,我们还需要下载浏览器驱动,Firefox 抛弃了我,所以只好下载 Chrome了。
驱动下载地址附上:http://chromedriver.storage.googleapis.com/index.html ,选择和自己浏览器大版本匹配的下载就可,下载解压后会得到一个 chromedriver.exe
复制到自己的Python安装目录下,这样默认就可以找到,不用再传参数了。
上边就绪后,我们就来对页面进行分析,有点类似于 Beautiful Soup
了
image-20210309222452929
然后分析页面,我们发现,问卷主体内容都在 id="ctl00_ContentPlaceHolder1_JQ1_question"
的 div 盒子下,于是我们确定了根节点。之后我们再看第一个问题的位置
image-20210309222729303
是在根目录下的 第二个div
下的 第一个fieldset
下的 第一个div
;然后我们定位一个问题的选项,是在再往下一级的 ul
的 li
中。于是,我们有了定位的路径。
selection1 = '//*[@id="ctl00_ContentPlaceHolder1_JQ1_question"]/div[2]/fieldset[1]/div[1]/div[2]/ul/li'
之后我们通过 webdriver.Chrome
来定位到 li
,我们发现实际上返回的是个列表
[<selenium.webdriver.remote.webelement.WebElement (session="93b4af8577a5b18396ea581abaa417ca", element="3ddfb20d-a1ef-4811-b49d-d50f70ab992f")>, <selenium.webdriver.remote.webelement.WebElement (session="93b4af8577a5b18396ea581abaa417ca", element="7db65e3e-2779-4180-be83-669ac2db1192")>]
我们取出最后一个,然后执行 click()
操作,即可选中。
answer_1 = driver.find_elements_by_xpath(selection1)[-1]
answer_1.click()
其他选项同理,复选框也如此。最后我们选择提交即可。
以为到这里就结束了?我当时也这么觉得,但是我们在提交后会出现进行人机核验的弹窗,需要我们额外处理下,同时还需要处理下拖动滑块验证。
image-20210309223929604
在实践检验真理的时候,发现了问题所在 在进行智能验证时,WJ星会识别 Selenium!,看来人家早就料到了啊。
至于说怎么识别的呢?使用 selenium 的话,浏览器控制台会多出一个属性 window.navigator.webdriver
会变为 true
,而当我们正常访问时它是 undefined
的。于是我们需要隐藏掉这个属性,新版本的Chrome (79以后的) 需要通过 js 来进行隐藏,具体代码放在下方
driver = Chrome('./chromedriver')
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
经过之后,这个表单终于可以提交了,先设置个次数?
for index in range(1, 200):
完整代码如下:
import time
import random
from selenium import webdriver
from selenium.webdriver import ChromeOptions
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
def autoFinishForm():
# 将问卷星网站放在下面
driver.get('群里的投票地址)
# 单选题 1
selection1 = '//*[@id="ctl00_ContentPlaceHolder1_JQ1_question"]/div[2]/fieldset[1]/div[1]/div[2]/ul/li'
# print("log driver.find_elements_by_xpath(selection1): ", driver.find_elements_by_xpath(selection1));
answer_1 = driver.find_elements_by_xpath(selection1)[-1]
answer_1.click()
# 单选题 2
selection2 = '//*[@id="ctl00_ContentPlaceHolder1_JQ1_question"]/div[2]/fieldset[1]/div[2]/div[2]/ul/li'
answer_2 = driver.find_elements_by_xpath(selection2)[-1]
answer_2.click()
# 单选题 3
selection3 = '//*[@id="ctl00_ContentPlaceHolder1_JQ1_question"]/div[2]/fieldset[1]/div[3]/div[2]/ul/li'
answer_3 = driver.find_elements_by_xpath(selection3)[-1]
answer_3.click()
# 提交按钮
submit = driver.find_elements_by_id('submit_button')[0]
submit.click()
# 提交后的验证弹窗
alterPath = '//*[@id="alert_box"]/div[2]/div[2]/div[2]/button'
checkButton = driver.find_elements_by_xpath(alterPath)[0]
checkButton.click()
# 智能验证按钮
captchaout = '//*[@id="captchaout"]/div[1]'
captchaoutBtn = driver.find_elements_by_xpath(captchaout)[0]
captchaoutBtn.click()
time.sleep(3)
# 拖动滑块的反向操作:关掉再点SM_POP_CLOSE_1
# closeslipper = '//*[@id="submit_div"]/div[1]/div[1]/div[1]/div[2]/div[1]/div[1]/div[1]/span'
# closeslipperBtn = driver.find_elements_by_xpath(closeslipper)[0]
closeslipperBtn = driver.find_elements_by_xpath("//div[@id='SM_POP_CLOSE_1' and @class='sm-pop-close']")[0]
closeslipperBtn.click()
captchaoutBtn.click()
time.sleep(3)
driver.quit()
if __name__ == '__main__':
# 循环200次
for index in range(1, 200):
driver = webdriver.Chrome(options=option)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
autoFinishForm()