Python爬取Coursera课程资源的详细过程


Posted in Python onNovember 04, 2014

有时候我们需要把一些经典的东西收藏起来,时时回味,而Coursera上的一些课程无疑就是经典之作。Coursera中的大部分完结课程都提供了完整的配套教学资源,包括ppt,视频以及字幕等,离线下来后会非常便于学习。很明显,我们不会去一个文件一个文件的下载,只有傻子才那么干,程序员都是聪明人!

那我们聪明人准备怎么办呢?当然是写一个脚本来批量下载了。首先我们需要分析一下手工下载的流程:登录自己的Coursera账户(有的课程需要我们登录并选课后才能看到相应的资源),在课程资源页面里,找到相应的文件链接,然后用喜欢的工具下载。

很简单是吧?我们可以用程序来模仿以上的步骤,这样就可以解放双手了。整个程序分为三个部分就可以了:

登录Coursera;在课程资源页面里面找到资源链接;根据资源链接选择合适的工具下载资源。

下面就来具体的实现以下吧!

登录

刚开始时自己并没有添加登录模块,以为访客就可以下载相应的课程资源,后来在测试comnetworks-002这门课程时发现访客访问资源页面时会自动跳转到登录界面,下图是chrome在隐身模式访问该课程资源页面时的情况。

Python爬取Coursera课程资源的详细过程

要想模拟登录,我们先找到登录的页面,然后利用google的Developer Tools分析账号密码是如何上传到服务器的。

我们在登录页面的表单中填入账号密码,然后点击登录。与此同时,我们需要双眼紧盯Developer Tools——Network,找到提交账号信息的url。一般情况下,如果要向服务器提交信息,一般都用post方法,这里我们只需要先找到Method为post的url。悲剧的是,每次登录账号时,Network里面都找不到提交账户信息的地址。猜测登录成功后,直接跳转到登录成功后的页面,想要找的内容一闪而过了。

于是就随便输入了一组账号密码,故意登录失败,果真找到了post的页面地址,如下图:

Python爬取Coursera课程资源的详细过程

地址为:https://accounts.coursera.org/api/v1/login。为了知道向服务器提交了哪些内容,进一步观察post页面中表单中内容,如下图:

Python爬取Coursera课程资源的详细过程

我们看到一共有三个字段:

email:账号的注册邮箱password:账号密码webrequest:附加的字段,值为true。

接下来就动手写吧,我选择用python的Requests库来模拟登录,关于Requests官网是这样介绍的。

Requests is an elegant and simple HTTP library for Python, built for human beings.

事实上requests用起来确实简单方便,不亏是专门为人类设计的http库。requests提供了Session对象,可以用来在不同的请求中传递一些相同的数据,比如在每次请求中都携带cookie。

初步的代码如下:

signin_url = "https://accounts.coursera.org/api/v1/login"
logininfo = {"email": "...",
             "password": "...",
             "webrequest": "true"
             }
user_agent = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) "
              "AppleWebKit/537.36 (KHTML, like Gecko) "
              "Chrome/36.0.1985.143 Safari/537.36")
post_headers = {"User-Agent": user_agent,
                "Referer": "https://accounts.coursera.org/signin"
                }
coursera_session = requests.Session()
login_res = coursera_session.post(signin_url,
                                  data=logininfo,
                                  headers=post_headers,
                                  )
if login_res.status_code == 200:
    print "Login Successfully!"
else:
    print login_res.text

将表单中提交的内容存放在字典中,然后作为data参数传递给Session.post函数。一般情况下,最好是加上请求User-AgentReferer等请求头部,User-Agent用来模拟浏览器请求,Referer用来告诉服务器我是从referer页面跳转到请求页面的,有时候服务器会检查请求的Referer字段来保证是从固定地址跳到当前请求页的。

上面片段的运行结果很奇怪,显示如下信息:Invalid CSRF Token。后来在github上面搜索到一个Coursera的批量下载脚本,发现人家发送页面请求时headers多了XCSRF2Cookie, XCSRF2Token, XCSRFToken, cookie4个字段。于是又重新看了一下post页面的请求头部,发现确实有这几个字段,估计是服务器端用来做一些限制的。

用浏览器登录了几次,发现XCSRF2Token, XCSRFToken是长度为24的随机字符串,XCSRF2Cookie为"csrf2_token_"加上长度为8的随机字符串。不过一直没搞明白Cookie是怎么求出来的,不过看github上面代码,Cookie似乎只是"csrftoken"和其他三个的组合,试了一下竟然可以。

在原来的代码上添加以下部分就足够了。

def randomString(length):
    return ''.join(random.choice(string.letters + string.digits) for i in xrange(length))
XCSRF2Cookie = 'csrf2_token_%s' % ''.join(randomString(8))
XCSRF2Token = ''.join(randomString(24))
XCSRFToken = ''.join(randomString(24))
cookie = "csrftoken=%s; %s=%s" % (XCSRFToken, XCSRF2Cookie, XCSRF2Token)
post_headers = {"User-Agent": user_agent,
                "Referer": "https://accounts.coursera.org/signin",
                "X-Requested-With": "XMLHttpRequest",
                "X-CSRF2-Cookie": XCSRF2Cookie,
                "X-CSRF2-Token": XCSRF2Token,
                "X-CSRFToken": XCSRFToken,
                "Cookie": cookie
                }

