学校教务系统的课程表爬取
课程作业需要实现一个课程表,我负责完成学校的教务系统中课程表的导入工作。
需要解决两个问题,第一个是教务系统访问课程表所在url时,会被告知需要先加载某框架,这让我很困扰,不知道如何用urlopen去解决这个问题;第二个问题是,不同的课程对应的课时是不一样的,意味着显示的时候rowspan值是不一样的,需要重新进行解析。
第一个问题采用selenium库,然后使用Chrome的headless模式就可以解决模拟登陆并获取课程表的html。
这里面课程表所在页面的url不是固定的,而是存在于MenuFrame的一段js代码中,因此采用js2xml对js代码格式化,然后使用xpath提取其中的url。
代码如下:
def login(login_driver, username, password):
login_driver.get('http://yjxt.bupt.edu.cn')
username = login_driver.find_element(By.ID, 'username')
username.send_keys(username)
password = login_driver.find_element(By.ID, 'password')
password.send_keys(password)
button = login_driver.find_element_by_class_name('btn-lg')
button.click()
def get_payload(get_payload_driver):
# 课程表所在页面的url不是固定的,需要从js代码中提取
get_payload_driver.switch_to.frame('MenuFrame')
source = get_payload_driver.page_source
soup = BeautifulSoup(source, 'html.parser')
script = soup.select('body form script')[1].string
# 使用js2xml格式化之后再使用xpath提取js代码中附加的url
script_text = js2xml.parse(script, debug=False)
for x in script_text.xpath("//object/property[@name = 'url']/string/text()"):
if x[:21] == 'Course/StuCourseQuery':
return x
def get_course_html(get_course_html_driver, payload):
get_course_html_driver.get('http://yjxt.bupt.edu.cn/Gstudent/' + payload)
return get_course_html_driver.page_source
在获取课程表页面的html之后就需要去解析课程表,课程表中每个课程占据的单元格是不定的,比如数学课占据了周一上午的第一节和第二节课,那么在遍历的过程中是无法遍历到周一上午第二节课这个单元格的,实际获取的单元格是周二上午第二节课,因此需要获得下一个单元格的实际位置用于同步,这里维护了一个二维数组用于模拟课程表,在遍历的过程中对其染色,比如遍历到了周一上午第一节课,会同时将第二节课也染色,这样就可以根据next_pic()函数获取实际的位置。代码如下:
def next_pic(pic, x, y):
for i in range(x, 11):
if i == x:
for j in range(y, 9):
if pic[i][j] == 0 and j != y:
return i, j
else:
for j in range(9):
if pic[i][j] == 0:
return i, j
def analysis_html(html):
# 解析html
# 获取table
soup = BeautifulSoup(html, 'html.parser')
table = soup.find(id='contentParent_dgData')
# 解析table
pic = [[0 for i in range(9)] for i in range(11)]
row = 0
column = 0
for x in table.find_all('td'):
if x.string != '\n':
try:
rowspan = int(x['rowspan'])
except AttributeError as e:
rowspan = 1
for i in range(rowspan):
pic[row + i][column] = 1
if x.string.strip().encode('utf-8') in EnumA:
row, column = next_pic(row, column)
continue
else:
print(x.string.strip().encode('gbk'), '周'.decode('utf-8').encode('gbk'),
column, row + 1, '-', row + rowspan)
else:
row, column = next_pic(row, column)
完整的代码见github