Python爬虫程序员我爱编程

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

2018-02-04  本文已影响2378人  ZeroForSpider

因为最近需要做一个关于课程类的项目,但苦于没有相关课程的信息及简介。当我在看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
这些库和工具的确比较方便,不过有时候需要具体问题具体分析,这样才能更好的发挥它们的作用。

上一篇下一篇

猜你喜欢

热点阅读