第5章 数据处理与可视化(5.1数据清洗与优化技巧,舆情评分)

  • 数据清洗与优化主要涉及常规的数据清洗、文本内容过滤、数据乱码问题处理等。

5.1.1 常用的数据清洗手段及日期格式的统一

  • 首先回顾一下常用的数据清洗手段,代码如下:
# 1.用strip()函数删除字符串首尾的空格、换行等空白字符
res = '  华能信托2019年实现利润42.11亿元,行业排名第三      '
res = res.strip() #删除上面字符串首尾的空格和换行
res

# 2.用split函数()拆分字符串,以截取需要的内容
date = '2022-4-2 14:27:10'
date = date.split(' ')[0] #提取年月日信息
date

# 3.用re库中的sub()函数进行内容替换
import re
title = '阿里<em>巴巴<em>人工智能再发力'
title = re.sub('<.*?>','',title) #清除形式为"<xxx>"的内容
title
  • 使用上述手段已能完成对标题、网址等信息的清洗和优化,下面在介绍一下如何对日期格式的统一。
  • 之前从百度新闻爬取的日期没有一个统一的格式。假设以“2022-4-2”作为标准格式,那么对于格式类似“2022-4-2 14:32”的日期,可以用split()函数来处理。但是百度新闻里还有很多其他的日期格式,如“2022年4月2日”“4小时前”“56分钟前”。其处理办法也不复杂:对于类似“2022年4月2日”的情况,可以将“年”和“月”替换为“-”,将“日”替换为空白字符:对于类似“4小时前”和“56分钟前”的情况,可以将包含“小时”或“分钟”的日期都替换成当天的日期。具体代码如下:
import time
for i in range(len(title)):
    date[i] = date[i].split(' ')[0]# 处理带时分秒的日期
    date[i] = re.sub('年','-',date[i]) #将“年”替换为“-”
    date[i] = re.sub('月','-',date[i]) #将“月”替换为“-”
    date[i] = re.sub('日','',date[i]) #将“日”替换为空字符串
    if ('小时' in date[i] ) or ('分钟' in date[i]):
        date[i] = time.strftime('%Y-%m-%d') #替换为当天的日期     
  • 第7行代码用运算符in判断当前日期是否包含“小时”或“分钟”。第8行代码用time.strftime(’%Y-%m-%d’)获得标准格式的今天的日期,用于替换当前日期。如果想要更加简洁,可以把这几行代码写到最初的for i in range(len(title))循环语句里。

5.1.2 文本内容过滤——剔除噪声数据

  • 来自网络的数据经常会包含一些噪声数据。例如,搜索“红豆集团”,搜索结果却包含“王菲经典歌曲红豆在登怀旧音乐榜首”,这显然不是我们想要的数据。那么如何将这些噪声数据过滤掉呢?这里介绍两种方式:一种是根据新闻标题进行简单过滤,另一种则是根据新闻正文内容进行深度过滤。此外,本节将介绍如何对新闻正文进行深度优化。

1.根据新闻标题简单过滤

  • 这种方式的思路是观察所爬取的公司名称是否还在新闻标题里,如果没有,那么就删除该标题以及相关的网址、日期、来源。核心代码如下:
# 根据新闻标题简单过滤
for i in range(len(title)):
    if company not in title[i]:
        title[i] = ''
        href[i] = ''
        date[i] = ''
        source[i] = ''
while '' in title:
    title.remove('')
while '' in href:
    href.remove('')
while '' in date:
    date.remove('')
while '' in source:
    source.remove('')
  • 这里首先变量标题列表,如果公司名称不在标题里,则把相应的标题、网址、日期、来源都赋为空字符串,然后通过如下代码批量删除空字符串:
while '' in title:
    title.remove('')
  • 有的读者可能会问:为什么不直接在for循环语句中用del titile[i]的方式删除元素呢?这是因为for循环根据len(title)确定了循环次数后,循环次数就不会改变了,然而在for循环语句里删除列表元素后,比如原来有10个元素,删除后还有8个元素,那么此时的title[8]、title[9]就不存在,从而导致列表序号越界的错误(list index out of range)。因此,需要先赋值为空字符串,再用while循环来批量处理。
  • 除了用上面的方法进行处理,还可以构建新列表来存储符合要求的内容,代码如下:
title_new = []
href_new = []
date_new = []
source_new = []
for i in range(len(title)):
    if companys in title[i]:
        title_new.append(title[i])
        href_new.append(href[i])
        date_new.append(date[i])
        source_new.append(soure[i])
  • 首先构造4个空列表,然后遍历每一个标题,如果公司名称在标题里,则把符合条件的信息用append()函数添加到行列表中。

