金融科技之情感分析(一):股民情绪指数与股市价格的相关性分析

  • 源代码获取
  • 前言
  • 文本数据源介绍
  • 评论数据爬取
  • 数据存储和查询
  • 计算情绪指数
  • 股票历史价格数据的获取
  • 相关性分析和可视化

源代码获取

2020年11月11日:
源代码可在点赞、评论该博文后,在下面百度网盘中自行下载。
复制这段内容后打开百度网盘App,操作更方便哦。 链接:https://pan.baidu.com/s/1Tzg-gdC6_-hvm1_b189aeA
提取码:9wx6

前言

本文是我在一家金融机构实习时做的第一个项目的整理。蒟蒻一枚,是金融科技和人工智能的初学者,写下本篇博文意在记录自己的足迹并与和我一样想要从事金融科技行业的小伙伴分享我遇到的问题和解决方案。如果本文有错误的地方,请大佬们指正。

在投资者社区中有许多文本型数据,我们可以对这些文本型数据进行处理,挖掘出有价值的信息。比如,东方财富股吧中有很多用户的发言、评论,我们可以借助自然语言处理中的情感分析技术,挖掘出市场的情绪指数,并研究情绪指数与金融市场的相关性。这个项目做的就是这个工作。

主要涉及爬虫自然语言处理相关性分析以及有限的MySQL的技术。

文本数据源介绍

首先我们打开http://guba.eastmoney.com/list,zssh000001,f.html,进入上证指数吧。进入后的界面如下图所示,同时我们选择排序方式为“发帖时间”。

可以看到,在股吧的界面中,有财经评论、东方财富网、股吧访谈等官方发布的内容,也有普通用户发表的评论。在这里,我们只研究普通用户发表的评论。
接着我们点击鼠标右键,选择网页源代码,可以看到该页面的html代码如下图。

我们发现在页面上看到的用户评论信息都被写在了HTML页面中,这为我们后面的爬虫提供了方便。进一步观察该源代码,我们发现源代码还同时包含了评论详细信息的网址、评论作者页面的网址。如果点击打开评论详细信息的网址,并打开其网页源代码,可以发现,我们可以在源代码中找到该条评论的**“发表时间”、”点赞数“**等数据。同样的,我们可以在评论作者的用户页面的源代码中找到该用户的”粉丝数“等数据。这些数据在我们后面情绪指数计算时都会用到。

评论数据爬取

我们通过分析数据源——股吧,发现我们所需要的数据基本上都被包含在了相关页面的HTML源代码中。那下一步便可以设计我们的爬虫了。我们这里只使用requests和BeautifulSoup两个库便可以完成数据爬取的任务了。
首先,引入这两个库。若还未安装这两个库,只需通过pip安装即可。

import requests
from bs4 import BeautifulSoup

首先把requests进一步封装成根据url获取HTML的函数

def getHtml(url):#下载网页源代码header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; LCTE; rv:11.0) like Gecko'}try:r=requests.get(url,headers=header)r.encoding='utf-8'#print(r.status_code)r.raise_for_status()return r.textexcept:getHtml(url)

现在我们可以根据url地址获取对应网页的HTML源代码。下一面要做的便是解析HTML的结果,定位我们需要的数据的位置。
通过分析股吧页面的HTML源代码我们发现,每条评论、公告、新闻等数据都存储在一个div标签下,且该div标签的class属性为articleh。

而对于每一个div标签,其又有5个span子标签,分别存储阅读量、评论数、标题、作者和发表时间。在第三个span子标签,即class属性值为”l3 a3“的span标签中,有一个a标签,a标签存在两个属性,其中href属性是该条评论详细页面的地址,title属性是该条评论的标题。我们可以发现多数散户的评论标题和正文的内容是一致的,所以这里保存title的值作为用户的评论文本。
分析完网页源代码后,我们便可以使用BeautifulSoup来获取对应的数据了。主要代码如下:(关于BeautifulSoup的用法,读者只需知道其可以通过标签类型和标签属性、属性值查询到相应的标签即可,具体的用法可自行在网络中查询。)

