​ 近年来,考研人数不断添加,考研对于我们来说是一个热点不断的话题,对于考研资讯信息不够集中,数据量过大,考生无法准确的判断某个时期热点的资料,因此越来越多考生想能够快速得了解考研资讯、国家政策以及院校政策。

​ 本项目通过对招生资讯、国家政策、院校政策进行总体数据挖掘以及分别进行数据挖掘,对院校的资讯热点进行提取绘制成词云图。

​ 考生通过数据绘制出词云图,可以清楚的了解到近年来院校关注的重点是什么,以及近年来考研相关的国家政策热点,借助这些信息,能够帮助到我们广大考生进行更好选择所需要关注的热点信息,更为方便的了解到国家政策。

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.1 总体流程图

​ 系统由爬虫子系统和数据挖掘子系统组成,流程

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 = []
# 发送http请求
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
# 使用BeautifulSoup进行煲汤 解析器为 html.parser
soup = bs4.BeautifulSoup(response.text, "html.parser")
# 寻找标签 ul css:news-list 的子标签
liList = soup.find("ul", class_="news-list").children
# 对li标签进行遍历
for li in liList:
# 判断类型是否为Tag
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):
# 发送http请求
response = requests.get(base_url + url)
# 判断是否请求成功
if response.status_code != 200:
print("response 请求失败")
return ""
# 设置相应数据的编码为猜测的编码
response.encoding = response.apparent_encoding
# 使用BeautifulSoup进行煲汤 解析器为 html.parser
soup = bs4.BeautifulSoup(response.text, "html.parser")
# 寻找标签 div id:article_dnull
div_element = soup.find("div", id="article_dnull")
result = ""
# 找到 div 标签下所有的p标签并进行遍历
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)
# 打印log
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)
# 将filePath、fileContent、dateTime、Species 加载到数据框corpos中,形成语料库

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())

# 构建DateFrame
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
# 招生资讯:kydt  院校政策:yxzc 国家政策:zcdh
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:
# 获取50个数据列表
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 次的词过滤,提高词云图的

渲染速度->创建词云图->保存图片

总体分析代码图

OverallAnalysis

OverallConsultant

OverallCountriesPolicy

OverallSchoolPolicy

评论