python-xpath爬取mooc网并生成词云

需求:

  • 爬取的链接: http://www.imooc.com/course/list
  • 爬取的内容: 课程链接, 课程的图片url, 课程的名称, 学习人数, 课程描述;
  • 爬取的内容如何存储:
    • 文件(.csv, );
    • mysql数据库;
  • 分析爬取的信息;
    • 词云

1 获取页面内容

import re

import requests
import lxml.etree as etree
import csv


def get_content(url):
    """爬取页面内容的函数"""

    try:
        user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36"
        response = requests.get(url, headers={'User-Agent': user_agent})
        response.raise_for_status()  # 如果返回的状态码不是200, 则抛出异常;
        response.encoding = response.apparent_encoding  # 判断网页的编码格式, 便于respons.text知道如何解码;
    except Exception as e:
        print("爬取错误")
    else:

        print(response.url)
        print("爬取成功!")
        return response.content

2.解析页面

发现我们需要的这个div块class=“course-card-container”
在这里插入图片描述
课程名称存储在h3里class=“course-card-name”,所以我们只需要获取这里的标签内容,其他的同理
在这里插入图片描述

def parser_content(html):
    """分析页面获取需要的信息:课程链接, 课程的图片url, 课程的名称, 学习人数, 课程描述 """
    # 1). 将html内容转化成xpath可以解析/匹配的格式;
    selector = etree.HTML(html)

    # 2). 获取每个课程的信息: <div class="course-card-container">
    courseDetails = selector.xpath('//div[@class="course-card-container"]')

    courseInfos = []
    for courseDetail in courseDetails:
        # 课程的名称: <h3 class="course-card-name">初识HTML+CSS</h3>
        name = courseDetail.xpath('.//h3[@class="course-card-name"]/text()')[0]

        # 学习人数
        """
        <div class="course-card-info">
					<span>入门</span><span><i class="icon-set_sns"></i>1000167</span>
				</div>
        """
        studentNum = courseDetail.xpath('.//span/text()')[1]

        # 课程描述: <p class="course-card-desc">HTML+CSS基础教程8小时带领大家步步深入学习标签用法和意义</p>
        courseInfo = courseDetail.xpath(".//p[@class='course-card-desc']/text()")[0]
        # print(name, studentNum, courseInfo)

        # 课程链接, h获取/learn/9 ====》 http://www.imooc.com/learn/9
        # <a target="_blank" href="/learn/9" class="course-card">
        courseUrl = "http://www.imooc.com" + courseDetail.xpath('.//a/@href')[0]
        # print(courseUrl)

        # 课程的图片url:
        """
        <img class="course-banner lazy" data-original="//img1.mukewang.com/529dc3380001379906000338-240-135.jpg" 
        src="//img1.mukewang.com/529dc3380001379906000338-240-135.jpg" style="display: inline;">
        """
        courseImgUrl = 'http:' + courseDetail.xpath('.//img/@src')[0]

        courseInfos.append((name, studentNum, courseInfo, courseUrl, courseImgUrl))

    return courseInfos

3.保存为csv格式

def save_csv(courseInfo):
    """将获取的课程信息保存为csv格式"""

    with open('doc/mooc.csv', 'w') as f:
        writer = csv.writer(f)
        writer.writerows(courseInfo)
    print("csv文件保存成功........")

4.保存为json格式

def save_json(courseInfo):
    """将获取的信息保存为json格式"""
    import json
    with open('doc/mooc.json', 'w', encoding='utf-8') as f:
        for item in courseInfo:
            item = {
                'name': item[0],
                'studentNum': item[1],
                'courseInfo': item[2],
                'courseUrl': item[3],
                'courseImgUrl': item[4]
            }
            # ensure_ascii: 如果有中文, 则设置为False, 表示使用Unicode编码, 中文不会乱码;
            #  indent=4: 所金为4个空格, 便于阅读;
            # json.dumps将字典格式转为json格式
            jsonitem = json.dumps(item, ensure_ascii=False, indent=4)
            f.write(jsonitem + '\n')
    print("json文件保存成功......")

这里,先测试第一页是否成功:

if __name__ == '__main__':
    url = "http://www.imooc.com/course/list"
    html = get_content(url=url)
    courseInfos = parser_content(html)  # 列表, 保存第一页的课程信息;
    save_csv(courseInfos)
    save_json(courseInfos)