至此登录功能初步实现。

分析资源链接

登录成功后,我们只需要get到资源页面的内容,然后过滤出自己需要的资源链接就行了。资源页面的地址很简单,为https://class.coursera.org/name/lecture,其中name为课程名称。比如对于课程comnetworks-002,资源页面地址为https://class.coursera.org/comnetworks-002/lecture。

抓取到页面资源后,我们需要分析html文件,这里选择使用BeautifulSoup。BeautifulSoup是一个可以从HTML或XML文件中提取数据的Python库,相当强大。具体使用官网上有很详细的文档,这里不再赘述。在使用BeautifulSoup前,我们还得找出资源链接的规律,方便我们过滤。

其中课程每周的总题目为class=course-item-list-header的div标签下,每周的课程均在class=course-item-list-section-list的ul标签下,每节课程在一个li标签中,课程资源则在li标签中的div标签中。

查看了几门课程之后,发现过滤资源链接的方法很简单,如下:

ppt和ppt资源:用正则表达式匹配链接;字幕资源:找到title="Subtitles (srt)"的标签,取其href属性;视频资源:找到title="Video (MP4)"的标签,取其href属性即可。

字幕和视频也可以用正则表达式过滤,不过用BeautifulSoup根据title属性来匹配,有更好的易读性。而ppt和pdf资源,没有固定的title属性,只好利用正则表达式来匹配。

具体代码如下:

soup = BeautifulSoup(content)
chapter_list = soup.find_all("div", class_="course-item-list-header")
lecture_resource_list = soup.find_all("ul", class_="course-item-list-section-list")
ppt_pattern = re.compile(r'https://[^"]*\.ppt[x]?')
pdf_pattern = re.compile(r'https://[^"]*\.pdf')
for lecture_item, chapter_item in zip(lecture_resource_list, chapter_list):
    # weekly title
    chapter = chapter_item.h3.text.lstrip()
    for lecture in lecture_item:
        lecture_name = lecture.a.string.lstrip()
        # get resource link
        ppt_tag = lecture.find(href=ppt_pattern)
        pdf_tag = lecture.find(href=pdf_pattern)
        srt_tag = lecture.find(title="Subtitles (srt)")
        mp4_tag = lecture.find(title="Video (MP4)")
        print ppt_tag["href"], pdf_tag["href"]
        print srt_tag["href"], mp4_tag["href"]

下载资源

既然已经得到了资源链接,下载部分就很容易了,这里我选择使用curl来下载。具体思路很简单,就是输出curl resource_link -o file_name到一个种子文件中去,比如到feed.sh中。这样只需要给种子文件执行权限,然后运行种子文件即可。

为了便于归类课程资源,可以为课程每周的标题建立一个文件夹,之后该周的所有课程均下载在该目录下。为了方便我们快速定位到每节课的所有资源,可以把一节课的所有资源文件均命名为课名.文件类型。具体的实现比较简单,这里不再给出具体程序了。可以看一下一个测试例子中的feed.sh文件,部分内容如下:

mkdir 'Week 1: Introduction, Protocols, and Layering'
cd 'Week 1: Introduction, Protocols, and Layering'
curl https://d396qusza40orc.cloudfront.net/comnetworks/lect/1-readings.pdf -o '1-1 Goals and Motivation (15:46).pdf'
curl https://class.coursera.org/comnetworks-002/lecture/subtitles?q=25_en&format=srt -o '1-1 Goals and Motivation (15:46).srt'
curl https://class.coursera.org/comnetworks-002/lecture/download.mp4?lecture_id=25 -o '1-1 Goals and Motivation (15:46).mp4'
curl https://d396qusza40orc.cloudfront.net/comnetworks/lect/1-readings.pdf -o '1-2 Uses of Networks (17:12).pdf'
curl https://class.coursera.org/comnetworks-002/lecture/subtitles?q=11_en&format=srt -o '1-2 Uses of Networks (17:12).srt'
curl https://class.coursera.org/comnetworks-002/lecture/download.mp4?lecture_id=11 -o '1-2 Uses of Networks (17:12).mp4'

到这里为止,我们已经成功完成爬取Coursera课程资源的目标,具体的代码放在gist上。使用时,我们只需要运行程序,并把课程名称作为参数传递给程序就可以了(这里的课程名称并不是整个课程的完整名字,而是在课程介绍页面地址中的缩略名字,比如Computer Networks这门课,课程名称是comnetworks-002)。

其实,这个程序可以看做一个简单的小爬虫程序了,下面粗略介绍下爬虫的概念。

一点都不简单的爬虫

关于什么是爬虫,wiki上是这样说的

A Web crawler is an Internet bot that systematically browses the World Wide Web, typically for the purpose of Web indexing.

爬虫的总体架构图如下(图片来自wiki):

Python爬取Coursera课程资源的详细过程

