如何爬取中国大学MOOC上的课程信息

因为最近需要做一个关于课程类的项目,但苦于没有相关课程的信息及简介。当我在看MOOC上面的Python爬虫课程时,突然想到MOOC上面的课程信息如此完善,我为何不利用下MOOC上面的课程信息呢,说干就干,我随便在MOOC上找到了一页课程信息后,便决定使用Python的requests库去获取课程信息了。课程信息如下


image.png

一、测试代码如下

import requests
print(requests.get("https://www.icourse163.org/category/computer").text)

很容易的就得到了该页面的源代码


image.png

本以为拿到了源代码但可以随便的提取数据了,但是我在下载下来的源代码中找了很久没有发现页面正常显示的时的课程信息。如上面的课程截图有C语言,我搜索了下C语言,可是没有在源代码中搜索到。


image.png

当我仔细看的查看课程列表页面的源代码的时候我才发现原来MOOC上面的课程列表信息是通过js加载的数据,js需要浏览器才能加载,普通的请求只能拿到渲染前的源代码,所以在源代码里面没有找到相关的课程信息。发现问题之后就好办了,既然需要浏览器加载js来渲染数据,那我们就给它一个浏览器然他渲染之后再去拿数据就是了。在python当中,可以使用selenium去模拟各种各样的浏览器,如chrome,safari,firefox。甚至是手机上的浏览器。selenium+phantomjs便是一个无头浏览器。它俩的结合便可以达到更好的数据采集效率。不过在python中需要安装下selenium,至于phantomjs到官网去下载一个就是了。

安装 selenium

pip3 install selenium

下载phantomjs


image.png

这两个工具安装好之后,便可以直接使用了,现在来试试去获取课程列表的那个页面,看看能不能加载出数据。

# -*- coding: UTF-8 -*-
from selenium import webdriver
chrome=webdriver.PhantomJS(executable_path="E:\phantomJs\phantomJs.exe")
chrome.get("https://www.icourse163.org/category/computer")
print(chrome.page_source)
# executable_path为你下载phantomjs的地址
# page_source为当前页面的源代码

于是现在源代码中便有了课程列表的信息


image.png

有了课表列表页面的源代码后,我们就可以提取课程的Url地址,提取出Url地址后,就可以进行课表的详情页面,然后就可以随便拿课程的详细信息了。

二、解析课程列表页面的课表url地址
在python中有许多的解析网页源代码的库,如正则表达式、beautifulsoup、pyquery等,通过运行这些解析库,可以让我们更加方便的提取我们自己想要的数据。本次将使用pyquery库,pyquery库同样需要安装。
安装pyquery库

pip3 install pyquery

安装好之后我们就可以解析源代码中的数据了。通过检查元素发现所有课程简介信息都位于id为j-courseCardListBox为的div下面,


image.png

再展开标签后便可发现所有的课程链接都位于a标签下。


image.png

发现这个规律后,我们便可以使用pyquery定位到id为j-courseCardListBox为的div中,再在该div中找出所有a标签并取其href的值,便可以拿到本页面的所有课程的详细信息的链接地址了。有了思路后,代码就比较好写了。
# -*- coding: UTF-8 -*-
from selenium import webdriver
from pyquery import PyQuery as pq
chrome=webdriver.PhantomJS(executable_path="E:\phantomJs\phantomJs.exe")
chrome.get("https://www.icourse163.org/category/computer")
code=pq(chrome.page_source)
href = code("#j-courseCardListBox a")
for i in href:
    print(str(code(i).attr("href")))

结果如下:


image.png

发现结果中包含一些其它的信息的,经测试发现类似于"//www.icourse163.org/course/XJTU-46006"这样的地址信息,才是课程的地址信息。所以在上面的代码中加了一些判断逻辑来去除其它的信息。

# -*- coding: UTF-8 -*-
from selenium import webdriver
from pyquery import PyQuery as pq
chrome=webdriver.PhantomJS(executable_path="E:\phantomJs\phantomJs.exe")
chrome.get("https://www.icourse163.org/category/computer")
code=pq(chrome.page_source)
href = code("#j-courseCardListBox a")
urlList=[]
for i in href:
     temp = "http:" +str(code(i).attr("href"))
     if temp.__contains__("www") and not temp.__contains__("https"):
         print(temp)
         urlList.append(temp)
         