2.根据新闻正文进行深度过滤(以爬取百度新闻为例)

  • 根据新闻标题来进行筛选,有时过于粗放。某些情况下,虽然标题里没有公司名称,但是新闻正文里有公司名称,这种新闻也是我们需要的。下面就来讲解如何根据新闻正文进行深度过滤。
  • 之前已经获取到每一条新闻的网址,那么通过下面这行代码就能爬取正文。其原理就是通过Requests库访问每一条新闻的网址,并获取网页源代码。
import re
import time
import requests
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36'}

def baidu(company):
    # 1.获取网页源代码
    url = 'https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=' + company  # 其中设置rtt=4则为按时间排序,如果rtt=1则为按焦点排序
    res = requests.get(url, headers=headers, timeout=10).text

    # 2.编写正则提炼内容
    #p_href = '<h3 class="news-title_1YtI1"><a href="(.*?)"'
    p_href = '<h3 class="news-title_1YtI1 "><a href="(.*?)" '
    href = re.findall(p_href, res)  # 不存在换行,无须添加re.S
    p_title = ' class="news-title-font_1xS-F" aria-label=.*? data-click=.*?<!--s-text-->(.*?)<!--/s-text--></a>'
    title = re.findall(p_title, res, re.S)
    p_date = '<span class="c-color-gray2 c-font-normal c-gap-right-xsmall" aria-label=.*?>(.*?)</span>'
    date = re.findall(p_date, res)
    p_source = '<span class="c-color-gray" aria-label=".*?">(.*?)</span>'
    source = re.findall(p_source, res)
    # 3.数据清洗
    for i in range(len(title)):
        title[i] = title[i].strip()  # strip()函数用来取消字符串两端的换行或者空格,不过目前(2020-10)并没有换行或空格,所以其实不写这一行也没事
        title[i] = re.sub('<.*?>', '', title[i])  # 核心,用re.sub()函数来替换不重要的内容
        # 统一日期格式
        date[i] = date[i].split(' ')[0]  # 提取2020年11月15日 10:26:22前面的年月日信息
        date[i] = re.sub('年', '-', date[i])  # 换成2020-11-15格式,注意只需要换年和月即可,日换成空值即可
        date[i] = re.sub('月', '-', date[i])
        date[i] = re.sub('日', '', date[i])
        if ('小时' in date[i]) or ('分钟' in date[i]):
            date[i] = time.strftime("%Y-%m-%d")
        else:
            date[i] = date[i]

    # 4.正文爬取及数据深度清洗
    for i in range(len(title)):
        # 获取新闻正文
        try:
            article = requests.get(href[i], headers=headers, timeout=10).text
        except:
            article = '单个新闻爬取失败'

        # 数据深度清洗
        company_re = company[0] + '.{0,5}' + company[-1]  # 匹配“公司名称第一个字 + 0到5个任意字符 + 公司最后一个字”这样的匹配规则
        if (len(re.findall(company_re, title[i])) < 1) and (len(re.findall(company_re, article)) < 1):
            title[i] = ''
            source[i] = ''
            href[i] = ''
            date[i] = ''
    while '' in title:
        title.remove('')
    while '' in href:
        href.remove('')
    while '' in date:
        date.remove('')
    while '' in source:
        source.remove('')

    # 5.打印清洗后的数据
    for i in range(len(title)):
        print(str(i + 1) + '.' + title[i] + '(' + date[i] + ' ' + source[i] + ')')
        print(href[i])


# 6.批量爬取多家公司
companys = ['华能信托', '阿里巴巴', '百度集团']
for i in companys:
    try:
        baidu(i)
        print(i + '数据爬取成功')
        time.sleep(3)
    except:
        print(i + '数据爬取失败')

“.{0,5}”是一个新知识点,“.”表示任意一个字符,“.{0,5}”则表示0~5个任意字符(千万注意这里的逗号后不能有空格)。re.findall(company_re, aritcle)表示寻找满足匹配规则company_re的字符串。如果能找到相关内容,返回的列表长度大于等于1;如果不能找到相关内容,返回的列表长度小于1,此时就可以将该新闻复制为空并清除。
通过上述判断条件就能较精准地过滤和筛选正文。例如,将公司名称设置为“华能信托”,就能筛选出正文含有“华能贵诚信托”的新闻。

