现在大学上课,老师一般都是通过学校的课程网站来分享课程的课件等资源。但是,在果壳大课程网站上下载资源,总是需要一项一项的点入,再单击下载,显得十分麻烦,特别有时囤积了大量资源需要去下载,还得比对一下哪些没有下载,这对于我这种数据强迫症的人来说,十分不友好!
恰巧那会对爬虫挺感兴趣的,就寻思着拿这个练练手(重在学习),说做就做吧!Let’s go!
前期准备
首先,得先定我的需求:
- 可以选择课程,对该课程的所有课件实现一键下载;
- 鉴于我的数据强迫症,课件下载完毕后,应该能向我反馈下载的信息,主要就是新下载了哪些课件;
- 由于疫情的特殊原因,学校采取了网上授课的方式,但是家里网络不稳定,总是故障,考虑下载视频到本地观看,也便于课后复习;(想想以前,为了能课后复习,都是拿着电脑在上课的时候现场录的);
现在,需求以及清楚,接下来就是开始捣鼓课程网站的情况;
- 正常情况下,我们首先需求登录,进入教务系统主页;
- 然后,进入课程网站主页;
- 然后,在自己的选课情况中,选择课程,进入到课程主页;
- 然后,找到进入该课程主页的资源页面;
- 进入相关资源,并点击下载;
Emmm,用的时候都还好,这么一捋愈发觉得麻烦了……
弯路:webdriver
果壳大的教育业务平台网址是:http://sep.ucas.ac.cn/ ,要想进行后面的操作,首先,就得实现教育业务平台的自动登录;
起初嘛,刚接触爬虫,对网络也不是特别了解。脑海里冒出来最简单的思路就是:利用 selenium 的 webdriver 模拟登录过程,然后获取 cookies,之后再利用 cookies 登录;
pip install selenium
安装 selenium;
- selenium 是 ThoughtWorks 提供的一个强大的基于浏览器的开源自动化测试工具。支持的浏览器包括 IE、Chrome 和 Firefox 等;
- 到相应的官网下载浏览器驱动,我这里下载的是火狐的浏览器驱动;
接下来,写段程序测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import requests import time from selenium import webdriver
''' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36' } '''
def log_in( ): user = input("请输入用户名:") password = input("请输入密码:") driver = webdriver.Firefox() driver.get('http://sep.ucas.ac.cn/')
driver.find_element_by_xpath("./*//input[@id='menhuusername']").clear() driver.find_element_by_xpath("./*//input[@id='menhuusername']").send_keys(user) driver.find_element_by_xpath("./*//input[@id='menhupassword']").clear() driver.find_element_by_xpath("./*//input[@id='menhupassword']").send_keys(password)
time.sleep(3) driver.find_element_by_class_name('loginbtn').click()
cookie_items = driver.get_cookies() cookie = [item["name"] + "=" + item["value"] for item in cookie_items] cookie_str = ';'.join(item for item in cookie) with open('cookie.txt', 'w', encoding='utf-8') as f: f.write(cookie_str) f.close() print("已获取到cookies!")
headers_cookie = { "Cookie": cookie_str }
session = requests.session() session.post('http://sep.ucas.ac.cn/', headers=headers_cookie) print('登录系统成功……') return session
if __name__ == '__main__': session = log_in()
|
本以为,万事大吉,结果运行测试,emmmmm……才意识到,还得输入验证码!
那就继续造:
- 要用到图形处理,所以
pip install pillow
;
- 坑:安装 pillow,但是导入的时候是 PIL;
- 抓取下来验证码,还不够,肯定还得识别验证码内容,选择百度文字识别的 OCR,
pip install baidu_api
;
pillow 的原身是 PIL(Python Imaging Library),PIL 是 Python 图像处理标准库,功能非常强大,API 却非常简单易用;
但是 PIL 仅支持到 Python 2.7,后来由志愿者在此基础上创建了兼容的版本,即:Pillow,支持最新Python 3.x,又加入了许多新特性
百度文字识别的OCR,即:Optical Character Recognition,光学字符识别
然后,在上面代码的基础上,新增如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| from PIL import Image from aip import AipOcr
png = driver.find_element_by_id('captcha_img') png.screenshot('captcha.png')
img = Image.open('captcha.png') img = img.convert('L') count = 165 table = [] for i in range(256): if i < count: table.append(0) else: table.append(1) img = img.point(table, '1') img.save('captcha1.png')
APP_ID = '***' API_KEY = '***' SECRET_KEY = '***'
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
def get_file_content(file_path): with open(file_path, 'rb') as f: return f.read() image = get_file_content('captcha.png')
options = {'language_type': 'ENG', }
result = client.basicGeneral(image, options) for word in result['words_result']: captcha = (word['words'])
print('识别结果:' + captcha)
driver.find_element_by_xpath("./*//input[@id='menhucaptcha']").clear()
driver.find_element_by_xpath("./*//input[@id='menhucaptcha']").send_keys(captcha)
|
至此,总算是完成了登录过程,麻不麻烦?
肯定麻烦啊!而且有个很大的问题,就是每次运行会启动 webdriver,把程序拖得很慢,十分影响使用体验,所以我后来才改用了其他方法;
但是也不得不说,这段弯路也让我学到了挺多东西,还是很有意义的!
正文
其实说正解不太准确,只是说这个办法更加简单易行罢了;
之所以会突然又提出新的办法,是因为有一次,我发现果壳大的综合信息网( http://onestop.ucas.ac.cn/ )也可以登录到教育业务平台,而且在这里登录不需要验证码!
这下子,终于可以去掉上面那繁琐的验证码处理过程了。
但是,这只解决了一个问题,还是无法让我摆脱 webdriver。于是,我寻思这么难顶的资源下载方式,难道就没有前人 “种个树”?再仔细搜了搜,还真有!
原作者的程序是一键下载课程网站所有课程所有课件,呃……,对我来说有点夸张了,毕竟几十门课程,怎么得也有个几百项资源吧?也许对于一个爬虫来说爬取这些资源不算什么,但是,还有好多资源我可能不那么需要,事后还得整理。不过无妨,程序框架在这了,修改起来也简单。
确定修改目标:
- 能够输出选课的课程目录,供按课程批量下载课件;
- 加入视频下载功能;
开干!!!
网站登录
首先,是登录信息,这里采用了直接将登录信息保存在 txt 文本文件里,避免了我原先那样每次运行脚本都需要手动输入的尴尬。简单的文本处理:
1 2 3 4 5 6 7 8
| try: config = open("user.txt", encoding='utf-8') line = config.readline().split() username = line[0] password = line[1] except IOError as e: print(e)
|
重点来了,这次登录改用了 requests 库的 session 会话对象,构造 post 表单的方式实现登录,并且由于 session 对象的特性,也便于我们后续其他页面的操作;
session 的特性体现在它的作用时间:从用户到达某个特定的 Web 页开始,到该用户离开 Web 站点,或在程序中利用代码终止某个 Session 结束。
引用 Session 则可以让一个用户访问多个页面,之间的切换也会保留该用户的信息;
说白了,就是一旦我们使用 session 成功的登录了某个网站后,则在再次使用该 session对象求求该网站的其他网页都会默认使用该 session 之前使用的 cookie 等参数;
详细用法参见文章:Python Requests库:HTTP for Humans
- 构造请求头:
- 打开网页( http://onestop.ucas.ac.cn/ ),然后进入开发者模式;

Accept
:用户代理期望的 MIME 类型列表,不用管;
Accept-Encoding
:用户代理支持的压缩方法,不用管;
Accept-language
:用户代理期望的页面语言,不用管;
Connection
:决定当前的事务完成后,是否会关闭网络连接。如果该值是“keep-alive”,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。因此,需要设置;
Cookie
:就不用说了,我们目标就是自动获取登录后的 cookie;
host
:指明服务器域名,需要设置;
upgrade-insecure-requests
:用来向服务器端发送信号,表示客户端优先选择加密及带有身份验证的响应,并且它可以成功处理 upgrade-insecure-requests CSP 指令。
User-Agent
;指明用户代理软件的应用类型、操作系统、软件开发商以及版本号;
- 更多详情可见:『MDN web docs』
- 构造 post 表单:
- 打开网页( http://onestop.ucas.ac.cn/ ),然后进入开发者模式(未登录状态);

Preserve log
:保留 log 信息;
XHR
:(XMLHttpRequest)筛选出与服务器的交互信息;
- 然后,开发者模式设置完成后,在浏览器输入信息登录(不要关闭开发窗口);

- 很明显,我所需要的信息应该在 Name = 0,的那条记录里,打开这条记录;

- 在 Form Data 里就有我们构造 post 表单所需要去构造的信息,为:用户名、密码、是否记住密码,这三个字段;
这一部分代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| session = requests.session() login_url = 'http://onestop.ucas.ac.cn/'
headers= { 'Host': 'onestop.ucas.ac.cn', "Connection": "keep-alive", 'Referer': 'http://onestop.ucas.ac.cn/home/index', 'X-Requested-With': 'XMLHttpRequest', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", }
post_data = { "username": username, "password": password, "remember": 'checked', } html = session.post(login_url, data=post_data, headers=headers).text
res = json.loads(html) html = session.get(res['msg']).text print('登录系统成功!')
|
这样,我们就有了一个建立了连接的 session,以后就可以利用该 session 完成其他页面的操作;
进入课程网站
首先,查找进入课程网站的 url:
利用之前的 session 访问:h_k = session.get(url)
这里有个注意点,在我们直接点击课程网站图标时,会进入一个跳转页面,而我们刚刚 session 访问到的就是这个跳转页面,所以实际上我们还并没有进入到课程网站页面中去;
为了便于分析,将 session.get() 到的对象转换成文本文件存储:
1 2 3 4 5 6 7 8 9 10
| def save_html(html): f = open('test.html','w',encoding='utf-8') f.write(html) f.close
url = "http://sep.ucas.ac.cn/portal/site/16/801" h_k = session.get(url) save_html(h_k.text)
|
打开 h_k.text,我们知道跳转页面里,提示信息会有 “点击这里跳转” 这种选项,在这个文本里 ctrl F,输入:“跳转”,就可以看到,确实存在一个标签,如下:
(当然了,也可以在跳转的时候,强制停止刷新网页,然后在跳转页面用开发者模式查找 “这里” 这个字段的 href)
1 2 3 4 5 6 7 8 9
| <div class="row-fluid">
<div class="span12" style="text-align:center;">
<h4>2秒钟没有响应请点击<a href="https://course.ucas.ac.cn/portal/plogin?Identity=fbd361f2-73cc-48b7-a5ec-37528b27a058&roleId=801"><strong>这里</strong></a>直接跳转</h4>
</div>
</div>
|
接下来,利用正则表达式,获取这个 url 里,Identity 的值(身份认证信息),重新构造 url,直接进入到课程网站页面:
1 2 3 4 5 6 7 8
| key = re.findall(r'"https://course.ucas.ac.cn/portal/plogin\?Identity=(.*)"', h_k.text)[0]
url = "http://course.ucas.ac.cn/portal/plogin/main/index?Identity=" + key page = session.get(url) print('课程网站系统进入成功!') return page
|
获取课程信息
先进入主页:
1 2 3 4 5
| mycourseBS = BeautifulSoup(courseSite.text,"lxml") url_mycourse = mycourseBS.find_all('a',{"class":'Mrphs-toolsNav__menuitem--link'})[0] url_mycourse = url_mycourse["href"] coursePage = session.get(url_mycourse)
|
在我的课程里,获取课程信息:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| coursePageBS = BeautifulSoup(coursePage.text,"lxml") Course_info = coursePageBS.find_all('li',{"class":"fav-sites-entry"}) length = len(Course_info) print("*****************************************************************") print("所选课程总数为:",length) print(("已选课程列表:")) for i in range(0,length-1): info = Course_info[i] tag = info.div.a courseName = tag["title"] print(" ",i,courseName) courseUrl = tag["href"] course_list.append((courseName,courseUrl)) print("*****************************************************************") return course_list
|
课件下载
后面的页面跳转等处理,其实都类似,这里只介绍一些关键点,毕竟主要的目的在于学习:
进入课程资源页面,这里直接将关键的 BeautifulSoup 查找语句列出:
1 2
| url = h_bs.find_all(title="资源 - 上传、下载课件,发布文档,网址等信息")[0].get("href")
|
查找所有资源链接:
- 这里文件夹的处理,涉及到 onclick(),展开文件夹,更新 html 页面,但是这里我没态弄太明白,后面再琢磨。可以的话,可以在评论区给我留言一些相关知识讲解文章;
下载文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| def download_kj(url, fileName, className, session): fileName = fileName.replace(u"\xa0", " ").replace(u"\xc2", "") fileName = re.sub(r"[/\\:*\"<>|?]", "", fileName) className = re.sub(r"[/\\:*\"<>|?]", "", className)
dir = os.getcwd() + "/" + className file = os.getcwd() + "/" + className + "/" + fileName if not os.path.exists(dir): os.mkdir(dir) if os.path.exists(file): print("%s已存在,就不下载了" % fileName) return 0 print("开始下载%s..." % fileName) s = session.get(url) with open(file, "wb") as data: data.write(s.content) return 1
|
视频下载
进入课程资源页面:
1 2
| h_bs = BeautifulSoup(h.text, "lxml") url = h_bs.find_all(title="课程视频 - 课程视频")[0].get("href")
|
又分为:课程视频(录播),直播视频(回放),这两部分处理方法一样,以第一项为例:
由于视频页可能包含多页,先抓取各个页的链接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| allpageURL.append(url) flag =1 i = 0 while flag: s = session.get(allpageURL[i]) page = re.search('<span><a href="([^上]*?)">下一页</a></span>',s.text, re.S) if page : page = page.groups()[0] pageURL = 'http://course.ucas.ac.cn' + page allpageURL.append(pageURL) else: flag = 0 i = i+1
|
接下来,就是循环在每页,不断的获取所有视频播放的 url,然后进入到播放页面,再找到视频源的 url 即可;
视频下载:由于果壳大视频采用的 .m3u8 流媒体格式,我使用到了 ffmpeg(需要提前在电脑上安装);用 subprocess 模块来产生子进程,调用 ffmpeg 完成下载;
1 2 3 4 5 6
| def download_sp(spName, spUrl): ins = 'ffmpeg -i ' + spUrl + ' -c copy ' + spName +'.mp4' p = subprocess.Popen(ins) p.wait() print('下载完毕')
|
写在最后
其实这个脚本是挺久之前弄的了,但是总觉得之前边写边学,零零碎碎,慢慢的又觉得忘的差不多了。这当然不行,于是,重新梳理总结了一下当时的编写历程。
通过这次,主要学习到的知识点:
- 利用 webdriver 模拟登陆,以及遇到验证码时,将验证码抓取下来处理,并完成识别;
- 利用 session 构造 post 表单的方式,实现网站登录;
- 正则表达式的使用;
- BeautifulSoup 的查找方法;
- subprocess 的简单使用;
- 等等
不足:
最后还是需要强调一下,重在学习,利用脚本下载的资源,仅供自己学习使用,请不要传播!
『脚本源码』