urlList=list(set(urlList)) # 去除重复的url地址

结果如下:


image.png

经过简单的处理之后便提取出了当前页面的课程详细信息页面的url链接地址,是不是很容易呢,果然是,很容易。提取一个页面的课程详细信息页面的url链接地址并不是我想要的最终结果,我的最终目标是拿到所有课程的详细信息,所以就需要提取出所有课程详细信息的url地址。要拿到所有课程的链接,只需要实现翻页就可以了,翻一页拿一页的课程详细信息的链接地址。要实现翻页爬取,只需要知道当前的课程的信息共有多少页,就可以使用一个循环,每循环一次翻页一次就可以了。但是发现总页数和其分页的样式都差不多一样,不是很方便提取总页数的信息。


image.png

经过我反复的实验后发现当页面位于最后一页时,下一页面的标签样式会有变化,当在非最后一页的时候,下一页的标签样式为


image.png

当在最后一页的时候,下一页的标签样式为
image.png

通过对比发现这两个标签的样式不一样,那我是不是就可以通过标签的样式来判断当前页面是否为最后一页了,有了翻页的思路之后就可以代码来实现了,用一个while循环来翻页,在循环内中判断当前页面的下一页的标签样式是否为当当前页面为最后一页的标签样式。如果是可以跳出循环了,至于提取单页面的课程详细信息的链接前面就已经实现了,下面是具体的代码实现

  chrome = webdriver.PhantomJS(executable_path="E:\phantomJs\phantomJs.exe")
    webdriver.PhantomJS()
    chrome.get(pageSourceUrl) # 打开课程列表页面
    allUrl=[] # 用于存放获取到的课程详细信息的url地址
    while (True):
        allUrl=chain(allUrl,getPageUrl(chrome.page_source)) # chain是为了将多个列表转换为一个列表,getPageUrl()为前面实现的获取单页面的课程详细信息的url地址
        chrome.find_element_by_link_text("下一页").click() # 单击下一页标签实现翻页
        time.sleep(3) # 让程序等待3秒钟,因为无头浏览器加载页面需要一点时间,为了保险起见,所以设置的比较长
        if (chrome.find_element_by_class_name("ux-pager_btn__next").get_attribute("class") == "ux-pager_btn ux-pager_btn__next z-dis"):
            allUrl = chain(allUrl, getPageUrl(chrome.page_source))
            break
     # if是判断当前页面是否为最后一页,如果是则获取最后一页的课程详细信息的url地址,再跳出循环
    chrome.quit()

部分结果如下:


image.png

通过以上的两个步骤就基本可以拿到课程详细信息的页面的url地址了,拿到这些地址之后我们就可以爬去每个课程的课程详细信息了。

三、拿到课程的详细信息
要拿到课程的详细信息无非就三个步骤,1、拿到其详细页面的url地址,2、下载其页面的源代码,3、提取想要的数据。第一步已经做到了,我们就可以直接开始做第二、三步了,下面随机选了一个课程详细信息的页面来做测试。我们想从该页面中提取以下信息,
1、课程名称
2、课程概述
3、授课教师
4、课程大纲


image.png

由于其它信息不方便截取,所以就没有截图说明了,经过反复的试验发现提取这些信息还是比较容易,因为他们都有样式名称,我们可以通过样式名称来直接定位,再提取信息就是了。


image.png
import requests
from pyquery import PyQuery as pq
info=pq(requests.get("https://www.icourse163.org/course/TONGJI-89002").text)
print(info(".course-title.f-ib.f-vam").text()) # 课程名称
t = info(".f-richEditorText") 
print(info(".cnt.f-fl").text().replace("\n", " ")) # 教师信息
print(info(t[0]).text()) # 课程简介
print(info(t[1]).text()) # 课程大纲
print(info(t[2]).text()) # 先修课程

结果如下:


image.png

四、使用redis存取拿到的所有课程详细信息
需要到redis的官网下载一个redis并安装,再下载一个redis可视化管理工具,并可以对redis进行管理了,当然也可以直接使用命令行。
在python中使用redis需要安装redis库才行,
安装redis库

