欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-10-19 10:25:59
有时候我们需要把一些经典的东西收藏起来,时时回味,而coursera上的一些课程无疑就是经典之作。coursera中的大部分完结课程都提供了完整的配套教学资源,包括ppt,...

有时候我们需要把一些经典的东西收藏起来,时时回味,而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一段时间的访问次数,或者是限制两次操作的时间间隔,加入验证码等等。绝大多数情况下,我们不知道服务器端是如何防止爬虫的,所以要想让爬虫工作起来确实挺难的。