输出:
在这里插入图片描述
csv文件
在这里插入图片描述

json文件
在这里插入图片描述

5.翻页下载功能

先来分析以下html这里把鼠标放在下一页处:

我先所在页数为28,这一页的url地址是http://www.imooc.com/course/list?page=28
再来看html代码,a标签中的href中自带了/course/list?page=29,和url地址中的尾部一样,所以我们尝试http://www.imooc.com/course/list?page=29,发现点开就是29页的内容。所以我们可以得出28页a标签里的内容,加上http://www.imooc.com/就是下一页的网址。
在这里插入图片描述
现在有个问题是到了最后一页翻不动时的情况:
我们到最后一页的网址时发现最后一页下一页的按钮不能点击,再来分析以下页面,发现没有href网址
在这里插入图片描述

def moocSpider():
    # 1). 爬取课程信息的第一页
    url = "http://www.imooc.com/course/list"
    html = get_content(url=url)
    courseInfos = parser_content(html)  # 列表, 保存第一页的课程信息;
    # 2). 如果有下一页信息, 则继续爬取课程内容;
    #     如果没有下一页信息, 则跳出循环, 将课程信息保存到文件中.....;
    #
    while True:
        # 获取是否拥有下一页?
        selector = etree.HTML(html)
        nextPage = selector.xpath('//a[contains(text(), "下一页")]/@href')
        # 模糊查找是否 a标签中含有“下一页“的href
        print(nextPage)
        # 只爬取前2页, 用于测试;
        # if nextPage and ('3' not in nextPage[0]):
        if nextPage:
            url = "http://www.imooc.com" + nextPage[0]
            html = get_content(url=url)  # 获取页面信息
            otherCourseInfo = parser_content(html) # 解析页面内容
            courseInfos += otherCourseInfo  # 把其他页获取的页面信息追加到变量中;
        else:
            print("全部爬取结束......")
            break

    # print(courseInfos)
    # 在这里保存一下
    save_csv(courseInfos) 
    save_json(courseInfos)

# #  1). 课程信息有多页, url规则:
# """
# 两种url均可:
# http://www.imooc.com/course/list?page=28
# http://www.imooc.com/course/list?page=1
#
# http://www.imooc.com/course/list/2
# http://www.imooc.com/course/list/28
# """
#
# # 2).  什么时候爬取结束? 没有下一页的时候
# """
# # 有下一页:
#     <a href="/course/list/2?page=2">下一页</a>
#
# # 没有下一页:
#     <span class="disabled_page">下一页</span>

测试一下是否爬取了所有页面内容

if __name__ == '__main__':
    # 爬取数据信息
    moocSpider()

在这里插入图片描述在这里插入图片描述在这里插入图片描述

6.清洗数据

为了分析出最火的课程,并且保存成词云图片,清洗数据必不可少。需要过滤掉标点数字

def dealCourseData(filename):
    """对于爬取的课程信息进行分析, 返回清洗好的数据"""
    #
    wordcloudString = ''
    # 读取需要的文件内容
    with open(filename) as f:
        reader = csv.reader(f)
        # 清洗需要分析的文本信息: 删除里面不必要的逗号, 句号, 表情;
        # [\u4e00-\u9fa5]+ 任意个中文 |[a-zA-Z0-9]+ 或者任意个英文、数字
        pattern = re.compile(r'([\u4e00-\u9fa5]+|[a-zA-Z0-9]+)')
        for item in reader:
            # 将来进行词云展示时, 需要的是字符串, 而不是列表;
            name = "".join(re.findall(pattern, item[0]))
            detail = "".join(re.findall(pattern, item[2]))
            wordcloudString += name
            wordcloudString += detail
        #  sub进行了替换,因为后面生成的词云不需要这些词语
        return  re.sub(r'(学习|使用|入门|基础|实现|掌握)', '', wordcloudString)

测试:

if __name__ == '__main__':
    text = dealCourseData('doc/mooc.csv')
    print(text)

输出:
在这里插入图片描述发现已经清洗掉了标点等

7.切割词语

下载以下模块
jieba(结巴):切割中文的模块;
wordcloud:
pillow: python3中专门用来处理图像的模块;
numpy:
matplotlib:

实现处理英文的词云比较简单
英文例子:复制一个passwd文件到tmp下,复制很多次root这一行,再复制少量saslauth这一行生成词云图片。
在这里插入图片描述在这里插入图片描述

另外,我们想生成爱心形状的词云。如果不指定自动生成矩形
在这里插入图片描述

import re

import jieba
from PIL import Image
from wordcloud import wordcloud
import numpy as np

# 1). 切割和处理英文字符,
data = []
with open('/tmp/passwd') as f:
    for line in f:
        # 以空格、冒号、/分割每个单词
        result1 = re.split(r'\s|:|/', line)
        # 如果item存在数据并且不是空格或者数字, 则继续进行处理;
        result2 = [item for item in result1 if not re.findall(r'\s+|\d+', item) and item]
        # print(result2)
        data.extend(result2)


# 2). 打开图片, 获取图片的数据信息(词云的形状就是由这个图片中有颜色的部分决定);
imgObj = Image.open('./doc/wordcloud.jpg')
img_mask = np.array(imgObj)
# print(img_mask)

# 3). 创建词云对象, 设置属性
wcObj = wordcloud.WordCloud(
    mask = img_mask,
    background_color="snow",
    min_font_size=5, #最小字体
    max_font_size=50,
    width=1000, # 指定图片的话以指定图片的大小为长宽
    height=1000,
    )
# 4). 生成图片;
# 词云绘制时, 默认之处理字符串类型, 怎么分隔每个单词? 必须以逗号分隔符分割
wcObj.generate(",".join(data))
wcObj.to_file('doc/wcObj.png')

输出:

在这里插入图片描述

中文例子:需要下载模块jieba

import re
import jieba
from PIL import Image
from wordcloud import wordcloud
import numpy as np

def gen_wordcloud(text, filename):


    # 1). 强调分割中有问题的词;
    jieba.suggest_freq(('微博'), True)
    jieba.suggest_freq(('热搜'), True)

    #  2). 难点: 如何切割中文, jieba, lcut
    result = jieba.lcut(text)
    print(result)

    # 绘制词云
    # 3). 打开图片, 获取图片的数据信息;
    imgObj = Image.open('./doc/wordcloud.jpg')
    img_mask = np.array(imgObj)
    # print(img_mask)
    # 4). 创建词云对象, 设置属性
    wcObj = wordcloud.WordCloud(
        mask = img_mask,   # 数据如何填充到图片
        background_color="snow",  # 北京颜色
        font_path="/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc",  # 如果是中文, 指定字体库(fc-list :lang=zh)
        min_font_size=5,  # 图片中最小的字体大小
        max_font_size=50,   # 图片中最小的字体大小
        width=1000,  # 图片宽度
        height=1000, # 高
        )
    # 5). 生成图片;
    # 词云绘制时, 默认之处理字符串类型, 怎么分隔每个单词? 必须以逗号分隔符分割
    wcObj.generate(",".join(result))
    wcObj.to_file(filename)


if __name__ == '__main__':
    text = "马云曾公开表态称对钱没兴趣称其从来没碰过钱上了微博热搜"
    filename = 'doc/wcObj.png'
    gen_wordcloud(text, filename)

在这里插入图片描述

同样的原理我们回到这个mooc网爬虫上,

import re
import jieba
from PIL import Image
from wordcloud import wordcloud
import numpy as np


def gen_wordcloud(text, filename):

    #  2). 难点: 如何切割中文, jieba, lcut
    result = jieba.lcut(text)
    # print(result)

    # 绘制词云
    # 3). 打开图片, 获取图片的数据信息;
    imgObj = Image.open('./doc/wordcloud.jpg')
    img_mask = np.array(imgObj)
    # print(img_mask)
    # 4). 创建词云对象, 设置属性
    wcObj = wordcloud.WordCloud(
        mask=img_mask,  # 数据如何填充到图片
        background_color="snow",  # 背景颜色
        font_path="/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc",  # 如果是中文, 指定字体库(fc-list :lang=zh)
        min_font_size=5,  # 图片中最小的字体大小
        max_font_size=50,  # 图片中最小的字体大小
        width=1000,  # 图片宽度
        height=1000,  # 高
    )
    # 5). 生成图片;
    # 词云绘制时, 默认之处理字符串类型, 怎么分隔每个单词? 必须以逗号分隔符分割
    wcObj.generate(",".join(result))
    wcObj.to_file(filename)
    print("生成图片%s成功......." %(filename))