3.新闻正文深度优化

  • 上面的正文捕捉还可以继续优化。通过article = requests.get(href[i]).text #获取新闻正文信息获得的是新闻详情页面的全部源代码,其中真正的新闻正文往往只占一小部分。例如,下图中的新闻(https://www.jiemian.com/article/2796459.html),正文其实很短,但是旁边的滚动新闻却不少。
    在这里插入图片描述
  • 如果这些滚动新闻中含有与目标公司无关的负面词,就会导致这条新闻被误判为负面新闻(其本身可能是正面新闻)。那么如何解决这个问题呢?2.3节讲过,段落内容通常是被<p></p>包围,而大部分网页的正文内容也被<p></p>包围,如下图所示。
    在这里插入图片描述
  • 再看滚动新闻在网页源代码中的结构,发现它不是被<p></p>包围,而是被<li></li>包围,如下图所示。
    在这里插入图片描述
  • 发现了这个规律后,便可以编写正则表达式提取所有被<p></p>包围的内容,再把获得的列表用’连接符’.join(列表名)的方式转换成字符串,得到新的article,核心代码如下:
p_article = '<p>(.*?)</p>'
article_main = re.findall(p_article,article) #提取<p>和</p>之间的正文内容
article = ' '.join(article_main) #将列表换成字符串,用空格连接
  • 完整代码如下:
import re
import time
import requests
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36'}

def baidu(company):
    # 1.获取网页源代码
    url = 'https://www.baidu.com/s?tn=news&rtt=4&bsst=1&cl=2&wd=' + company  # 其中设置rtt=4则为按时间排序,如果rtt=1则为按焦点排序
    res = requests.get(url, headers=headers, timeout=10).text

    # 2.编写正则提炼内容
    #p_href = '<h3 class="news-title_1YtI1"><a href="(.*?)"'
    p_href = '<h3 class="news-title_1YtI1 "><a href="(.*?)" '
    href = re.findall(p_href, res)  # 不存在换行,无须添加re.S
    p_title = ' class="news-title-font_1xS-F" aria-label=.*? data-click=.*?<!--s-text-->(.*?)<!--/s-text--></a>'
    title = re.findall(p_title, res, re.S)
    p_date = '<span class="c-color-gray2 c-font-normal c-gap-right-xsmall" aria-label=.*?>(.*?)</span>'
    date = re.findall(p_date, res)
    p_source = '<span class="c-color-gray" aria-label=".*?">(.*?)</span>'
    source = re.findall(p_source, res)
    # 3.数据清洗
    for i in range(len(title)):
        title[i] = title[i].strip()  # strip()函数用来取消字符串两端的换行或者空格,不过目前(2020-10)并没有换行或空格,所以其实不写这一行也没事
        title[i] = re.sub('<.*?>', '', title[i])  # 核心,用re.sub()函数来替换不重要的内容
        # 统一日期格式
        date[i] = date[i].split(' ')[0]  # 提取2020年11月15日 10:26:22前面的年月日信息
        date[i] = re.sub('年', '-', date[i])  # 换成2020-11-15格式,注意只需要换年和月即可,日换成空值即可
        date[i] = re.sub('月', '-', date[i])
        date[i] = re.sub('日', '', date[i])
        if ('小时' in date[i]) or ('分钟' in date[i]):
            date[i] = time.strftime("%Y-%m-%d")
        else:
            date[i] = date[i]

    # 4.正文爬取及数据深度清洗
    for i in range(len(title)):
        # 获取新闻正文
        try:
            article = requests.get(href[i], headers=headers, timeout=10).text
        except:
            article = '单个新闻爬取失败'

        # 正文信息再优化
        p_article = '<p>(.*?)</p>'
        article_main = re.findall(p_article,article) #提取<p>和</p>之间的正文内容
        article = ' '.join(article_main) #将列表换成字符串,用空格连接

        # 数据深度清洗
        company_re = company[0] + '.{0,5}' + company[-1]  # 匹配“公司名称第一个字 + 0到5个任意字符 + 公司最后一个字”这样的匹配规则
        if (len(re.findall(company_re, title[i])) < 1) and (len(re.findall(company_re, article)) < 1):
            title[i] = ''
            source[i] = ''
            href[i] = ''
            date[i] = ''
    while '' in title:
        title.remove('')
    while '' in href:
        href.remove('')
    while '' in date:
        date.remove('')
    while '' in source:
        source.remove('')

    # 5.打印清洗后的数据
    for i in range(len(title)):
        print(str(i + 1) + '.' + title[i] + '(' + date[i] + ' ' + source[i] + ')')
        print(href[i])


# 6.批量爬取多家公司
companys = ['华能信托', '阿里巴巴', '百度集团']
for i in companys:
    try:
        baidu(i)
        print(i + '数据爬取成功')
        time.sleep(3)
    except:
        print(i + '数据爬取失败')
  • 有时<p>标签中可能还有class属性,如<p class='main_content'>,此时可以对上面的内容进行优化,用“.*?”代替可能变化的内容,代码如下:
