爬取百度新闻以及各省市新闻标题存进文本,并对文本处理,可视化前十个最高词频。

我做这个小项目做了两天了,中间也是遇到各种问题。通过做这次的小项目,我慢慢开始转变成面向对象编程的方式。对函数式编程越来越上手。对于文本的字符处理,自己通过研究写了一个函数来进行处理(本来是打算使用递归的方式处理,后来证实比较困难。)因为,明天是要开始上课了。所以,项目只能暂时做到这里。唯一美中不足的地方在于没有采用与前端的交互,即没有引入Flask框架,我对Flask了解的不是很多导致没有做完整。我等自学完Flask后,应该可以做出一个完整项目。到时候,再跟大家分享一个更加完整项目。因为,最终的目标是要动态交互,还需要引入数据库跟前端配合。

话不多说,先上代码,如下图:

from selenium import webdriver#从selenium库导入浏览器驱动
from pyecharts.charts import *#调用pyecharts库中的所有图形
from pyecharts import options as opts#导入pyecharts中所有的配置项
import jieba#引入jieba库
import random#导入随机库,使时间随机
import time#导入时间库
import re
import os#导入系统模块

options = webdriver.ChromeOptions()#创建配置对象
options.add_argument('--headless')#添加参数无头模式
options.add_argument('--disable-gpu')#添加参数关闭gpu
driver = webdriver.Chrome(options=options)#使用chrome作为爬虫测试用的浏览器,没有引入配置。因为,要展示爬取效果。

