又到了毕业的季节,大学准毕业生又开始写论文了,前几天有位同学论文需要一个问卷调查但无奈通讯录好友达不到调查人数,也就帮下忙好了,开始用的是游猴的脚本但没有自动点击提交和批量处理的功能,选项也不能自定义点击,所以干脆写个爬虫好了。
脚本的第一步,相信大家都不陌生,首先分析网页元素,发现题目选项的 div 标签 id 都为divquestion + 数字,这样的话处理起来就简单了,直接通过 id 定位到选项的 div 标签,然后往下定位具体选项的 li 标签再点击就实现了单选框的选择,那么我们就可以直接写个函数。
def gddx(driver, x:int, y:int):
'''固定单选题,x为题号,y为想要选择的选项'''
question = driver.find_elements_by_css_selector('#divquestion' + x + ' ul li')
question[y-1].click()
以上是确定的单选题的代码,
下面再来个随机选择的单选题:
def sjdx(driver, x:int):
'''随机单选题,x为题号'''
question = driver.find_elements_by_css_selector('#divquestion' + x + ' ul li')
question[random.randint(0, len(question) - 1)].click()
可以看到代码都非常简单。
有了单选题的代码,那么其他类型的题目只要照葫芦画瓢就可以了,废话不多说,直接贴代码
'''下拉框确定选择'''
def qdxl(driver, x:int, y:int):
question = driver.find_elements_by_css_selector('#divquestion' + x + ' select option')
question[random.randint(0, len(question) - 1)].click()
question[y].click()
'''下拉框随机选择'''
def sjxl(driver, x:int):
question = driver.find_elements_by_css_selector('#divquestion' + x + ' select option')
question[random.randint(0, len(question) - 1)].click()
'''多选题随机选择'''
def sjdxx(driver, x:int):
question = driver.find_elements_by_css_selector('#divquestion' + x + ' ul li')
length = len(question)
choice = [x for x in range(1, length)]
choice = random.sample(choice, 3)
for i in choice:
question[i].click()
哦吼,这就已经差不多完成,接下来让我们测试一下代码,用到的是Code_st提供的测试问卷调查
from selenium import webdriver
import random
import time
import json
import requests
import base64
chrome_driver = 'D:\chromedriver_win32\chromedriver.exe' # chromedriver的文件位置
drive = webdriver.Chrome(executable_path=chrome_driver)
drive.maximize_window() # 将浏览器最大化
'''固定单选题,x为题号,y为想要选择的选项'''
def gddx(driver, x:int, y:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' ul li')
question[y-1].click()
'''随机单选题,x为题号'''
def sjdx(driver, x:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' ul li')
question[random.randint(0, len(question) - 1)].click()
'''下拉框确定选择'''
def qdxl(driver, x:int, y:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' select option')
question[random.randint(0, len(question) - 1)].click()
question[y].click()
'''下拉框随机选择'''
def sjxl(driver, x:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' select option')
question[random.randint(0, len(question) - 1)].click()
'''多选题随机选择'''
def sjdxx(driver, x:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' ul li')
length = len(question)
choice = [x for x in range(1, length)]
choice = random.sample(choice, 3)
for i in choice:
question[i].click()
def test():
drive.get('https://www.wjx.cn/jq/27168497.aspx')
for i in range(1,6):
sjdx(drive, i)
for i in range(6, 9):
sjdxx(drive, i)
for i in range(9,13):
gddx(drive, i, 2)
'''点击按钮提交调查'''
bt = drive.find_element_by_id('submit_button')
bt.click()
time.sleep(999)
if __name__ == '__main__':
test()
至此一次完整的问卷调查就填写完成了,接下来我们将test函数修改一下, 将 time.sleep(999) 修改一下,然后再添加一个for循环便实现了提交多份问卷,具体修改代码如下
def test():
drive.get('https://www.wjx.cn/jq/27168497.aspx')
for i in range(1,6):
sjdx(drive, i)
for i in range(6, 9):
sjdxx(drive, i)
for i in range(9,13):
gddx(drive, i, 2)
'''点击按钮提交调查'''
bt = drive.find_element_by_id('submit_button')
bt.click()
time.sleep(1)
if __name__ == '__main__':
for i in range(0, 20):
test()
drive.quit()
到这里似乎已经可以生成多份问卷调查了,但是在多份调查提交中发现,网页会提示验证码。这时候,我们就需要用到一些其他的东西了,比如易源网,比如图鉴等提供验证码识别的东西(其实自己也能写,但是对于图像的处理还是有点难度…之后的学习中再使用吧,现在先用用现成的好了),那么接下来我们还是回到开始,分析网页…
首先,我们需要得到验证码的位置,因此还是老样子,定位标签。而多次测试发现,提交按钮及验证码所在 div 标签中的 style 属性在没有验证码时为 none ,在有验证码时为空值,那么我们就可以通关判断该 __div__标签来判断是否含有验证码,然后再通过浏览器截屏得到包含验证码的图片,代码如下:
# 判断页面是否出现验证码。
cs = drive.find_element_by_id('ctl00_ContentPlaceHolder1_JQ1_tdCode')
str = cs.get_attribute('style')
if 'none' not in str:
# 点击验证码输入框,显示验证码。
input = drive.find_elements_by_css_selector('#yucinput')[0]
input.click()
time.sleep(1)
drive.get_screenshot_as_file('a.png')
接下来就是通过 crop(b1, a1, b2 ,a2) 函数裁剪出验证码所在区域了,搜索验证码的获取发现大家用的方法都是获取验证码在网页中的位置得到在浏览器中的位置的,但是在这里并不是适用,因为这时候验证码所在位置位于网页底部,验证码的坐标是根据整个网页大小来定位的,我们截取的图片仅仅只是底部的一部分而已。这时候我们就需要从原函数进行分析,crop(b1, a1, b2 ,a2) 到底是怎么截取的?网上很多人说的是上下左右四个方位的坐标,然而并不是…一张图解释坐标
通过图像我们就可以知道函数是如何截取的了。下一步就是获取这四个位置的值了,一开始我使用的是蠢方法,直接把4个坐标设置到截屏的最大值然后通过修改单一坐标慢慢试,后面我发现windows自带的 图画 软件是带有像素坐标的!!!实在是太棒了,截取验证码网页后用 图画 打开图片,鼠标移动到这四个坐标便可以了
截取图片
非常完整的验证码就这样截取了下来,接下来就是拿去验证码平台分析然后填入验证码文本框就可以了,在此用到的是完整代码如下:
from selenium import webdriver
import random
import time
import json
import requests
import base64
from selenium.webdriver.support import expected_conditions as EC
from PIL import Image
from io import BytesIO
from sys import version_info
chrome_driver = 'D:\chromedriver_win32\chromedriver.exe' # chromedriver的文件位置
drive = webdriver.Chrome(executable_path=chrome_driver)
drive.maximize_window() # 将浏览器最大化
'''固定单选题,x为题号,y为想要选择的选项'''
def gddx(driver, x:int, y:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' ul li')
question[y-1].click()
'''随机单选题,x为题号'''
def sjdx(driver, x:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' ul li')
question[random.randint(0, len(question) - 1)].click()
'''下拉框确定选择'''
def qdxl(driver, x:int, y:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' select option')
question[random.randint(0, len(question) - 1)].click()
question[y].click()
'''下拉框随机选择'''
def sjxl(driver, x:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' select option')
question[random.randint(0, len(question) - 1)].click()
'''多选题随机选择'''
def sjdxx(driver, x:int):
question = driver.find_elements_by_css_selector('#divquestion' + str(x) + ' ul li')
length = len(question)
choice = [x for x in range(1, length)]
choice = random.sample(choice, 3)
for i in choice:
question[i].click()
def base64_api(uname, pwd, img):
img = img.convert('RGB')
buffered = BytesIO()
img.save(buffered, format="JPEG")
if version_info.major >= 3:
b64 = str(base64.b64encode(buffered.getvalue()), encoding='utf-8')
else:
b64 = str(base64.b64encode(buffered.getvalue()))
data = {"username": uname, "password": pwd, "image": b64}
result = json.loads(requests.post("http://api.ttshitu.com/base64", json=data).text)
if result['success']:
return result["data"]["result"]
else:
return result["message"]
return ""
def test():
drive.get('https://www.wjx.cn/jq/27168497.aspx')
for i in range(1,6):
sjdx(drive, i)
for i in range(6, 9):
sjdxx(drive, i)
for i in range(9,13):
gddx(drive, i, 2)
# 判断页面是否出现验证码。
cs = drive.find_element_by_id('ctl00_ContentPlaceHolder1_JQ1_tdCode')
str = cs.get_attribute('style')
if 'none' not in str:
# 点击验证码输入框,显示验证码。
input = drive.find_elements_by_css_selector('#yucinput')[0]
input.click()
time.sleep(1)
drive.get_screenshot_as_file('a.png')
a = Image.open("a.png")
im = a.crop((900, 890, 1010, 925))
im.save('b.png')
img = Image.open('b.png')
result = base64_api(uname='账号', pwd='密码', img=img)
input.send_keys(result)
bt = drive.find_element_by_id('submit_button')
bt.click()
if __name__ == '__main__':
for i in range(0, 20):
test()
drive.quit()
以上就完成了这次爬虫的基本要求了,当然还是有点小bug并有做处理,比如当本地网络不稳定时可能需要点击按钮之后才会提示需要输入验证码,比如验证码识别平台可能识别错误的问题。但是作为刚入门的小白目前就到这里了。还请各位大牛多多指教。
最后要感谢几位文章的博主:
python PIL库的crop函数–图片裁剪操作 作者 banxia1995
Python~selenium填写问卷星作者 包子加入侵
Python爬虫拓展应用——Selenium代刷问卷星问卷作者 Code_st
本文所使用的验证码识别平台为 图鉴