p_article = '<p.*?>(.*?)</p>'

5.1.3 数据乱码问题处理

1.网页源代码乱码的原因
  • 网页源代码乱码的原因主要是Python获取的网页源代码的编码格式和网页实际的编码格式不一致。网页中常用的编码格式有UTF-8、GBK、ISO-8859-1。
  • 用开发者工具查看网页源代码,然后展开<head>标签,查看<meta>标签里的charset属性,如下图所示,该属性的值就是网页的编码格式,这里为UTF-8。
    在这里插入图片描述
  • 用Requests库的encoding属性可以查看Python获取的网页源代码的编辑格式,代码如下:
code = requests.get(url,headers=headers).encoding
import requests
url = 'https://www.51job.com/'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36'}
code = requests.get(url,headers=headers).encoding
print(code)

在这里插入图片描述

  • 将code打印输出,结果为ISO-8859-1,与网页实际的编码格式UTF-8不一致,这就是Python获取的内容乱码的原因。

2.网页源代码乱码的解决方法

  • 在3.2.4节已经讲解了解决数据乱码的两种方法,这里总结一下,为之后讲解通用的解决方法做铺垫。
方法1:对获取的网页源代码文本进行处理
  • 第1中方法是对获取的网页源代码文本进行重新编码及解码,代码如下:
res = requests.get(url).text
res = res.encode('ISO-8859-1').decode('utf-8')
方法2:对获取的网页响应进行编码处理,再提取文本
  • 第2中方法是先对获取的网页响应进行编码处理,再通过text属性提取网页源代码的文本,代码如下:
res = requests.get(url,headers=headers)
res.encoding = 'utf-8'
res = res.text
  • 之后讲解的通用解决方法主要是基于方法1的原理,即对获取的网页源代码文本进行重新编码及解码。

3.网页源代码乱码的通用解决方法

  • 如果对于编码的知识还是不太理解,或者不愿意花精力去查看Python获取内容的编码格式及网页实际的编码格式,可以采用下面3种经验方法。通过逐个尝试着3种方法,或者直接将这3中方法整合,可以解决绝大部分的乱码问题。
  • 方法1:对于大部分网页,可以先尝试如下所示的常规代码(为了演示编码知识,先不加headers和timeout参数)。
res = requests.get(url).text
  • 方法2:如果方法1爬取到的内容有乱码,则尝试使用如下代码。
res = requests.get(url).text
res = res.encode('ISO-8859-1').decode('gbk')
  • 方法3:如果方法2爬取到的内容还是乱码或者报错,继续尝试使用如下代码。
res = requests.get(url).text
res = res.encode('ISO-8859-1').decode('utf-8')
  • 根据众多项目实战经验,导致乱码的大部分原因都是由于Python获取的内容是ISO-8859-1编码格式,而网页实际的编码格式时GBK或UTF-8。因此,通过逐个尝试上述3中方法,就可以尝试解决绝大多数的乱码问题。
  • 如果不想逐个尝试,可以通过try/except语句把3种方法整合到一起,代码如下:
res = requests.get(url).text
try:
    res = res.encode('ISO-8859-1').decode('utf-8') #方法3
except:
    try:
        res = res.encode('ISO-8859-1').decode('gbk') #方法2
    except:
        res = res #方法1
  • 数据乱码通常出现在用Requests库获取的网页源代码中,用Selenium库获取的网页源代码不会出现乱码。不过因为Requests库有Selenium库无法取代的优势——爬取速度快,所以了解乱码的解决方法还是有意义的。

5.1.4 数据爬后处理值舆情评分

  • 下面通过简单的案例来演示舆情评分的核心知识点,代码如下:
keywords = ['违约','诉讼','兑付','大跌','弄虚作假']
title = 'A公司大跌 且弄虚作假'
score = 100
for k in keywords:
    if k in title:
        score = score - 5
  • 第一行代码定义负面词清单;第2行代码定义示例标题title;第3行代码为评分score赋初始值100;第4~6行代码为核心代码,通过for循环语句遍历负面词清单中的每一个负面词,如果该词出现在标题中,则扣5分。
    # 舆情评分版本1
    score = []
    keywords = ['违约', '诉讼', '兑付', '阿里', '百度', '京东', '互联网']  # 这个关键词列表可以自己定义,这里只是为了演示
    for i in range(len(title)):
        num = 0
        for k in keywords:
            if k in title[i]:
                num -= 5  # 也可以写成 num = num -5
        score.append(num)

    for i in range(len(title)):
        title[i] = title[i].strip()  # strip()函数用来取消字符串两端的换行或者空格,不过目前(2020-10)并没有换行或空格,所以其实不写这一行也没事
        title[i] = re.sub('<.*?>', '', title[i])  # 核心,用re.sub()函数来替换不重要的内容
        print(str(i + 1) + '.' + title[i])
        print(company + '该条新闻舆情评分为' + str(score[i]))  # 这边注意,不要写score[i],因为它是数字,需要通过str()函数进行字符串拼接
        print(href[i])