html=getHtml(url)
soup = BeautifulSoup(html, "html.parser")
contain = soup.find_all("div", {"class": "articleh"})#获取存有数据的div标签,存在contain中,因为一个页面有多条评论,所以contain是一个列表。
for i in contain:#遍历containcontent = i.find("span", {"class": "l3 a3"}).find("a")#获取一个div便签中第三个span标签下的a标签,其有href和title两个属性contentUrl="http://guba.eastmoney.com"+content["href"]#content["href"]是该评论的详细界面网址,因为其是相对地址,所以需要在前添加网址的前缀,得到完整的界面网址commentId=content["href"][-14:-5]#我们观察content["href"]属性的值,发现其是具有规则的字符串,从该字符串的倒数第14个位置到倒数第5个位置是该条评论的idtext=content.attrs["title"]#获取评论文本(标题)userUrl = i.find("span", {"class": "l4 a4"}).find("a").attrs["href"]#用同样的方法获取用户主页链接userId=userUrl[23:]#获取用户ID

通过上面的代码段,我们已经从股票的评论列表页面中获取了我们需要的数据。下面我们将根据我们获取的conteUrl和userUrl获取其他我们需要的信息,方法和上面类似。

commentHtml=getHtml(contentUrl)#获取评论详细信息源代码
soup = BeautifulSoup(commentHtml, "html.parser")
date = soup.find("div", {"class": "zwfbtime"}).text[4:14]#获取评论发表时间
likeCount=int(soup.find("div", {"data-like_count": True}))#获取评论点赞数,并转换成整数类型。(因为从html中获取会认为是字符串类型)
userHtml=getHtml(userUrl)#获取用户主页源代码
soup = BeautifulSoup(userHtml, "html.parser")
fans=int(soup.find("a", {"id": "tafansa"}).find("span").text)#获取用户粉丝数

至此,我们已经能够获取我们所需要的数据了。但目前我们只是从评论列表的第一页获取数据,如果获取其他页的数据呢?很简单,当我们在网页上点击第二页时,可以发现浏览器的地址栏变成了
http://guba.eastmoney.com/list,zssh000001,f_2.html。所有我们只需要改变该地址字符串的”f_2”和“.html“之间的数字即可。
不过,上述代码只是简化后的结果。没有考虑如果该条”评论“是新闻、讨论、问答等的情况。事实上,这些情况经常出现,且因为不同类型的数据其html得到标签结构往往不同,所有可能导致程序异常终止。一个简单粗暴的方法便是在程序段外面,在循环语句下加上try-except异常处理,若出现异常,则执行continue语句,跳过这一层循环,即跳过该条”评论“。