pip3 install redis

安装好之后就可以直接使用redis了。

import redis
r=redis.from_url("http://127.0.0.1:6379")
r.set("test","123")
print(r.get("test"))

结果如下:


image.png

因为爬取的课程信息是由许多的数据项构成,所以我们在存储的时候可以使用hashMap。在python中即r.hset(),r.hget()。
我将上面的所有步骤都分别写成了具体的函数,下面是本次爬虫中的所有代码。

# -*- coding: UTF-8 -*-
from selenium import webdriver
from pyquery import PyQuery as pq
import time
import redis
import chardet
import requests
from itertools import chain
r = redis.from_url("http://127.0.0.1:6379")
def getPageUrl(pageSource):
    code = pq(pageSource)
    href = code("#j-courseCardListBox a")
    urlList = []
    for i in href:
        temp = str(code(i).attr("href"))
        if temp.__contains__("www") and not temp.__contains__("https"):
            urlList.append("http:" + temp)
    urlList = list(set(urlList))
    return urlList

def getAllUrl(pageSourceUrl):
    chrome = webdriver.PhantomJS(executable_path="E:\phantomJs\phantomJs.exe")
    webdriver.PhantomJS()
    chrome.get(pageSourceUrl)
    allUrl=[]
    count=1
    while (True):
        allUrl=chain(allUrl,getPageUrl(chrome.page_source))
        print(count)
        print(chrome.find_element_by_class_name("ux-pager_btn__next").get_attribute("class"))
        chrome.find_element_by_link_text("下一页").click()
        time.sleep(3)
        if (chrome.find_element_by_class_name("ux-pager_btn__next").get_attribute("class") == "ux-pager_btn ux-pager_btn__next z-dis"):
            allUrl = chain(allUrl, getPageUrl(chrome.page_source))
            print(count)
            break
        count+=1
    chrome.quit()
    return allUrl

def saveCourseInfoes(courseUrlList=[]):
    count,index=0,0
    errorList=[]
    while(count<courseUrlList.__len__()):
        info = pq(requests.get(courseUrlList[count]).text)
        print(info(".course-title.f-ib.f-vam").text())
        t = info(".f-richEditorText")
        if (len(t) >= 3):
            print(info(".cnt.f-fl").text().replace("\n", " "))
            r.hset("CourseInfo", index, {
                "courseName": info(".course-title.f-ib.f-vam").text(),
                "teacherInfo": info(".cnt.f-fl").text().replace("\n", " "),
                "anOverviewOfTheCourse": info(t[0]).text(),
                "teachingObjectives": info(t[1]).text(),
                "syllabus": info(t[2]).text()
            })
            index+=1
        else:
            errorList.append(courseUrlList[count])
        count+=1
    return errorList


timeStart=time.time()
allUrl=getAllUrl("https://www.icourse163.org/category/all")
errorList=saveCourseInfoes(list(allUrl))
print("\n\n")
for i in errorList:
    print(i)
print("共耗时:",end=" ")
print(time.time()-timeStart)

结果如下:


image.png

以上就是本次爬虫的所有内容了,总爬到了1111门课程信息,不知道是否有重复的内容存在。总结一下本次爬虫用到的一些库和工具。
1、python
2、selenium+phantomjs
3、requests
4、pyquery
5、redis
这些库和工具的确比较方便,不过有时候需要具体问题具体分析,这样才能更好的发挥它们的作用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,952评论 25 707
  • 有一个爱倒立的小女孩,她叫米奇,她倒立着爬山,倒立着上楼梯,甚至倒立着吃饭。 一天,她的爸爸和妈妈觉得她倒立有点不...
    晨光微晓阅读 225评论 0 0
  • 昨天晚上孩子订正月考作文,我想看看,她不让看,满分50分,得37分,还以为是考少了不让看,当时有点气氛想抢过来看,...
    幸福驾到阅读 258评论 0 4
  • 如果说陪伴是最长情的告白那,等待一定是最极致的思念吧 五个月匆匆而过,转眼间,我们已经分离了近半年了,近来,天气转...
    Maries阅读 313评论 0 0