if __name__ == '__main__':
    # 爬取数据信息
    moocSpider()
    # 分析爬取的数据
    text = dealCourseData('doc/mooc.csv')
    print(text)
    filename = "doc/mooc.png"
    gen_wordcloud(text, filename)

最终得到词云:
在这里插入图片描述
完整代码:

import re

import requests
import lxml.etree as etree
import csv


def get_content(url):
    """爬取页面内容的函数"""

    try:
        user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36"
        response = requests.get(url, headers={'User-Agent': user_agent})
        response.raise_for_status()  # 如果返回的状态码不是200, 则抛出异常;
        response.encoding = response.apparent_encoding  # 判断网页的编码格式, 便于respons.text知道如何解码;
    except Exception as e:
        print("爬取错误")
    else:

        print(response.url)
        print("爬取成功!")
        return response.content


def parser_content(html):
    """分析页面获取需要的信息:课程链接, 课程的图片url, 课程的名称, 学习人数, 课程描述 """
    # 1). 将html内容转化成xpath可以解析/匹配的格式;
    selector = etree.HTML(html)

    # 2). 获取每个课程的信息: <div class="course-card-container">
    courseDetails = selector.xpath('//div[@class="course-card-container"]')

    courseInfos = []
    for courseDetail in courseDetails:
        # 课程的名称: <h3 class="course-card-name">初识HTML+CSS</h3>
        name = courseDetail.xpath('.//h3[@class="course-card-name"]/text()')[0]

        # 学习人数
        """
        <div class="course-card-info">
					<span>入门</span><span><i class="icon-set_sns"></i>1000167</span>
				</div>
        """
        studentNum = courseDetail.xpath('.//span/text()')[1]

        # 课程描述: <p class="course-card-desc">HTML+CSS基础教程8小时带领大家步步深入学习标签用法和意义</p>
        courseInfo = courseDetail.xpath(".//p[@class='course-card-desc']/text()")[0]
        # print(name, studentNum, courseInfo)

        # 课程链接, h获取/learn/9 ====》 http://www.imooc.com/learn/9
        # <a target="_blank" href="/learn/9" class="course-card">
        courseUrl = "http://www.imooc.com" + courseDetail.xpath('.//a/@href')[0]
        # print(courseUrl)

        # 课程的图片url:
        """
        <img class="course-banner lazy" data-original="//img1.mukewang.com/529dc3380001379906000338-240-135.jpg" 
        src="//img1.mukewang.com/529dc3380001379906000338-240-135.jpg" style="display: inline;">
        """
        courseImgUrl = 'http:' + courseDetail.xpath('.//img/@src')[0]

        courseInfos.append((name, studentNum, courseInfo, courseUrl, courseImgUrl))

    return courseInfos


def save_csv(courseInfo):
    """将获取的课程信息保存为csv格式"""

    with open('doc/mooc.csv', 'w') as f:
        writer = csv.writer(f)
        writer.writerows(courseInfo)
    print("csv文件保存成功........")


def save_json(courseInfo):
    """将获取的信息保存为json格式"""
    import json
    with open('doc/mooc.json', 'w', encoding='utf-8') as f:
        for item in courseInfo:
            item = {
                'name': item[0],
                'studentNum': item[1],
                'courseInfo': item[2],
                'courseUrl': item[3],
                'courseImgUrl': item[4]
            }
            # ensure_ascii: 如果有中文, 则设置为False, 表示使用Unicode编码, 中文不会乱码;
            #  indent=4: 所金为4个空格, 便于阅读;
            # json.dumps将字典格式转为json格式
            jsonitem = json.dumps(item, ensure_ascii=False, indent=4)
            f.write(jsonitem + '\n')
    print("json文件保存成功......")

# if __name__ == '__main__':
#     url = "http://www.imooc.com/course/list"
#     html = get_content(url=url)
#     courseInfos = parser_content(html)  # 列表, 保存第一页的课程信息;
#     save_csv(courseInfos)
#     save_json(courseInfos)


