其实,自己是一直打算爬取教务系统的,可是懒啊懒,懒啊懒,就一直懒到现在了。所以,寒假开始,痛改前非,重新做人,就稍微接触了一些python的知识,用来爬取每个学生都梦寐以求的教务系统。
前期准备
教务系统页面解析
首先,我们看看,教务系统页面长啥样?
这里,我们需要注意两点:
- 验证码的获取。
- 登陆时提交的信息。
验证码获取
这里,我们只需要进行抓包即可,这里我选择使用Fiddler进行抓包。首先打开Fiddler,然后刷新一下教务系统的界面即可。
我们在这里能够看到,获取验证码的URL是:http://210.42.121.241/servlet/GenImg
所以,我们就可以这样一步一步来获取我们需要的信息。
- 提交请求到:http://210.42.121.241/servlet/GenImg
- 下载验证码到本地,进行读取。(因为我也是刚接触python,所以这里就下载下来手动读取了,后面学习过程中,我会尝试用机器进行读取的)
- 输入验证码,提交到登陆请求,保存cookies。(这一步是很重要的)
登陆时提交信息
怎么取提交道路请求呢,我们继续来抓包。先清空Fiddler中的信息,然后在网页上输入学号,密码,验证码,点击登陆,就可以看到抓包信息了。
提交的请求包括三个参数:
{ id: 学号 pwd: 密码 xdvfb: 验证码 }
这样,我们就可以模拟取提交请求,并登陆到我们的教务系统啦~
模拟登陆
好,那么我们现在就用python开始模拟登陆。
def get_cookie():
# 验证码地址和登陆地址
base_url = "http://210.42.121.241/servlet/GenImg"
login_url = "http://210.42.121.241/servlet/Login"
# 将cookie绑定到一个opener,cookie由cookielib自动管理
cookie = cookielib.MozillaCookieJar("file.txt")
handler = urllib2.HTTPCookieProcessor(cookie)
opener = urllib2.build_opener(handler)
# 用户名和密码
username = "xxxxxxxxxxxxxxx"
password = "xxxxxxxxxxxxxxx"
# 用opener访问验证码地址,获取验证码
picture = opener.open(base_url).read()
# 保存验证码到本地
local = open("image.jpg", "wb")
local.write(picture)
local.close()
# 输入验证码,并构造表单
secret_code = raw_input("请输入验证码:")
data = {
'id': 'xxxxxxxxxxxx',
'pwd': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'xdvfb': secret_code
}
# 根据抓包信息,构造headers
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Content-Length': 64,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
}
# 获取数据,保存cooki,最后返回openner
data = urllib.urlencode(data)
request = urllib2.Request(login_url, data, headers)
response = opener.open(request)
cookie.save(ignore_discard=True, ignore_expires=True)
return opener
终于好啦,现在我们的验证码图片就保存到image.jpg里了,我们的cookie就保存到file.txt中了。
后面,我们就可以直接通过这个opener进行访问啦~~~~
分析课表页面
我们在选课页面,单击第二页,通过抓包,我们可以看到访问页面。
好,通过抓包,我们可以看到:请求页面的URL是:
http://210.42.121.241/stu/choose_PubLsn_list.jsp?XiaoQu=0&credit=0&keyword=&pageNum=2
参数说明:
- XiaoQu:这个参数很好理解,就是我们的校区。0表示全校,1表示1区(文理学部),2表示2区(工学部),3表示3区(信息学部),4表示4区(医学部)。
- credit:学分
- keyword:这个是查询的关键字,这里我们不需要使用,大家有想继续深入的,可以继续研究
- pageNum:页码。
好,获取课程信息的URL我们也解析出来了,现在就需要我们依次爬取每个页面的信息,获取到课程信息就行了。
爬取页面
首先,这里,因为总共有24页,所以这里,我打算采用多线程进行处理,将所有爬到的课程信息保存到一个lesson_list中,然后将这个list中的信息保存到文件中即可。
构造Lesson实体类
class Lesson:
def __init__(self, name, degree, number, teacher, jobTitle, college, textbook, year, semester, time, remarks):
self.name = name
self.degree = degree
self.number = number
self.teacher = teacher
self.jobTitle = jobTitle
self.college = college
self.textbook = textbook
self.year = year
self.semester = semester
self.time = time
self.remarks = remarks
def get_name(self):
return self.name
def get_degree(self):
return self.degree
def get_number(self):
return self.number
def get_teacher(self):
return self.teacher
def get_job_title(self):
return self.jobTitle
def get_college(self):
return self.college
def get_textbook(self):
return self.textbook
def get_year(self):
return self.year
def get_semester(self):
return self.semester
def get_time(self):
return self.time
def get_remarks(self):
return self.remarks
def __str__(self):
return str(self.name + "\t" + self.degree + "\t" + self.number + "\t" + self.teacher + "\t" + self.jobTitle + "\t" + self.college + "\t" + self.textbook + "\t" + self.year + "\t" + self.semester + "\t" + self.time + "\t" + self.remarks)
构造线程类MyThread
- 线程类MyThread继承threading.Thread
- 属性说明:
- name:线程名称
- page:该线程要爬取的页码
- lesson_list:爬取的课程信息保存到lesson_list中
- opener:opener
# -*- coding: utf-8 -*-
import sys
import threading
import urllib2
from bs4 import BeautifulSoup
from Utils import get_lesson
reload(sys)
sys.setdefaultencoding('utf-8')
threadLock = threading.Lock()
class MyThread(threading.Thread):
def __init__(self, name, page, lesson_list, opener):
threading.Thread.__init__(self)
self.name = name
self.page = page
self.lesson_list = lesson_list
self.opener = opener
def run(self):
get_lessons(self.name, self.page, self.lesson_list, self.opener)
def get_lessons(name, page, lesson_list, opener):
print name + " is starting......"
try:
lesson_url = "http://210.42.121.241/stu/choose_PubLsn_list.jsp?XiaoQu=0&credit=0&keyword=&pageNum=" + page
result = opener.open(lesson_url).read().decode('gb2312', 'ignore').encode('utf-8')
soup = BeautifulSoup(result, "html.parser")
trs = soup.find_all(name='tr')
threadLock.acquire() # 互斥地写入lesson_list
for j in range(1, len(trs)):
tr = trs[j]
lesson = get_lesson(tr)
lesson_list.append(lesson)
threadLock.release()
except urllib2.HTTPError, e:
print e.code
构造工具类Util
这里,需要明白一点,我们爬取地页面中,课程信息是保存到一个table中,第一个tr是表头,往后的每个tr都代表了一个lesson,所以我们在Util中定义一个方法get_lesson(tr)用来解析。
这里,具体的解析,我写的也很粗暴,详细的我就不多说了,大家看一下源码就明白了。
# -*- coding: utf-8 -*-
import cookielib
import urllib
import urllib2
from bs4 import BeautifulSoup
from xlwt import Workbook
from Lesson import Lesson
def get_lesson(tr):
soup = BeautifulSoup(str(tr), "html.parser")
tds = soup.find_all(name='td')
name = tds[0].string
degree = tds[1].string
teacher = tds[3].string
jobTitle = tds[4].string
college = tds[5].string
textbook = tds[6].string
year = tds[7].string
semester = tds[8].string
soup = BeautifulSoup(str(tds[2]), "html.parser")
number = soup.findAll("font")[0].string + str(tds[2])[str(tds[2]).index("</font>") + 7: str(tds[2]).rindex("</td>")]
soup = BeautifulSoup(str(tds[9]), "html.parser")
time = soup.findAll(name="div")[0].string.strip().replace(" ", "").replace("\n", "")
soup = BeautifulSoup(str(tds[10]), "html.parser")
remarks = soup.findAll(name="div")[0].string.strip().replace(" ", "").replace("\n", "")
lesson = Lesson(name, degree, number, teacher, jobTitle, college, textbook, year, semester, time, remarks)
return lesson
def init(sheet):
sheet.write(0, 0, "课程名称")
sheet.write(0, 1, "学分")
sheet.write(0, 2, "剩余/最大人数")
sheet.write(0, 3, "教师名")
sheet.write(0, 4, "职称")
sheet.write(0, 5, "授课学院")
sheet.write(0, 6, "教材")
sheet.write(0, 7, "学年")
sheet.write(0, 8, "学期")
sheet.write(0, 9, "上课时间地点")
sheet.write(0, 10, "备注")
def save_lesson(lesson, sheet, row):
sheet.write(row, 0, lesson.get_name())
sheet.write(row, 1, lesson.get_degree())
sheet.write(row, 2, lesson.get_number())
sheet.write(row, 3, lesson.get_teacher())
sheet.write(row, 4, lesson.get_job_title())
sheet.write(row, 5, lesson.get_college())
sheet.write(row, 6, lesson.get_textbook())
sheet.write(row, 7, lesson.get_year())
sheet.write(row, 8, lesson.get_semester())
sheet.write(row, 9, lesson.get_time())
sheet.write(row, 10, lesson.get_remarks())
def save(lesson_list):
book = Workbook(encoding='utf-8')
sheet = book.add_sheet("lesson")
init(sheet)
row = 1
for lesson in lesson_list:
save_lesson(lesson, sheet, row)
row = row + 1
book.save("lesson.xls")
def get_cookie():
# 验证码地址和post地址
base_url = "http://210.42.121.241/servlet/GenImg"
login_url = "http://210.42.121.241/servlet/Login"
# 将cookie绑定到一个opener,cookie由cookielib自动管理
cookie = cookielib.MozillaCookieJar("file.txt")
handler = urllib2.HTTPCookieProcessor(cookie)
opener = urllib2.build_opener(handler)
# 用户名和密码
username = "xxxxxxxxxxxxxxx"
password = "xxxxxxxxxxxxxxx"
# 用opener访问验证码地址,获取cookie
picture = opener.open(base_url).read()
# 保存验证码到本地
local = open("image.jpg", "wb")
local.write(picture)
local.close()
# 输入验证码,并构造表单
secret_code = raw_input("请输入验证码:")
data = {
'id': 'xxxxxxxxxxxx',
'pwd': 'xxxxxxxxxxxxxxxx',
'xdvfb': secret_code
}
# 根据抓包信息,构造headers
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Content-Length': 64,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
}
data = urllib.urlencode(data)
request = urllib2.Request(login_url, data, headers)
response = opener.open(request)
cookie.save(ignore_discard=True, ignore_expires=True)
return opener
保存到文件
这里,保存到文件,我们使用了python的一个包xlwt,就来将我们的文件直接保存到excel文件中。
总结
我也就不多说什么了,思路就是我写的这样。代码我都放在了我的github(项目地址)上,大家可以去下载学习,有什么不好的地方,欢迎大家多批评指正。