class BaiDu_News:#定义BaiDu_News的类,百度新闻爬虫
    def __init__(self):#定义一个初始化函数
        self.data = []#定义一个存储处理后数据的空列表。
        self.cut_data = []#定义一个空列表用于存储分词后得到的列表
        self.dt = {
    
    }#定义一个空字典用于统计词频

    def Request(self,url):#定义一个请求函数,方便多次调用
        driver.get(url)#发出get请求
        driver.implicitly_wait(random.randint(3,5))#隐式等待,随机时间3-5秒

    def Spyder(self):#定义一个爬虫函数
        url = 'https://news.baidu.com/'#设置百度新闻的url地址
        BaiDu_News.Request(self,url)#调用函数
        hot_news = driver.find_element_by_xpath('//*[@id="left-col-wrapper"]').text#获取热点新闻
        print("Hot_news")
        print(hot_news)
        BaiDu_News.save_file(hot_news)
        print()#换行输出

        def city(num):#定义一个有参数的函数city(),函数嵌套,嵌套在Spyder函数中。
            try:#加入异常处理,增加程序的健壮性
                print("正在抓取数据,请稍候...")#爬虫显示的效果
                change = driver.find_element_by_xpath('//*[@id="change-city"]')#获取改变省市的按钮
                change.click()#模拟点击打开改变页面
                time.sleep(random.randint(2,3))#等待页面响应,避免速度太快获取到未更新的数据
                other_city = driver.find_element_by_xpath('//*[@id="city_view"]/div[1]/a[{}]'.format(num))#选择城市
                other_city.click()#模拟点击改变城市
                time.sleep(random.randint(2,3))#等待响应
                Title = driver.find_element_by_xpath('//*[@id="city_name"]').text#获取城市的标题
                BaiDu_News.save_file(Title)#调用函数,保存城市标题到定义的存储函数中
                print(Title)
                City_news = driver.find_element_by_xpath('//*[@id="local_news"]/div[2]').text#获取城市的新闻
                BaiDu_News.save_file(City_news)#调用函数,保存城市对应的新闻内容
                print(City_news)
                print()
            except:#出现异常,执行:
                print("出现异常!!")

        def city2():#定义一个无参数的函数city2()
            number = 4#设定初始化number值为4
            while True:
                number += 1#每次number自增1
                if number == 33:#当number自增到33时退出爬取!
                    break
                try:
                    print("正在抓取数据,请稍候...")
                    change = driver.find_element_by_xpath('//*[@id="change-city"]')#获取改变按钮
                    change.click()#模拟点击,回到所有省市页面
                    time.sleep(random.randint(2, 3))
                    province = driver.find_element_by_xpath('//*[@id="city_view"]/div[1]/a[{}]'.format(number))#选择省
                    province.click()#模拟点击,改变省
                    time.sleep(random.randint(2, 3))
                    for i in range(1, 30):
                        try:
                            city_name = driver.find_element_by_xpath('//*[@id="city_view"]/div[1]/a[{}]'.format(i))#选择市
                            city_name.click()#模拟人的鼠标左击
                            time.sleep(random.randint(2, 3))
                            Title = driver.find_element_by_xpath('//*[@id="city_name"]').text#获取城市的标题
                            BaiDu_News.save_file(Title)
                            print(Title)
                            City_news = driver.find_element_by_xpath('//*[@id="local_news"]/div[2]').text#获取城市的新闻
                            BaiDu_News.save_file(City_news)
                            print(City_news)
                            print()
                            change = driver.find_element_by_xpath('//*[@id="change-city"]')#获取改变按钮
                            change.click()#点击改变城市
                            time.sleep(random.randint(2, 3))#给页面响应时间,不设置导致速度太快,会爬取到未更新的省市数据。
                            province = driver.find_element_by_xpath('//*[@id="city_view"]/div[1]/a[{}]'.format(number))#重新选择省
                            province.click()#模拟点击,重新改变
                            time.sleep(random.randint(2, 3))#休眠时间设置为随机的2,3秒钟
                        except:#如果出现异常,执行:
                            print("超出范围!!\n准备爬取下一个省市!!")
                            Return = driver.find_element_by_xpath('//*[@id="btn_back"]')#获取返回的xpath路径
                            Return.click()#点击返回到当前页面,方便下一次循环的继续。
                            time.sleep(random.randint(2, 3))
                            break#跳出当前for循环,开始下一次循环。
                        finally:#无论是否出现异常,都执行finally语句
                            print("爬取完毕!!")
                            print()
                            time.sleep(random.randint(1,2))
                except:
                    print("继续爬取!!")
        for i in range(1,5):#前四个直辖市,采用单独定义的函数爬取,采用循环的方式调用。
              city(i)#每次i变化都会自动会对函数调用。
        city2()#调用函数,从第五个省市开始,采用第二种函数爬取。


    def save_file(content):#定义一个存储函数
        if os.path.exists('F:/Baidu_news.txt'):#如果路径中存在txt文件,则执行追加操作。
            with open(r'F:/Baidu_new.txt', 'a+', encoding='utf-8') as f:#打开文件
                f.write(content+"\n")#追加后换行,避免挤在同一行。
        else:
            os.mkdir('F:/Baidu_news.txt')#如果路径中不存在txt文件,则创建一个txt。
            with open(r'F:/Baidu_new.txt','a+',encoding='utf-8') as f:
                f.write(content+"\n")#追加后换行,避免挤在同一行。

    def Word_Cut(self):#定义一个函数用于分词
        with open(r"F:/Baidu_new.txt","r+",encoding="utf-8") as f:
            lines = f.readlines()
        for l in lines:
            line = l.strip()
            #print(line)
            result = BaiDu_News.String_process(self,line)#将函数String_process处理结果的返回值String存储到变量result中
            self.data.append(result)#将每一条处理后的结果result都加入到列表中,方便统一处理。
        for d in self.data:
            d = BaiDu_News.String_process(self,d)
            # if ' ' in d:
            #     d = d.replace(' ',"")
            cut = jieba.lcut(d)#精准模式分词
            self.cut_data.append(cut)#将分词结果的列表加入到self.cut_data中
        #print(self.cut_data)

    def Word_All_Count(self):#定义函数统计所有词语的数量,并计算每个词语出现在文本中的概率
        for element in self.cut_data:#从列表中取出每一个切分的列表。
            for e in element:#从每一个切分的列表中分别找出每一个词语。
                if len(e) == 1:#除去一个字的词语
                    continue
                elif e not in self.dt.keys():#如果词语e第一次出现
                    self.dt[e] = 1#则赋予初始值1并加入到字典中。
                elif e in self.dt.keys():#如果词语e再次出现则累加1进行词语计数。
                    self.dt[e] = self.dt[e] + 1
        #print(self.dt)
        Length = 0#定义整个文本的初始长度为0
        self.plv = []  # 定义一个空列表用于存储频率
        self.plv_dt = {
    
    }
        for l in self.data:#获取每个处理后的字符串长度
            Length += len(l)#进行累加,得到整个文本的长度,用于计算频率
        for word in self.dt:
            #print(word+": 文本中出现的次数:"+str(self.dt[word]))#统计词语出现的次数
            #print("文本中出现的频率是"+str(round(self.dt[word]/Length,10)))#统计词语的频率,保留10位小数
            self.plv.append(round(self.dt[word]/Length,10))
            self.plv_dt[word] = round(self.dt[word]/Length,10)

    def Ten_Words_Count(self):#定义一个函数用于展示文本中前十个频率最高的词语
        self.ten_dt = {
    
    }#定义一个存储前10个数和概率的字典。
        self.plv = sorted(self.plv,reverse=True)#从小到大进行排序并进行翻转。
        #print(self.plv)
        self.plv = self.plv[0:10]#将列表self.plv中前十个元素切片并取代self.plv
        #print(self.plv)
        print("文本中出现频率最高的前十个词语:")
        for element in self.plv_dt:
            if self.plv_dt[element] in self.plv:
                self.ten_dt[element] = self.plv_dt[element]
                print(element)#输出文本中前十个频率最高的词语
            else:
                continue

    def String_process(self,string):#定义一个处理文本字符的函数。
        varchar = '‘’“:#,!*!*【】,、&#|?『【|,&;』;?:"▏┃·./丨②~”(」)“(「)→@“》!..." "|—‖《?…_'#字符集合
        ls = []
        for s in string:
            ls.append(s)
        for element in ls:
            if element in varchar:#判断是否在字符集合中
                ls.remove(element)#在集合中就删除
        String = ''
        for l in ls:
            String = String + l
        return String#返回处理完成的字符串
        #print(String)


    def Data_Show(self):#定义可视化函数,使用pyecharts的Bar()条形图进行可视化
        x_ls = []
        y_ls = []
        for d in self.ten_dt:#从字典中取出键(词语)加入列表
            x_ls.append(d)
            y_ls.append(self.ten_dt[d])#取出字典的值(词语的频率)加入列表
        B = (
            Bar()
            .add_xaxis(x_ls)#加入x轴的数据,list类型
            .add_yaxis('词频',y_ls)#加入y轴的标题和数据,也是list类型
            .set_global_opts(title_opts=opts.TitleOpts(title="百度新闻词频统计图"))#设置全局配置,图形的标题。
            .render('F:/Baidu_news.html')#图形存放的位置
        )