def moocSpider():
    # 1). 爬取课程信息的第一页
    url = "http://www.imooc.com/course/list"
    html = get_content(url=url)
    courseInfos = parser_content(html)  # 列表, 保存第一页的课程信息;
    # 2). 如果有下一页信息, 则继续爬取课程内容;
    #     如果没有下一页信息, 则跳出循环, 将课程信息保存到文件中.....;
    #
    while True:
        # 获取是否拥有下一页?
        selector = etree.HTML(html)
        nextPage = selector.xpath('//a[contains(text(), "下一页")]/@href')
        # 模糊查找是否a标签中含有下一页
        print(nextPage)
        # 只爬取前2页, 用于测试;
        # if nextPage and ('3' not in nextPage[0]):
        if nextPage:
            url = "http://www.imooc.com" + nextPage[0]
            html = get_content(url=url)  # 获取页面信息
            otherCourseInfo = parser_content(html) # 解析页面内容
            courseInfos += otherCourseInfo  # 把其他页获取的页面信息追加到变量中;
        else:
            print("全部爬取结束......")
            break

    # print(courseInfos)
    save_csv(courseInfos)
    save_json(courseInfos)

    # #  1). 课程信息有多页, url规则:
    # """
    # 两种url均可:
    # http://www.imooc.com/course/list?page=28
    # http://www.imooc.com/course/list?page=1
    #
    # http://www.imooc.com/course/list/2
    # http://www.imooc.com/course/list/28
    # """
    #
    # # 2).  什么时候爬取结束? 没有下一页的时候
    # """
    # # 有下一页:
    #     <a href="/course/list/2?page=2">下一页</a>
    #
    # # 没有下一页:
    #     <span class="disabled_page">下一页</span>
    #
    # """
    #
    #

#
def dealCourseData(filename):
    """对于爬取的课程信息进行分析, 返回清洗好的数据"""
    #
    wordcloudString = ''
    # 读取需要的文件内容
    with open(filename) as f:
        reader = csv.reader(f)
        # 清洗需要分析的文本信息: 删除里面不必要的逗号, 句号, 表情;
        # [\u4e00-\u9fa5]+ 任意个中文 |[a-zA-Z0-9]+ 或者任意个英文、数字
        pattern = re.compile(r'([\u4e00-\u9fa5]+|[a-zA-Z0-9]+)')
        for item in reader:
            # 将来进行词云展示时, 需要的是字符串, 而不是列表;
            name = "".join(re.findall(pattern, item[0]))
            detail = "".join(re.findall(pattern, item[2]))
            wordcloudString += name
            wordcloudString += detail
        #  sub进行了替换,因为后面生成的词云不需要这些词语
        return  re.sub(r'(学习|使用|入门|基础|实现|掌握)', '', wordcloudString)
#
#
import re
import jieba
from PIL import Image
from wordcloud import wordcloud
import numpy as np


def gen_wordcloud(text, filename):
    # 1). 强调分割中有问题的词;
    # jieba.suggest_freq(('微博'), True)
    # jieba.suggest_freq(('热搜'), True)

    #  2). 难点: 如何切割中文, jieba, lcut
    result = jieba.lcut(text)
    # print(result)

    # 绘制词云
    # 3). 打开图片, 获取图片的数据信息;
    imgObj = Image.open('./doc/wordcloud.jpg')
    img_mask = np.array(imgObj)
    # print(img_mask)
    # 4). 创建词云对象, 设置属性
    wcObj = wordcloud.WordCloud(
        mask=img_mask,  # 数据如何填充到图片
        background_color="snow",  # 北京颜色
        font_path="/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc",  # 如果是中文, 指定字体库(fc-list :lang=zh)
        min_font_size=5,  # 图片中最小的字体大小
        max_font_size=50,  # 图片中最小的字体大小
        width=1000,  # 图片宽度
        height=1000,  # 高
    )
    # 5). 生成图片;
    # 词云绘制时, 默认之处理字符串类型, 怎么分隔每个单词? 必须以逗号分隔符分割
    wcObj.generate(",".join(result))
    wcObj.to_file(filename)
    print("生成图片%s成功......." %(filename))


if __name__ == '__main__':
    # 爬取数据信息
    moocSpider()

    # 分析爬取的数据
    text = dealCourseData('doc/mooc.csv')
    print(text)
    filename = "doc/mooc.png"
    gen_wordcloud(text, filename)

猜你喜欢

转载自blog.csdn.net/weixin_43067754/article/details/87802756