companys = ['阿里巴巴', '万科集团', '百度']
for i in companys:
    try:
        baidu(i)
        print(i + '该公司百度新闻爬取成功')
        print('——————————————————————————————')  # 这个是当分隔符使用
    except:
        print(i + '该公司百度新闻爬取失败')

 # 舆情评分版本2
    score = []
    keywords = ['违约', '诉讼', '兑付', '阿里', '百度', '京东', '互联网']  # 这个关键词列表可以自己定义,这里只是为了演示
    for i in range(len(title)):
        num = 0
        # 版本2:获取新闻正文
        try:
            article = requests.get(href[i], headers=headers, timeout=10).text
        except:
            article = '爬取失败'
        for k in keywords:
            if (k in article) or (k in title[i]):
                num -= 5  # 也可以写成 num = num -5
        score.append(num)

 # 舆情评分版本3
    score = []
    keywords = ['违约', '诉讼', '兑付', '阿里', '百度', '京东', '互联网']  # 这个关键词列表可以自己定义,这里只是为了演示
    for i in range(len(title)):
        num = 0
        # 版本2:获取新闻正文
        try:
            article = requests.get(href[i], headers=headers, timeout=10).text
        except:
            article = '爬取失败'
        # 版本3:解决可能存在的乱码问题
        try:
            article = article.encode('ISO-8859-1').decode('utf-8')  # 方法3
        except:
            try:
                article = article.encode('ISO-8859-1').decode('gbk')  # 方法2
            except:
                article = article  # 方法1
        for k in keywords:
            if (k in article) or (k in title[i]):
                num -= 5  # 也可以写成 num = num -5
        score.append(num)

# 舆情评分版本4
    score = []
    keywords = ['违约', '诉讼', '兑付', '阿里', '百度', '京东', '互联网']  # 这个关键词列表可以自己定义,这里只是为了演示
    for i in range(len(title)):
        num = 0
        # 获取新闻正文
        try:
            article = requests.get(href[i], headers=headers, timeout=10).text
        except:
            article = '爬取失败'
        # 解决可能存在的乱码问题
        try:
            article = article.encode('ISO-8859-1').decode('utf-8')  # 方法3
        except:
            try:
                article = article.encode('ISO-8859-1').decode('gbk')  # 方法2
            except:
                article = article  # 方法1
        # 只筛选真正的正文内容,旁边的滚动新闻之类的内容忽略
        p_article = '<p.*?>(.*?)</p>'  # 有的时候p标签里可能还有class属性,例如<p class='main_content'>,通过.*?代替<p>标签中可能变化的内容
        article_main = re.findall(p_article, article)  # 获取<p>标签里的正文信息
        article = ''.join(article_main)  # 将列表转换成为字符串
        for k in keywords:
            if (k in article) or (k in title[i]):
                num -= 5
        score.append(num)

# 舆情评分版本4
    score = []
    import pandas as pd
    keywords = pd.read_excel('负面词清单.xlsx')['负面词'] #得先建立一个excel表
    for i in range(len(title)):
        num = 0
        # 获取新闻正文
        try:
            article = requests.get(href[i], headers=headers, timeout=10).text
        except:
            article = '爬取失败'
        # 解决可能存在的乱码问题
        try:
            article = article.encode('ISO-8859-1').decode('utf-8')  # 方法3
        except:
            try:
                article = article.encode('ISO-8859-1').decode('gbk')  # 方法2
            except:
                article = article  # 方法1
        # 只筛选真正的正文内容,旁边的滚动新闻之类的内容忽略
        p_article = '<p.*?>(.*?)</p>'  # 有的时候p标签里可能还有class属性,例如<p class='main_content'>,通过.*?代替<p>标签中可能变化的内容
        article_main = re.findall(p_article, article)  # 获取<p>标签里的正文信息
        article = ''.join(article_main)  # 将列表转换成为字符串
        for k in keywords:
            if (k in article) or (k in title[i]):
                num -= 5
        score.append(num)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值