if __name__ == '__main__':#定义一个主函数,程序入口。
    BD = BaiDu_News()#实例化对象BD
    BD.Spyder()#BD对象调用Spyder函数(方法)
    BD.Word_Cut()
    BD.Word_All_Count()
    BD.Ten_Words_Count()
    BD.Data_Show()

爬虫运行截图:
在这里插入图片描述
爬取到的各省市新闻的原始文本,如下图:
在这里插入图片描述
文本通过我自定义的函数进行预处理后得到的结果,如下图:
在这里插入图片描述
对全部词语进行统计,如下图:
在这里插入图片描述
输出每个词语的次数和对应的词频(保留10位小数),如下图:
在这里插入图片描述
对文中出现频率最高前10个词语的统计,如下图:
在这里插入图片描述
最后,进行词频可视化,如下图:
在这里插入图片描述
去F盘打开文件,如下图:
在这里插入图片描述
关于代码的解释,我都写在注释中了。通过做本次不完整的项目,明白了异常处理的用处。设计了自动化爬虫,当我爬虫失败的时候,我设置的异常处理机制可以让爬虫继续下去。包括爬取的范围也不需要去管,自定义一个大的范围,超出范围就证明爬完了。这时,异常处理会让爬虫继续爬取数据而不退出!还有对文本各种奇葩字符处理的时候,我定义了一个函数来实现对文本字符的处理,没有使用re库。因为,我试过了不太好使。同时也知道函数(方法)可以赋值给一个对象,在看re的sub参数明白的。我试着把函数赋值给变量,其实就是将函数的返回值赋值给变量。总之,通过这次自己做的历时两天小项目,虽然不够完整(开头指明了原因),但是收获足够多。在做这个项目的过程中也遇到各种各样的问题,但是都想尽办法去解决了。大家在看代码不明白的地方可以与我交流探讨,欢迎大家前来指正我代码中不足之处,也可以提出改进意见。

最后,感谢大家前来观看鄙人的文章,文中或有诸多不妥之处,还望指出和海涵。

猜你喜欢

转载自blog.csdn.net/weixin_43408020/article/details/114806833