本帖最后由 Hakutaku 于 2020-7-30 19:27 编辑
题记
之前因为太懒、嫌查成绩麻烦写了个简单的Python爬虫运行一键查询考试成绩,接下来我将记录、分析一下当时爬取教务网络管理系统所遇到的问题,然后展示我所编写的代码模板。
思路分析
总体上来说,不管是哪所高校,只要使用了青果教务网络管理系统,参照本代码依葫芦画瓢就能进行爬虫的编写。
因为青果教务网络管理系统作为一家普遍应用于全国各大高校的教务网络管理系统,自然具备着一定的反爬虫能力。所以一般的requests模块爬取还是有一定问题的。因此,这里我主要使用的是Selenium自动化测试。由于时间问题(其实是懒),我就不进行函数封装了。接下来,我们来说说分析的主要步骤:
1.我们想要进入青果教务网络管理系统页面,自首当其冲的目标是解决登录问题。那么我们打开高校教务网络管理系统官网的“开发者工具”进行分析。观察登录模块的整体源代码,可以发现我们要解决的第一个问题:Frame子页面问题。因为青果的账号登录的整个模块是囊括在Frame子页面中,所以直接获取输入框和按钮是获取不到的。这时,我们可以通过以下代码解决:
[Python] 纯文本查看 复制代码 browser.switch_to.frame('frm_login') # 进入教务网的子Frame页面
2.进入到教务网络管理系统的Frame子页面后,我们就可以直接获取到输入框和按钮了。关于账号和密码的输入,可以通过三种方式:① 直接写死到send_keys里面输入;② input()方法手动输入;③ 通过读取文件输入。这三种方式我都试过,最后是通过读入文件输入的(下列代码演示是通过input()方法输入)。
[Python] 纯文本查看 复制代码 login = input("请输入账号: ")
password = input("请输入密码: ")
browser.find_element_by_id('txt_asmcdefsddsd').send_keys(login)
browser.find_element_by_id('txt_asmcdefsddsd').send_keys(Keys.TAB) # TAB键是制表符,输入完账号后按下TAB跳至下一行密码输入框
browser.find_element_by_id('txt_pewerwedsdfsdff').send_keys(password)
browser.find_element_by_id('txt_sdertfgsadscxcadsads').click() # 点击验证码框使验证码显示出来
如果需要读入文件输入,这里我使用的是txt文本。文本名称为“账号密码.txt”,文本内格式为账号和密码分别单独占一行。读取账号密码txt文本的代码如下所示:
[Python] 纯文本查看 复制代码 with open('账号密码.txt', 'r') as file: # 账号和密码分别为一行
user = file.readlines() # 读取账号密码到列表中
login = user[0].strip() # 账号
password = user[1].strip() # 密码
3.验证码问题。关于验证码的问题,我使用过python的OCR库进行了灰度化和二值化,但是识别效果很不理想(毕竟验证码的目的就是为了限制爬虫,属于反爬虫的手段之一)。所以,这里我使用Python的PIL库进行页面的整体截图+精细裁剪获取验证码并自动弹出。
[Python] 纯文本查看 复制代码 # 验证码截图获取
image_file = "验证码.png"
screenshot = browser.save_screenshot(image_file) # 获取屏幕截图,保存成验证码.png
wait = WebDriverWait(browser, 20) # 显式等待最长20秒
img = wait.until(EC.presence_of_element_located((By.ID, 'txt_sdertfgsadscxcadsads'))) # 定位图片位置
time.sleep(1)
location = img.location
size = img.size
top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width']
# print("验证码位置:", top, bottom, left, right) # 366 463 236 432
im = Image.open(image_file)
im = im.crop((left+210, top+275, right+206, bottom+280)) # 有头模式验证码位置
# im = im.crop((left+130, top+125, right+90, bottom+130)) # 无头模式验证码位置
im.save(image_file) # 保存截取后的图片
im.show(image_file) # 显示图片
code = input("请输入识别后的验证码: ")
browser.find_element_by_id('txt_sdertfgsadscxcadsads').send_keys(code)
browser.find_element_by_id('btn_login').click()
4.输入验证码登录成功后,这里有些细节需要注意了!因为在多次尝试后,我发现在青果教务网络管理系统登录时如果之前登录的cookies生命周期还未消亡,此时登录会弹出“上次登录已下线”的弹窗。这里,我们通过一个if语句判断、捕获一下可能出现的弹窗问题。
[Python] 纯文本查看 复制代码 # 判断是否出现弹窗,如果出现则进行捕获并点击确认
if browser.switch_to.alert:
get_window = browser.switch_to.alert # 捕获弹窗“您在别处的登录已下线”
time.sleep(1)
get_window.accept() # 点击确认按钮
print("登录成功,现在您已到达XX大学教务网络页面!")
5.打开开发者工具中的Network,点击“学生成绩”下的“查看成绩”选项,观察到新增加了Stu_MyScore.aspx,这里我们点击找到Request URL。这样,我们登录后只需请求该url就能够获取网页源代码并进行成绩的爬取。
6.接下来就没什么好说的,不过就是通过Selenium进行页面元素的获取完成爬虫。唯一需要注意的是青果教务网络管理系统的学生成绩不是文字的,因此不能直接进行爬取。这里我们通过selenium的屏幕截图代码直接截图解决。
[Python] 纯文本查看 复制代码 browser.save_screenshot()
代码模板
[Python] 纯文本查看 复制代码 from selenium import webdriver
import time
# import requests
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select # 下拉选择框
# import tesserocr
from PIL import Image
with open('账号密码.txt', 'r') as file: # 账号和密码分别为一行
user = file.readlines() # 读取账号密码到列表中
login = user[0].strip() # 账号
password = user[1].strip() # 密码
# chrome_options = webdriver.ChromeOptions()
# chrome_options.add_argument('--headless')
# browser = webdriver.Chrome(options=chrome_options) # 开启无头模式
browser = webdriver.Chrome()
# browser = webdriver.PhantomJS(executable_path=r'D:\PhantomJS\phantomjs-2.1.1-windows\phantomjs-2.1.1-windows\bin\PhantomJS')
browser.get('http://jiaowu.高校.edu.cn/jwweb/home.aspx') # 填写入高校的青果教务网络管理系统的官网url
time.sleep(2) # 延时等待Frame子页面加载
browser.switch_to.frame('frm_login') # 进入教务网的子Frame页面
# login = input("请输入账号: ")
# password = input("请输入密码: ")
browser.find_element_by_id('txt_asmcdefsddsd').send_keys(login)
browser.find_element_by_id('txt_asmcdefsddsd').send_keys(Keys.TAB) # TAB键是制表符,输入完账号后按下TAB跳至下一行密码输入框
browser.find_element_by_id('txt_pewerwedsdfsdff').send_keys(password)
browser.find_element_by_id('txt_sdertfgsadscxcadsads').click() # 点击验证码框使验证码显示出来
# 验证码截图获取
image_file = "验证码.png"
screenshot = browser.save_screenshot(image_file) # 获取屏幕截图,保存成验证码.png
wait = WebDriverWait(browser, 20) # 显式等待最长20秒
img = wait.until(EC.presence_of_element_located((By.ID, 'txt_sdertfgsadscxcadsads'))) # 定位图片位置
time.sleep(1)
location = img.location
size = img.size
top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width']
# print("验证码位置:", top, bottom, left, right) # 366 463 236 432
im = Image.open(image_file)
im = im.crop((left+210, top+275, right+206, bottom+280)) # 有头模式验证码位置
# im = im.crop((left+130, top+125, right+90, bottom+130)) # 无头模式验证码位置
im.save(image_file) # 保存截取后的图片
im.show(image_file) # 显示图片
print("欢迎来到XX大学教务系统登录页面!")
code = input("请输入识别后的验证码: ")
browser.find_element_by_id('txt_sdertfgsadscxcadsads').send_keys(code)
browser.find_element_by_id('btn_login').click()
time.sleep(1)
# 判断是否出现弹窗,如果出现则进行捕获并点击确认
if browser.switch_to.alert:
get_window = browser.switch_to.alert # 捕获弹窗“您在别处的登录已下线”
time.sleep(1)
get_window.accept() # 点击确认按钮
print("登录成功,现在您已到达XX大学教务网络页面!")
# time.sleep(10)
# browser.close()
browser.get('http://jiaowu.高校.edu.cn/jwweb/xscj/Stu_MyScore.aspx') # 填写入高校的青果教务网络管理系统的学生成绩里面的ajax地址
time.sleep(2)
browser.find_element_by_id('SelXNXQ_2').click() # 点击学期
cj = Select(browser.find_element_by_id('sel_xn'))
year = input("请输入学年:(例如2019-2020学年输入2019)\n")
cj.select_by_value(year)
xq = Select(browser.find_element_by_id('sel_xq'))
xueqi_input = input("请输入学期:(第一学期输入1/第二学期输入2)")
xueqi = str(int(xueqi_input)-1)
xq.select_by_value(xueqi)
cjlx = input("请输入成绩类型:(原始/有效)\n")
if cjlx == "原始":
browser.find_element_by_id("ys_sj").click() # 点击原始成绩
browser.find_element_by_name("btn_search").click() # 点击检索
time.sleep(5)
browser.save_screenshot(str(year) + "-" + str(int(year)+1) + '学年第' + xueqi_input + '学期原始成绩.png')
elif cjlx == "有效":
browser.find_element_by_id("yx_sj").click() # 点击有效成绩
browser.find_element_by_name("btn_search").click() # 点击检索
time.sleep(5)
browser.save_screenshot(str(year) + "-" + str(int(year) + 1) + '学年第' + xueqi_input + '学期有效成绩.png')
print("提示: 用户[" + login + "]在" + year + "-" + str(int(year)+1) + "学年第" + xueqi_input + "学期的" + cjlx + "成绩查询成功!")
browser.close()
运行效果
补充
因为时间原因(其实是懒),我未发布无头模式的教务网络管理系统爬虫。有头模式和无头模式的青果教务网络管理系统爬虫区别主要就在于验证码位置和成绩截图位置的问题,因为我只是为了图个乐呵、因此对于无头模式的成绩页面打开是缩放的问题没有解决。感兴趣的小伙伴可以根据我发布的有头模式的代码进行一定的扩展延伸,修改成无头模式并添加其他功能。 |