近年来,考研人数不断添加,考研对于我们来说是一个热点不断的话题,对于考研资讯信息不够集中,数据量过大,考生无法准确的判断某个时期热点的资料,因此越来越多考生想能够快速得了解考研资讯、国家政策以及院校政策。
本项目通过对招生资讯、国家政策、院校政策进行总体数据挖掘以及分别进行数据挖掘,对院校的资讯热点进行提取绘制成词云图。
考生通过数据绘制出词云图,可以清楚的了解到近年来院校关注的重点是什么,以及近年来考研相关的国家政策热点,借助这些信息,能够帮助到我们广大考生进行更好选择所需要关注的热点信息,更为方便的了解到国家政策。
1 研究背景及目的
1.1 背景
近年来,考研人数不断添加,考研对于我们来说是一个热点不断的话题,对于考研资讯信息不够集中,数据量过大,考生无法准确的判断某个时期热点的资料,因此越来越多考生想能够快速得了解考研资讯、国家政策以及院校政策。
1.2 考研资讯政策数据资源
(1)数据来源
中国研究生招生信息网
(2)数据规模
爬取了近 5 年的招生资讯、国家政策、院校政策,共 5600 篇文章,约 750 万字。
(3)数据特征分析
文本型数据,招生资讯占比 62%,国家政策占比 4%,院校政策占比 34%。
1.3 考研资讯政策数据挖掘目的和意义
(1)数据挖掘的目标
通过对招生资讯、国家政策、院校政策进行总体数据挖掘以及分别进行数据挖掘,对 院校的资讯热点进行提取绘制成词云图。
(2)实际应用价值分析
考生通过数据绘制出词云图,可以清楚的了解到近年来院校关注的重点是什么,以及近年来考研相关的国家政策热点,借助这些信息,能够帮助到我们广大考生进行更好选择所需要关注的热点信息,更为方便的了解到国家政策。
2 考研资讯政策数据挖掘系统设计
2.1 系统总体设计
2.1.1 系统设计目标
通过爬虫爬取信息,将信息按照规定格式进行存储,将数据读入,构建语料库,进行中文分词,再进行词频统计分析,绘制词云图和柱状图,进行保存。
2.1.2 总体流程图
系统由爬虫子系统和数据挖掘子系统组成,流程
2.2 系统功能模块设计
2.2.1 数据抓取模块设计
给予开始位置、模式,即可开始抓取到研招网的招生资讯、院校政策、国家政策
的列表。
1 2 3 4 5
| 模块:requests, bs4 函数: http 请求函数:requests.get(url) url:待获取网页源代码的 URL html 解析函数:bs4.BeautifulSoup(content,model) content:待解析的 html 文本, model:解释器
|
2.2.2 数据解析模块设计
给予 url,即可开始解析网页中的文章数据。
1 2 3
| 模块:requests, bs4 函数: 数据标签查找函数:BeautifulSoup.find(tag,id) tag:html 标签 id:过滤器
|
2.2.3 数据保存模块设计
给予待保存数据、文件路径,将会将数据文件按格式保存在磁盘中
1 2 3 4
| 模块:os 函数: 打开文件函数 open(path, model) path:打开的文档路径 model:打开模式(wt 为写 入模式) 写入数据函数:f.write(article) article:待写入的数据
|
2.2.4 数据导入模块设计
遍历文件夹,将文本数据从文件夹中取出来,并分析出出文本时间、文本类型,
返回拥有多个返回值 filePaths,fileContents,dateTimes,Species 都为 list 类型。
1 2 3 4
| 模块:os,codecs 函数: 文件遍历函数:os.walk(path) path:待遍历文件路径 读取文件函数 codecs.open(filePath,'r','utf-8') 文件路径 打开模式 文件编码
|
2.2.5 构建语料库模块设计
给予数据文件夹路径,调用数据导入模块,取得文件数据,构建语料库。
1 2 3
| 模块: pandas 函数: 创建数据框函数:pandas.DataFrame({}) 参数:字典类型
|
2.2.6 中文分词模块设计
TextRank 算法分析: 类似于 PageRank 的思想,将文本中的语法单元视作图中的 节点,如果两个语法单元存在一定语法关系,则这两个语法单元在图中就会有一条边 相互连接,通过一定的迭代次数,最终不同的节点会有不同的权重,权重高的语法单 元可以作为关键词。节点的权重不仅依赖于它的入度结点,还依赖于这些入度结点的 权重,入度结点越多,入度结点的权重越大,说明这个结点的权重越高。
给予语料库,对语料库文本进行中文分词,由于数据量较大,采用多线程方式执
行。
1 2 3 4 5 6 7 8 9
| 模块: concurrent, jieba numpy 函数: 线程池创建函数:ThreadPoolExecutor(max_workers) max_workers: 同时进行的线 程个数 等待线程完成函数:wait(all_task, return_when=ALL_COMPLETED) all_task:线程 池句柄 return_when:等待类型 ALL_COMPLETED 全部线程执行完毕 读取 csv 数据函数:pandas.read_csv(path, encoding, index_col) path:路径, encoding: 编码, index_col 序列 Textran:jieba.analyse.textrank(content,topK=50,withWeight=False,allowPOS=('ns', 'n', 'vn', 'v')) content:待分割的文本
|
2.2.7 词频统计模块设计
给予中文分词数据框、过滤词频,对分词的数据进行合并统计。
1 2 3 4
| 模块: pandas 函数: 词频合并函数:segment_dataframe.groupby('word')['count'].sum() 参数(’word’):抽 取 word 字段 ['count'].sum() 计算 count 数据的和 排序函数:ser_word.sort_values(ascending=False) ascending 是否为升序 元组转换函数:zip(a,b) 打包 a,b 为元组数据
|
3 系统实现
3.1 数据抓取
向服务器发送请求->编码猜测->对 html 文档进行遍历->组合成 list 数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| def get_consultant_list(start, model): result = [] response = requests.get(base_url + "/kyzx/"+model+"/?start=" + str(start)) if response.status_code != 200: print("get_consultant_list请求失败") return result response.encoding = response.apparent_encoding soup = bs4.BeautifulSoup(response.text, "html.parser") liList = soup.find("ul", class_="news-list").children for li in liList: if isinstance(li, bs4.element.Tag): e = [] date = li.span.string title = li.a.string url = li.a['href'] if date is None or title is None or url is None: continue e.append(title) e.append(url) e.append(date) result.append(e) return result
|
运行结果无误
采用 requests 库构建 HTTP 请求,再使用 BeautifulSoup 库进行 html 解析,找到存放文章 url 的标签之后,对该标签进行遍历,并过滤掉无用的标签,最后再不断拆分 html 数据,组合成自己 的数据结构。
3.2 数据解析
向服务器发送请求->编码猜测->对 html 文档进行解析->遍历 p 标签->组合 p 标签数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def get_article(url): response = requests.get(base_url + url) if response.status_code != 200: print("response 请求失败") return "" response.encoding = response.apparent_encoding soup = bs4.BeautifulSoup(response.text, "html.parser") div_element = soup.find("div", id="article_dnull") result = "" for p in div_element.findAll("p"): if p.string is None: continue result = result + p.string return result
|
运行结果无误
分析:采用 requests 库构建 HTTP 请求,使用 BeautifulSoup 库进行 html 解析,定位到存放文章的 标签 div 上面,再对其下的 p 标签进行遍历,取出每一句的文章数据进行组合。
3.3 数据保存
替换掉特殊字符->取出文章数据->判断文章是否为空->写入磁盘->打印 log
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def save_article(l,path): title = l[0].replace(":", "") title = title.replace(":", "") title = title.replace("/", "") totalPath = path + l[2] + "-" + title + ".txt" article = get_article(l[1]) if article == "": return with open(totalPath, 'wt') as f: f.write(article) print(l[2] + "-" + l[0])
|
分析:取出文章的标题信息,将标题信息的特殊字符过滤掉避免无法保存到计算机里,判断文章 数据是否为空值,写出文本文件。
3.4 数据导入
遍历文件夹->截取时间元组->读入文本数据->判断文本类型->组合数据返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| import os; import os.path; import codecs;
def file_traversal(path): """遍历文件夹
遍历数据文件夹
Args: path: 待遍历的文件路径
Returns: 拥有多个返回值 filePaths,fileContents,dateTimes,Species 都为list类型 """ filePaths = [] fileContents = [] years = [] months = [] days = [] Species=[] for root, dirs, files in os.walk(path): for name in files: if name == ".DS_Store": continue time_str = name[:10] year = int(time_str[:4]) month = int(time_str[5:7]) day = int(time_str[9:11]) filePath = os.path.join(root,name) filePaths.append(filePath) f = codecs.open(filePath,'r','utf-8') fileContent = f.read() f.close() years.append(year) months.append(month) days.append(day) fileContents.append(fileContent) if root.find('consultant') != -1: Species.append('consultant') elif root.find('countries_policy') != -1: Species.append('countries_policy') elif root.find('school_policy') != -1: Species.append('school_policy') else : Species.append('null') return filePaths,fileContents,Species,years,months,days
|
运行结果无误
分析:采用 os.walk 进行文件遍历,使用 codecs.open 读入文本数据,分割出文章文本的发布时 间,通过文件路径名判断文本数据的类型
3.5 数据预处理
将数据读入的返回值构建成语料库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import pandas
def build_corpus(path): """构建语料库
构建语料库
Args: path: 待遍历的文件路径
Returns: DateFrame 语料库 """ filePaths,fileContents,Species,years,months,days = file_traversal(path)
corpos = pandas.DataFrame({ 'filePath':filePaths, 'fileContent':fileContents, 'Species':Species, 'year':years, 'month':months, 'day':days }) return corpos
|
运行结果无误
分析:将文件遍历的结构构建成语料库
3.6 文本关键词模型的构建
创建线程池->遍历语料库->提交结巴分词任务->读入停用词->采用 TextRank 进行分词->等待线程结束->组合多线程函数结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| import jieba import numpy import jieba.analyse
def jieba_segment(content): """结巴分词
对语料库文本进行中文分词
Args: content: 待切割文本
Returns: List [{'word':a,'count',1}] 分词列表 """ segments = [] stopwords = pandas.read_csv( "/Users/Apple/Documents/CodeWork/DataAnalysis/code_week_6/3.1/StopwordsCN.txt", encoding='utf8', index_col=False ) words = jieba.analyse.textrank(content, topK=50,withWeight=False,allowPOS=('ns', 'n', 'vn', 'v')) for word in words: if word not in stopwords: segments.append({'word':word, 'count':1}) return segments
from concurrent.futures import ThreadPoolExecutor,wait,ALL_COMPLETED
def word_segment(corpos): """中文分词
对语料库文本进行中文分词,由于数据量较大,采用多线程方式执行
Args: corpos: 语料库
Returns: DateFrame 词频 """ executor = ThreadPoolExecutor(max_workers=20) all_task = [] segments = [] for index, row in corpos.iterrows(): content = row['fileContent'] all_task.append(executor.submit(jieba_segment,content)) wait(all_task, return_when=ALL_COMPLETED) for task in all_task: segments.extend(task.result()) dfSg = pd.DataFrame(segments)
return dfSg
|
运行结果无误
分析:由于数据量过于庞大,单线程需要等待时间过久,这里采用多线程的方式进行文本分割, 建立线程池后,将待分割的文本数据传入结巴分词函数中,结巴分词采用 TextRank 算法进行文本 分割,等待文本分割完成后,再将数据组合。
3.7 文本关键词模型的训练
词频统计:将分词进行聚合合并->降序排序->过滤数据->转换成元组数据
词云图生成:输入词频元组数据,标题,设置词云图参数,返回词云图对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| def word_frequency(segment_dataframe,count_filter = 0): """词频统计
对分词的数据进行合并统计
Args: segment_dataframe: 中文分词 count_filter:排除小于过滤的词
Returns: tuple 词频 """ ser_word = segment_dataframe.groupby('word')['count'].sum() nSegStat = ser_word.sort_values(ascending=False) nSegStat = nSegStat[nSegStat >= count_filter] tup = tuple(zip(nSegStat.index,nSegStat)) return tup
from pyecharts import options as opts from pyecharts.charts import Page, WordCloud from pyecharts.globals import SymbolType,CurrentConfig, NotebookType CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_LAB
def build_word_cloud(data,title=""): word_cloud = ( WordCloud() .add(series_name=title, data_pair=data, word_size_range=[10, 100]) .set_global_opts( title_opts=opts.TitleOpts( title=title, title_textstyle_opts=opts.TextStyleOpts(font_size=23) ), tooltip_opts=opts.TooltipOpts(is_show=True), ) ) return word_cloud
|
运行结果无误
分析:词频统计将分词数据使用聚合函数进行分类,使用 sort_values 进行降序排序,过滤掉过 小而无法显示的数据,提高词云图渲染的速度。
4 系统测试分析
4.1 数据爬取
创建线程池->设置抓取的资讯->获取数据列表->遍历数据列表->提交爬取文章任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| executor = ThreadPoolExecutor(max_workers=20)
start = 0 go_ahead = True
model = "zcdh"
file_path = "" if model == "kydt": file_path = "consultant" elif model == "yxzc": file_path = "school_policy" elif model == "zcdh": file_path = "countries_policy" if file_path == "": print("请输入合理的爬虫参数") return
while go_ahead: clist = get_consultant_list(start,model) all_task = [] for l in clist: year = (l[2])[:4] if year == "2012": go_ahead = False break path = os.getcwd()+'/'+file_path+'/'+year+"/" if not os.path.exists(path): os.makedirs(path) all_task.append(executor.submit(save_article,l,path)) wait(all_task, return_when=ALL_COMPLETED) start = start + 50
|
分析:数据量过大且 HTTP 请求需要时间,采用了多线程的方式爬取数据,并将数据按照规定的
格式进行保存,方便数据读入分析。
4.2 总体分析
将数据文件夹传入->构建语料库->创建中文分词->将词频低于 150 次的词过滤,提高词云图的
渲染速度->创建词云图->保存图片