html=getHtml(url)
soup = BeautifulSoup(html, "html.parser")
contain = soup.find_all("div", {"class": "articleh"})
for i in contain:try:content=i.find("span", {"class": "l3 a3"}).find("a")contentUrl="http://guba.eastmoney.com"+content["href"]commentId=content["href"][-14:-5]text=content.attrs["title"]userUrl=i.find("span", {"class": "l4 a4"}).find("a").attrs["href"]userId=userUrl[23:]commentHtml=getHtml(contentUrl)soup=BeautifulSoup(commentHtml, "html.parser")date=soup.find("div", {"class": "zwfbtime"}).text[4:14]likeCount=int(soup.find("div", {"data-like_count": True})userHtml=getHtml(userUrl)soup=BeautifulSoup(userHtml, "html.parser")fans=int(soup.find("a", {"id": "tafansa"}).find("span").text)except:continue

这样可以解决大多数情况的问题,但也并非万无一失。可能有的”评论“标签(这里指新闻讨等类型的数据)执行try语句下的代码并没有出现异常,但存储的数据并非我们想要的内容。这需要我们在实践的过程中不断总结、寻找规律、判断并排除这类情况。
至此,获取数据我们已经介绍完了。

数据存储和查询

在利用爬虫技术获取到我们需要的数据后,便需要将数据存储以待后续处理。数据存储有很多方法,在这里,我们将其存储到数据库中。
这里使用的是MySQL数据库和DataGrip数据库图形界面操作工具。读者可参考网络上的MySQL安装教材进行安装。若读者无法使用DataGrip(收费软件,在校大学生和教师可申请免费使用)也无妨,我只是使用该软件方面查看数据库的内容。

成功安装数据库,并输入密码登录后。便可以创建数据库和数据表了。
首先我们创建数据库。注:windows操作系统下不区分大小写

create database dfcf;

其中,dfcf是自定义的数据库名。dfcf即东方财富。
接着,我们在dfcf数据库中创建数据表。

use dfcf;
create table tb_comment
(id         int auto_incrementprimary key,comment_id varchar(20)  null,content    varchar(300) null,like_count int          null,date       date         null,user_id    varchar(20)  null,share_code varchar(15)  null,
);
create table tb_user
(id   char(18) not nullprimary key,fans int      null
);

通过上面的代码,我们便在dfcf数据库中创建了两个表。一个存储评论的相关信息,另一个存储用户的相关信息。在tb_comment表中,id字段为从1开始递增的整数,用来方便查看存储的数据总数。share_code字段存储的是该条评论所针对的股票,在这里,因为我们研究的是上证指数吧,所以share_code为zssh000001.

现在我们已经打建好了数据库。下一个问题便是如何向数据库中存储和从数据库中查询数据了。只需要简单的了解一下SQL语句即可。对数据库的操作基本上可以归为增删改查四大块。每一块常用的语句很少也很简单,读者自行在网络上了解即可,后面我也会给出用到的SQL语句。
关于数据存储和查询的最后一个问题便是:如何使用Python对数据库进行操作。
事实上,我们只需要安装pymysql库,再按照一定格式调用即可。下面给出例子:

import pymysql
def  storeCommentInf(comment):#存储评论db = pymysql.connect("localhost", "root", "你的登录密码", "dfcf")cur=db.cursor()sql = "INSERT INTO TB_COMMENT(comment_id,content,like_count,date ,user_id,share_code) values (%(comment_id)s,%(content)s,%(like_count)s,%(date)s,%(user_id)s,%(share_code)s)"sql1 = "SELECT * FROM TB_COMMENT where COMMENT_ID=%s"if  cur.execute(sql1,(comment["comment_id"])):#去重cur.close()print("评论已经存在")db.close()else:cur.execute(sql, (comment))db.commit()cur.close()db.close()print("插入评论成功")

在上面的代码中,我们首先导入了pymysql库,并定义了一个函数。函数中有一个参数comment,为字典类型,存储的是一条评论的有关数据。上述代码段的第3行,我们连接了dfcf数据库。第4行,我们引入了一个会话指针,这是固定写法,读者不必深究。随后,我们定义个两个字符串sql和sql1来定义要执行的数据库操作。sql用来插入comment,其values()内定义的%(xxx)s中的”xxx"是参数comment中的键,执行该语句可以将参数comment对应键的值存入数据库对应字段中。sql1用来根据comment_id来查询数据,避免同一条评论被重复插入。cur.execute()便是用来执行设置好的sql语句。
下面给出全部的用到的关于数据库操作的函数:

import pymysqldef  storeCommentInf(comment):#存储评论db = pymysql.connect("localhost", "root", "你的登录密码", "dfcf")cur=db.cursor()sql = "INSERT INTO TB_COMMENT(comment_id,content,like_count,date ,user_id,share_code) values (%(comment_id)s,%(content)s,%(like_count)s,%(date)s,%(user_id)s,%(share_code)s)"sql1 = "SELECT * FROM TB_COMMENT where COMMENT_ID=%s"if  cur.execute(sql1,(comment["comment_id"])):#去重cur.close()print("评论已经存在")db.close()else:cur.execute(sql, (comment))db.commit()cur.close()db.close()print("插入评论成功")def storeUserInf(user):#储存用户数据db = pymysql.connect("localhost", "root", "你的登录密码
", "dfcf")cur = db.cursor()sql = "INSERT INTO TB_USER(id,fans) values (%(id)s,%(fans)s)"sql1="SELECT * FROM TB_USER where ID=%s"if  cur.execute(sql1,(user["id"])):#去重cur.close()db.close()print("用户已经存在")else:cur.execute(sql, (user))db.commit()cur.close()db.close()print("插入用户成功")def selectCommentOrderByDate(share_code,method):#查询评论信息db = pymysql.connect("localhost", "root", "你的登录密码", "dfcf")cur = db.cursor()if  method==0:#按照日期升序sql = "SELECT * FROM TB_COMMENT WHERE SHARE_CODE=%s ORDER BY DATE"else:#按照日期降序sql="SELECT * FROM TB_COMMENT WHERE SHARE_CODE=%s ORDER  BY DATE DESC "cur.execute(sql,(share_code))db.commit()cur.close()return  cur.fetchall()def selectFansByUserId(userId):#查询用户粉丝数db = pymysql.connect("localhost", "root", "你的登录密码", "dfcf")cur = db.cursor()sql = "SELECT FANS FROM TB_USER where ID=%s"cur.execute(sql,userId)db.commit()cur.close()return cur.fetchall()

至此,我们已经解决了获取数据和存储、查询数据的问题了。

计算情绪指数

现在,我们便可以计算投资者的情绪指数了。我们需要借助自然语言处理中的情感分类技术。按照正常的处理流程,我们需要搭建模型、准备语料库、训练模型、测试模型然后得到一个情感分类的模型。但这里,我们直接使用现有的模型。snownlp是一个中文的开源的自然语言处理的Python库,可以进行分词、情感分类等。但snownlp库有一个缺陷,便是其模型的训练语料是商品购物评论语料,用来做金融领域的情感分类效果一般,但目前还并没有关于金融领域的中文自然语言处理的开源库、语料库。所以这里我们暂时使用snownlp库来完成我们的实验。若想进一步提高准确率,还需自己搭建语料库进行模型训练。
下面介绍用snownlp进行情感分析的方法:
首先,需要安装snownlp库,直接用pip安装即可。安装完毕之后,按照下列方法调用。

from snownlp import SnowNLP
text="大牛市来啦,发财啦"
nlp=SnowNLP(text)
print(nlp.sentiments)

运行上述代码,我们可以得到一个浮点数0.7343040563996935。nlp.sentiments是一个在【0,1】之间的浮点数,这个数越接近1,代表该文本表达的积极情绪越强,反之,则代表该文本表达的消极情绪越强。
现在我们已经可以计算一个评论文本的情感得分了,下一步便是量化出某一日市场投资者的整体情绪。量化的方法有许多种,可以将某一日所有的评论情绪得分得分相加再求评价,也可以求某一日情绪得分大于0.5的评论所占的比例。

本蒟蒻采用的方法是:

①将情绪得分>0.6的评论当作积极评论,小于0.4的评论当作消极评论。

②设置变量neg和pos,存储某一日市场的积极情绪因子和消极情绪因子。关于neg和pos的计算方法,以neg为例: 初始化为0
若某一日的某一评论comment的情绪得分<0.4 neg=neg+1+log(该条评论的点赞数+该条评论作者的粉丝数+1,2)
其中log(x,2)表示以2为低的x的对数。考虑该条评论的点赞数和该条评论作者的粉丝数是因为考虑到不同的评论的质量不同。取对数是为了让数据更加平滑,防止极值过大。+1是为了防止该条评论的点赞数和该条评论作者的粉丝数都为0。

③计算某一日市场的总体情绪得分score。我设计的模型是
score=log((pos/(pos+neg+0.0001)-0.5)*(该日评论总数+1))
(pos/(pos+neg+0.0001)-0.5)的意思是计算市场的情绪倾向,大于0表明市场积极情绪情绪较强,越接近0.5越强。小于0反之。后面的(该日评论总数+1),是因为本人认为某一日投资者的评论越多,代表市场投资者情绪的波动越大。

该部分的核心代码如下:

def quantilizeSentiments(data,date):pos=neg=0for comment in data[date]:try:nlp = SnowNLP(comment['comment'])sentimentScore = nlp.sentimentsexcept:print(traceback.format_exc())continueif(sentimentScore>0.6):fans=SQL.selectFansByUserId(comment['user_id'])pos+=1+math.log(comment['like_count']+fans[0][0]+1,2)if(sentimentScore<0.4):fans=SQL.selectFansByUserId(comment['user_id'])neg+=1+math.log(comment['like_count']+fans[0][0]+1,2)print("负:"+str(neg)+"  正:"+str(pos))return math.log((pos/(pos+neg+0.0001)-0.5)*(len(data[date])+1),2)

股票历史价格数据的获取

关于股票历史价格数据,推荐使用tushare.pro,其提供了很多免费的接口来获取金融数据,在校大学生还可以申请使用更高级的接口。
tushare的官方有很详细的使用文档和案例。

我们这里用tushare获取000001.SZ从20170601到20191231每日的交易数据。

pro = ts.pro_api()
df = pro.daily(ts_code="000001.SZ",start_date='20170601', end_date='20191231')
print(df)

相关性分析和可视化

通过上面的步骤,我们已经可以计算某一日市场的情绪指数了。下面便是最后一步,分析情绪指数与股票走势的相关性。这里以平安银行近两年的股票价格走势为例。

我将其计算出的情绪指数和股票历史价格都存储在了data.xlsx excel文件中,部分数据如下:全部数据是从2017-06到2019-06的交易日的情绪指数和股票价格。

下面进行相关性分析。在这里,我们使用MIC最大互信息系数来表示socre和price的相关性。关于MIC的具体算法和原理,我在做的时候没有去深究,但最近正好信息论学了互信息,后面可能会去研究一下。现在我们需要知道其能衡量线性、非线性数据的相关性即可。MIC的值越接近1,则表明两者的相关性越强。
我们可以直接使用Python的minepy包来计算MIC值。minepy需要使用pip安装,若读者在安装时出现错误,可在CSDN的其他博客上寻找解决方案。
安装完minepy包后,我们按照调用规则调用即可。同时,我们可以利用matplotlib绘制出两者的曲线图。代码如下:

import pandas as pd
import  matplotlib.pyplot as plt
from pandas.plotting import register_matplotlib_converters
import numpy as np
from minepy import MINE
register_matplotlib_converters()plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=Falsedata=pd.read_excel('data.xlsx')#读取数据mine = MINE(alpha=0.6, c=15)
mine.compute_score(data['score'], data['price'])
print(mine.mic())//相关性技术plt.plot(data['date'],data['score'],linestyle='--',label='情绪')
plt.xticks(rotation=45)
plt.legend(loc='upper left')
plt.twinx()
plt.plot(data['date'],data['price'],color='red',label='股价')
plt.xticks(rotation=45)
plt.legend(loc='upper right')
plt.show()

运行,得到的结果是0.22005529890307557,表现出较弱的相关性,绘制出的折线图如下:

从图片上可以看出情绪指数与股票价格的走势大致相同,但MIC的结果和我们的直觉不太一致。

下面,我们对数据进行平滑处理,消除一部分的噪音。我采用的方法是计算score和price的移动平均值

import numpy as np
data['avg_Score'] = np.round(data['score'].rolling(window=30,min_periods=1).mean(), 2)
data['avg_Price'] = np.round(data['price'].rolling(window=30,min_periods=1).mean(), 2)

上面的代码求出了socre和price的30日均线。我们再用计算出的data[‘avg_Score’]和data[‘avg_Price’]代替前面的data[‘score’]和data[‘price’],重新计算会绘制。得到如下结果:
MIC系数为0.5477048122148983,代表呈现出强的相关性。从图像可以更直观看出:

至此,我们已经完成了所有的步骤并得出了比较好的结果。

如有不足之处,欢迎指教。

金融科技之情感分析:股民情绪指数与股市价格的相关性分析相关推荐

  1. arcgis重心迁移分析_山东省植被覆盖度变化与气候因子相关性分析

    点上方"测绘科学"关注我们 摘 要 植被是陆地生态系统的重要组成部分,能够对陆地生态系统的所有变化做出响应,在能量交换中也起着至关重要的作用,是表现和衡量生态环境状况的主要指标.植 ...

  2. spss相关性分析看结果_利用spss做Pearson相关性分析步骤详解

    有蛮多的学生私信老徐问如何利用spss做相关性分析,其实相关性分析应该是spss分析中较为基础的一个功能应用,很多学生可能是因为跨专业或者对统计软件了解较少,在没有经过系统学习的前提下,感觉云里雾里. ...

  3. 神策数据荣获“金猿奖 - 2017 金融科技优秀产品奖”

    10 月 25 日,由<数据猿>主办的"2017 金融科技•数据驱动金融商业裂变价值峰会"在北京悠唐皇冠假日酒店举行.神策数据的用户行为分析产品神策分析荣获" ...

  4. (转)美国金融科技监管框架

    [独家编译]美国金融科技监管框架 2017-01-23 坤镯 FLR金融监管 I. 序言 金融科技的创新具有根本改变金融服务业及更广阔经济领域的潜能.虽然金融科技仍处于发展初期,但却能够发挥促进普惠金 ...

  5. excel两个指标相关性分析_用Excel做相关性分析

    一.概念理解 相关关系:变量之间存在着的非严格的不确定的关系,对它们进行深层次的分析,观察它们的密切程度. 相关性分析:对变量之间相关关系的分析,即相关性分析.其中比较常用的是线性相关分析,用来衡量它 ...

  6. 毕设论文数据分析记录-part2:相关性分析

    20220225-相关性分析 polyfit线性拟合后进行t检验判断线性关系显著水平-一阶差分去趋势 a=xlsread('gai-maizey.xls','gai-maizey'); a1=a(:, ...

  7. 【20210825】【数据分析】两个向量做相关性分析时,需不需要统一量纲?

    对这个问题的思考源于机器学习中的特征工程,加工了 24 维特征,对此想要分析特征和特征之间.特征和标签之间的相关性,但不同维度的特征物理意义不尽相同,就在思考:需要先对特征归一化.标准化吗? 先放结论 ...

  8. 基于相关性分析和主成分分析的变量筛选方法

    基于相关性分析和主成分分析的变量筛选方法 https://www.zybuluo.com/notmylove/note/1508052 主成分分析法 指标筛选 既然在课程专题四中讲到主成分分析法,那么 ...

  9. 数据化运营09 抓住问题关键:用相关性分析拆解多个影响因素

    前一讲,和你探讨了多维分析的方法,通过多维分析来寻找指标变化的原因.当我们找到问题的原因时,自然会进一步思考一个问题:指标变化的原因这么多,决定问题的关键因素又是哪个呢? 需要专栏原数据进行实操的同学 ...

最新文章

  1. 没有添加跳新增,添加之后跳修改
  2. 最新:2020年度陈嘉庚科学奖出炉!施一公获生命科学奖
  3. UML建模之时序图(Sequence Diagram)教程
  4. Repeater的嵌套结合用户控件的使用
  5. [Leetcode][第32题][JAVA][最长有效括号][动态规划][栈][正向逆向结合]
  6. c# splitContainer 中间的分割线问题
  7. jsp定义java方法_jsp教程上:jsp的定义和使用方法
  8. 本周大新闻|传Cambria采用升级版XR2芯片,TGS增设VR虚拟展厅
  9. 使用腾讯云COS对象存储(详细流程)
  10. 2021-04-19 一万小时定律
  11. mysql分区 alter_mysql分区表设计(一)
  12. springboot 发送邮件 QQ邮箱 535error
  13. 建立ftp 服务器连接
  14. php输出setcookie,PHP函数:setcookie()
  15. 数字化工厂建设的内容主要有哪三个方面
  16. OSG入门即osgEarth建立一个地球的详细步骤
  17. windows如何获得U盘的详细信息
  18. 准备使用vue制作开始一个的单机挂机放置类小游戏
  19. 图片转CAD文件如何转换?这个方法get一下
  20. 低压电缆载流量估算一览表

热门文章

  1. 2021年美容师(中级)多少分及格及美容师(中级)考试技巧
  2. 基于Matlab及Simulink的机电系统仿真
  3. (一)EJB.JPA jpa概述
  4. python爬取唐诗三百首
  5. 微信公众号验证token+关注回复+关键字回复+单图文回复
  6. qcc304x笔记之ota升级(十)
  7. 组织机构代码生成规则
  8. wifi 联想小新_联想小新连不上无线网
  9. 搜狗输入法与谷歌输入法
  10. 我为什么要花3年熬几百个夜写一本推荐系统的专著?