简单来说,爬虫从Scheduler中获取初始的urls,下载相应的页面,存储有用的数据,同时分析该页面中的链接,如果已经访问就pass,没访问的话加入到Scheduler中等待抓取页面。

当然有一些协议来约束爬虫的行为规范,比如许多网站都有一个robots.txt文件来规定网站哪些内容可以被爬取,哪些不可以。

每个搜索引擎背后都有一个强大的爬虫程序,把触角伸到网络中的所有角落,不断去收集有用信息,并建立索引。这种搜索引擎级别的爬虫实现起来非常复杂,因为网络上的页面数量太过庞大,只是遍历他们就已经很困难了,更不要说去分析页面信息,并建立索引了。

实际应用中,我们只需要爬取特定站点,抓取少量的资源,这样实现起来简单很多。不过仍然有许多让人头疼的问题,比如许多页面元素是javascript生成的,这时候我们需要一个javascript引擎,渲染出整个页面,再加以过滤。

更糟糕的是,许多站点都会用一些措施来阻止爬虫爬取资源,比如限定同一IP一段时间的访问次数,或者是限制两次操作的时间间隔,加入验证码等等。绝大多数情况下,我们不知道服务器端是如何防止爬虫的,所以要想让爬虫工作起来确实挺难的。

Python 相关文章推荐
Python入门篇之函数
Oct 20 Python
Python实现各种排序算法的代码示例总结
Dec 11 Python
Python使用正则表达式获取网页中所需要的信息
Jan 29 Python
Python SQLite3简介
Feb 22 Python
Python基于TCP实现会聊天的小机器人功能示例
Apr 09 Python
Python2.7.10以上pip更新及其他包的安装教程
Jun 12 Python
Python使用googletrans报错的解决方法
Sep 25 Python
详解python列表(list)的使用技巧及高级操作
Aug 15 Python
DataFrame.to_excel多次写入不同Sheet的实例
Dec 02 Python
python实现手势识别的示例(入门)
Apr 15 Python
卸载tensorflow-cpu重装tensorflow-gpu操作
Jun 23 Python
用OpenCV进行年龄和性别检测的实现示例
Jan 29 Python
使用python开发vim插件及心得分享
Nov 04 #Python
Python学习笔记之os模块使用总结
Nov 03 #Python
Python中获取网页状态码的两个方法
Nov 03 #Python
Python random模块常用方法
Nov 03 #Python
Python求两个list的差集、交集与并集的方法
Nov 01 #Python
python多线程threading.Lock锁用法实例
Nov 01 #Python
python分割文件的常用方法
Nov 01 #Python
You might like
PHP获取表单textarea数据中的换行问题
2010/09/10 PHP
探讨:使用XMLSerialize 序列化与反序列化
2013/06/08 PHP
php Session存储到Redis的方法
2013/11/04 PHP
zf框架的校验器InArray使用示例
2014/03/13 PHP
CI框架整合smarty步骤详解
2016/05/19 PHP
ajax+php实现无刷新验证手机号的实例
2017/12/22 PHP
html读出文本文件内容
2007/01/22 Javascript
JS字符串函数扩展代码
2011/09/13 Javascript
jquery如何实现锚点链接之间的平滑滚动
2013/12/02 Javascript
jquery实现公告翻滚效果
2015/02/27 Javascript
深入理解JavaScript系列(40):设计模式之组合模式详解
2015/03/04 Javascript
js实现页面跳转的几种方法小结
2016/05/16 Javascript
JavaScript实现的搜索及高亮显示功能示例
2017/08/14 Javascript
JavaScript学习笔记之惰性函数示例详解
2017/08/27 Javascript
jquery插件开发之选项卡制作详解
2017/08/30 jQuery
Nodejs实现爬虫抓取数据实例解析
2018/07/05 NodeJs
JavaScript原型链与继承操作实例总结
2018/08/24 Javascript
vue element中axios下载文件(后端Python)
2019/05/10 Javascript
解决vue打包后刷新页面报错:Unexpected token
2019/08/27 Javascript
Js代码中的span拼接问题解决
2019/11/22 Javascript
机器学习10大经典算法详解
2017/12/07 Python
python2.7实现爬虫网页数据
2018/05/25 Python
python爬虫 urllib模块发起post请求过程解析
2019/08/20 Python
Python切割图片成九宫格的示例代码
2020/03/10 Python
通过Python扫描代码关键字并进行预警的实现方法
2020/05/24 Python
逼真的HTML5树叶飘落动画
2016/03/01 HTML / CSS
First Aid Beauty官网:FAB急救面霜
2018/05/24 全球购物
下面代码从性能上考虑,有什么问题
2015/04/03 面试题
趣味运动会活动方案
2014/02/12 职场文书
大学生求职工作的自我评价
2014/02/13 职场文书
节约能源标语
2014/06/17 职场文书
“四风”查摆问题自我剖析材料
2014/09/27 职场文书
2014年安全保卫工作总结
2014/11/13 职场文书
同意落户证明
2015/06/19 职场文书
Java实现房屋出租系统详解
2021/10/05 Java/Android
SQL Server表分区降低运维和维护成本
2022/04/08 SQL Server