利用python对股票新闻公告做简单的舆情分析

519 阅读12分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

利用python对股票新闻公告做舆情分析

这篇博客只是在开发金融研投系统时,应急用的,技术性含量不高。

粗浅理解,文中若有错误,欢迎您的批评指正!

思路讲解

本文针对股票公告的利好和利空可能包含的关键词进行罗列,然后形成字典与公告的标题或关键词进行匹配。其中,本小白对于利好新闻分析大概从以下几个方面入手:

  • 管理层或大股东的增持或预增持;
  • 公司回购股票;
  • 盈利超预期;
  • 签订重大合同或项目等;

同样的,利空新闻一般可能表达的意思会有:

  • 管理层或大股东减持、大量减持;
  • 违款违法公告;
  • 盈利低于预期;
  • 定增预案、股票定向增发、定增募资等等意思。 然后针对这些可能的意思取一些关键词,然后可以利用正则表达式对标题或文章关键词进行匹配。

具体实现

连接数据库

import mysql.connector

# 连接数据库
def DBconn_base(database='********'):
    db_native = mysql.connector.connect(
        host="***.***.***.***",
        user="****",
        passwd="**********",
        database=database
    )
    cursor_native = db_native.cursor()
    return db_native, cursor_native

取出原始数据

这里的新闻资讯等都是合法从东方财富爬取然后存储在相应的数据库里的:

    # 从数据库中获取从东方财富爬取的各公司的新闻资讯
    dbconn, cursor = DBconn_base()  # 开启游标 开启连接
    cursor.execute(
        '''select *  from invest_report_news where update_time > (select max(update_time) from 
        wutong_native.invest_report_news_tag);''')
    news_list = pd.DataFrame(  # 每条记录转换成dataframe结构,取名为news_list
        cursor.fetchall(),
        columns=[x for x in cursor.column_names]
    ).values
    cursor.close()  # 关闭游标
    dbconn.close()  # 关闭连接

利好利空关键词字典

帖主在这里的处理其实有点呆,我觉得这里和 NLP 有关,利用语义分析等等,但帖主没花时间去了解,目前想到的方法就是用这种关键词了,然后可以用正则匹配,又因为懒得写正则,所以直接用对象字典了。

news_tagged_list = []  # 定义一张标签有利好利空的数据库表,暂为空表
    tag_list = ['利好', '利空']  # 两个标签,下面是标签相对应的关键词
    reason_obj = {
        '利好': ['管理层或大股东增持', '公司回购股票', '盈利超预期', '签订重大合同'],
        '利空': ['管理层或大股东减持', '违款违法公告', '盈利低于预期', '定增预案']
    }
    good_obj = {
        '管理层或大股东增持': [
            ['股东', '增持'], ['高管', '增持'], ['董事', '增持'], ['董监高', '增持'], ['总经理', '增持'], ['控制人', '增持'], ['实控人', '增持'],
            ['拟', '增持'], ['此次增持'], ['计划', '增持'], ['实施', '增持'], ['继续增持'], ['持股比例', '增'], ['股份占比', '增']
        ],
        '公司回购股票': [
            ['回购', '股份'], ['回购', '股票'], ['收购', '股权'], ['拟', '回购', '股'], ['发布', '回购', '预案'], ['实施', '回购方案']
        ],
        '盈利超预期': [
            ['净利', '增'], ['盈利', '超', '预期'], ['业绩', '符合', '预期'], ['业绩', '超', '预期'], ['业绩', '好转'], ['业绩', '改善'],
            ['业绩', '大涨'], ['业绩', '反转'], ['业绩', '创', '新高'], ['业绩', '新纪录'], ['业绩', '高增长'], ['业绩', '爆发'], ['销量', '大涨'],
            ['销量', '暴涨'], ['业绩', '预增'], ['调高', '盈利增幅'], ['收入', '翻倍'],
            ['收入', '增'], ['预计', '盈利', '增'], ['净利', '预盈']
        ],
        '签订重大合同': [
            ['签', '重大', '合同'], ['签', '销售合同'], ['重大', '销售合同'], ['重大', '项目合同'], ['签', '订货合同'], ['签', '供货合同'],
            ['项目', '中标'], ['签', '战略', '合作协议']
        ]
    }
    bad_obj = {
        '管理层或大股东减持': [
            ['股东', '减持'], ['高管', '减持'], ['董事', '减持'], ['董监高', '减持'], ['总经理', '减持'], ['实控人', '减持'], ['控制人', '减持'],
            ['拟减持'], ['计划', '减持']
        ],
        '违款违法公告': [
            ['违款'], ['违法'], ['违规'], ['违约'], ['触犯', '法律'], ['违反', '法律'], ['违反', '法规'], ['违反', '规定']
        ],
        '盈利低于预期': [
            ['盈利', '低于预期'], ['业绩', '低于预期'], ['收入下降'], ['利润', '同比下降'], ['利润', '同比减少'], ['业绩', '恶化'],
            ['业绩', '下滑'], ['业绩', '亏损'], ['净利润亏损'], ['主营业务', '失败'], ['净利', '亏损'], ['净利', '下滑'], ['预计亏损']
        ],
        '定增预案': [
            ['定增预案'], ['定增', '计划', '募资'], ['拟定增', '募资'], ['定增', '申请', '募资'], ['定增', '计划', '募集'], ['拟定增', '募集'],
            ['定增', '申请', '募集']
        ]
    }

利用以上的对象字典与新闻资讯的标题和关键词进行循环匹配,然后插入利好或利空的标签以及标签关键词即 reason_obj 对象里的字典元素里的元素。逻辑缜密的同学肯定会想到这里存在的潜在 BUG,因为可能有的标题或关键词同时包含几种标签关键词,如果都是利好关键词或者利空关键词还好,但万一又有利好关键词,又有利空关键词,则该新闻资讯的标签就会出现了变动,影响最终结果。

匹配后打标签

dw = []
    for i in range(len(min_date)):
        dw.append(min_date[i][0])
        
        # 根据表长度循环,对每条新闻进行标签,更改交易时间等相关操作
        for i in range(len(news_list)):
            # print(str(i) + ':')  # 若不想打印结果中有字段都为空的行,可以删除该行code
            NEWS_ID = news_list[i][0]
            STOCK_CODE = news_list[i][1]
            STOCK_NAME = news_list[i][2]
            NEWS_DATE = news_list[i][3]
            NEWS_TITLE = news_list[i][4]
            NEWS_CONTENT = str(news_list[i][5])
            # UPDATE_TIME = news_list[i][6]
            
            # 定义成功标签利好利空后的新闻资讯列表
            news_tagged_item = {
                'news_id': NEWS_ID,  # 新闻ID,主键
                'stock_code': STOCK_CODE,
                # 新增字段股票代码数字
                'stock_code_figure': STOCK_CODE[:6],  # 该字段为取STOCK_CODE字段值的前6位
                'stock_name': STOCK_NAME,
                'news_date': NEWS_DATE,
                'news_title': NEWS_TITLE,
                # 新增字段交易时间
                'trading_date': '',  # 交易时间需要重新筛选,从日期生成序列里进行规则查找
                # 新增字段标签类型
                'tag_type': '',  # 利好 或 利空
                # 新增字段标签类型的在细分类的所属分类
                'tag_reason_type': '',
                # 新增字段更新时间
                # 'update_time': UPDATE_TIME  # 更新时间统一指定为操作更新的当前时间的时间戳
            }
            
            a = STOCK_NAME
            if a[:3] == '*ST':
                name = a[a.rfind(a[:3]):]
            elif a[:2] == 'ST':
                name = a[a.rfind(a[:2]):]
            elif '股份' in a or '证券' in a or '集团' in a or '控股' in a or '科技' in a or '医药' in a:
                name = a[0:-2]
            elif '中国' in a:
                name = a[a.rfind(a[:2]):]
            else:
                name = a
                
                # 先要判断新闻标题与内容与对应的公司股票相关名称name是否能够匹配
                # 利好、利空 标签分类,在双循环中进行标签所属关键词和新闻标题、新闻内容的判断操作
                if name in NEWS_TITLE or name in NEWS_CONTENT:
                    for news_tag in tag_list:
                        for reason_word in reason_obj[news_tag]:
                            if news_tag == '利好':
                                for tag_word in good_obj[reason_word]:
                                    if all(tag_element in NEWS_TITLE for tag_element in tag_word) or all(
                                        tag_element in NEWS_CONTENT for tag_element in tag_word):
                                        if len(news_tagged_item['tag_type']) == 0:
                                            news_tagged_item['tag_type'] = news_tag
                                            news_tagged_item['tag_reason_type'] = reason_word
                                        elif news_tag == '利空':
                                            for tag_word in bad_obj[reason_word]:
                                                if all(tag_element in NEWS_TITLE for tag_element in tag_word) or all(
                                                    tag_element in NEWS_CONTENT for tag_element in tag_word):
                                                    if len(news_tagged_item['tag_type']) == 0:
                                                        news_tagged_item['tag_type'] = news_tag
                                                        news_tagged_item['tag_reason_type'] = reason_word
                                                        
                                                        # 更改交易日期为去除周六周末、法定节假日的时间生成序列中小于等于新闻日期的最大日期
                                                        for date in dw:
                                                            if date <= news_list[i][3]:
                                                                news_tagged_item['trading_date'] = date
                                                                
                                                                if len(news_tagged_item['tag_type']) != 0:  # 标签字段为空的新闻资讯,不入库显示
                                                                    print(news_tagged_item)
                                                                    news_tagged_list.append(news_tagged_item)

对需要进行的数据记录进行循环匹配,当一个字典里的数组元素的所有元素都在标题或新闻关键词中时,则可以给他标上相应的标签。当然这里可能存在一些文章最后并没有被打上标签,这里没对他做处理。

完整代码

import mysql.connector
import pandas as pd
from sqlalchemy import create_engine

# 连接数据库
def DBconn_base(database='********'):
    db_native = mysql.connector.connect(
        host="***.***.***.***",
        user="****",
        passwd="**********",
        database=database
    )
    cursor_native = db_native.cursor()
    return db_native, cursor_native

if __name__ == '__main__':
    # 从数据库中获取从东方财富爬取的各公司的新闻资讯
    dbconn, cursor = DBconn_base()  # 开启游标 开启连接
    cursor.execute(
        '''select *  from invest_report_news where update_time > (select max(update_time) from 
        wutong_native.invest_report_news_tag);''')
    news_list = pd.DataFrame(  # 每条记录转换成dataframe结构,取名为news_list
        cursor.fetchall(),
        columns=[x for x in cursor.column_names]
    ).values
    cursor.close()  # 关闭游标
    dbconn.close()  # 关闭连接
    
    news_tagged_list = []  # 定义一张标签有利好利空的数据库表,暂为空表
    tag_list = ['利好', '利空']  # 两个标签,下面是标签相对应的关键词
    reason_obj = {
        '利好': ['管理层或大股东增持', '公司回购股票', '盈利超预期', '签订重大合同'],
        '利空': ['管理层或大股东减持', '违款违法公告', '盈利低于预期', '定增预案']
    }
    good_obj = {
        '管理层或大股东增持': [
            ['股东', '增持'], ['高管', '增持'], ['董事', '增持'], ['董监高', '增持'], ['总经理', '增持'], ['控制人', '增持'], ['实控人', '增持'],
            ['拟', '增持'], ['此次增持'], ['计划', '增持'], ['实施', '增持'], ['继续增持'], ['持股比例', '增'], ['股份占比', '增']
        ],
        '公司回购股票': [
            ['回购', '股份'], ['回购', '股票'], ['收购', '股权'], ['拟', '回购', '股'], ['发布', '回购', '预案'], ['实施', '回购方案']
        ],
        '盈利超预期': [
            ['净利', '增'], ['盈利', '超', '预期'], ['业绩', '符合', '预期'], ['业绩', '超', '预期'], ['业绩', '好转'], ['业绩', '改善'],
            ['业绩', '大涨'], ['业绩', '反转'], ['业绩', '创', '新高'], ['业绩', '新纪录'], ['业绩', '高增长'], ['业绩', '爆发'], ['销量', '大涨'],
            ['销量', '暴涨'], ['业绩', '预增'], ['调高', '盈利增幅'], ['收入', '翻倍'],
            ['收入', '增'], ['预计', '盈利', '增'], ['净利', '预盈']
        ],
        '签订重大合同': [
            ['签', '重大', '合同'], ['签', '销售合同'], ['重大', '销售合同'], ['重大', '项目合同'], ['签', '订货合同'], ['签', '供货合同'],
            ['项目', '中标'], ['签', '战略', '合作协议']
        ]
    }
    bad_obj = {
        '管理层或大股东减持': [
            ['股东', '减持'], ['高管', '减持'], ['董事', '减持'], ['董监高', '减持'], ['总经理', '减持'], ['实控人', '减持'], ['控制人', '减持'],
            ['拟减持'], ['计划', '减持']
        ],
        '违款违法公告': [
            ['违款'], ['违法'], ['违规'], ['违约'], ['触犯', '法律'], ['违反', '法律'], ['违反', '法规'], ['违反', '规定']
        ],
        '盈利低于预期': [
            ['盈利', '低于预期'], ['业绩', '低于预期'], ['收入下降'], ['利润', '同比下降'], ['利润', '同比减少'], ['业绩', '恶化'],
            ['业绩', '下滑'], ['业绩', '亏损'], ['净利润亏损'], ['主营业务', '失败'], ['净利', '亏损'], ['净利', '下滑'], ['预计亏损']
        ],
        '定增预案': [
            ['定增预案'], ['定增', '计划', '募资'], ['拟定增', '募资'], ['定增', '申请', '募资'], ['定增', '计划', '募集'], ['拟定增', '募集'],
            ['定增', '申请', '募集']
        ]
    }
    
    dbconn, cursor = DBconn_base()  # 开启游标 开启连接
    # 查询语句的查找条件为新闻获取的update_time >= 历史新闻标签完成的最大update_time
    cursor.execute('''SELECT  trading_date from wutong_base.wt_ashare_info_calendar WHERE trading_date > (select 
    DATE_FORMAT( min(NEWS_DATE),'%Y-%m-%d')  from invest_report_news where UNIX_TIMESTAMP(update_time)  >= (select 
    UNIX_TIMESTAMP(max(update_time))  from wutong_native.invest_report_news_tag))  and  trading_date <= (select 
    DATE_FORMAT( max(NEWS_DATE),'%Y-%m-%d') from invest_report_news where UNIX_TIMESTAMP(update_time)  >= (select 
    UNIX_TIMESTAMP(max(update_time))  from wutong_native.invest_report_news_tag))  and exch_market = 'SZSE' order by 
    trading_date asc; ''')
    min_date = pd.DataFrame(
        cursor.fetchall(),
        columns=[x for x in cursor.column_names]
    ).values
    cursor.close()  # 关闭游标
    dbconn.close()  # 关闭连接
    dw = []
    for i in range(len(min_date)):
        dw.append(min_date[i][0])
        
        # 根据表长度循环,对每条新闻进行标签,更改交易时间等相关操作
        for i in range(len(news_list)):
            # print(str(i) + ':')  # 若不想打印结果中有字段都为空的行,可以删除该行code
            NEWS_ID = news_list[i][0]
            STOCK_CODE = news_list[i][1]
            STOCK_NAME = news_list[i][2]
            NEWS_DATE = news_list[i][3]
            NEWS_TITLE = news_list[i][4]
            NEWS_CONTENT = str(news_list[i][5])
            # UPDATE_TIME = news_list[i][6]
            
            # 定义成功标签利好利空后的新闻资讯列表
            news_tagged_item = {
                'news_id': NEWS_ID,  # 新闻ID,主键
                'stock_code': STOCK_CODE,
                # 新增字段股票代码数字
                'stock_code_figure': STOCK_CODE[:6],  # 该字段为取STOCK_CODE字段值的前6位
                'stock_name': STOCK_NAME,
                'news_date': NEWS_DATE,
                'news_title': NEWS_TITLE,
                # 新增字段交易时间
                'trading_date': '',  # 交易时间需要重新筛选,从日期生成序列里进行规则查找
                # 新增字段标签类型
                'tag_type': '',  # 利好 或 利空
                # 新增字段标签类型的在细分类的所属分类
                'tag_reason_type': '',
                # 新增字段更新时间
                # 'update_time': UPDATE_TIME  # 更新时间统一指定为操作更新的当前时间的时间戳
            }
            
            a = STOCK_NAME
            if a[:3] == '*ST':
                name = a[a.rfind(a[:3]):]
            elif a[:2] == 'ST':
                name = a[a.rfind(a[:2]):]
            elif '股份' in a or '证券' in a or '集团' in a or '控股' in a or '科技' in a or '医药' in a:
                name = a[0:-2]
            elif '中国' in a:
                name = a[a.rfind(a[:2]):]
            else:
                name = a
                
                # 先要判断新闻标题与内容与对应的公司股票相关名称name是否能够匹配
                # 利好、利空 标签分类,在双循环中进行标签所属关键词和新闻标题、新闻内容的判断操作
                if name in NEWS_TITLE or name in NEWS_CONTENT:
                    for news_tag in tag_list:
                        for reason_word in reason_obj[news_tag]:
                            if news_tag == '利好':
                                for tag_word in good_obj[reason_word]:
                                    if all(tag_element in NEWS_TITLE for tag_element in tag_word) or all(
                                        tag_element in NEWS_CONTENT for tag_element in tag_word):
                                        if len(news_tagged_item['tag_type']) == 0:
                                            news_tagged_item['tag_type'] = news_tag
                                            news_tagged_item['tag_reason_type'] = reason_word
                                        elif news_tag == '利空':
                                            for tag_word in bad_obj[reason_word]:
                                                if all(tag_element in NEWS_TITLE for tag_element in tag_word) or all(
                                                    tag_element in NEWS_CONTENT for tag_element in tag_word):
                                                    if len(news_tagged_item['tag_type']) == 0:
                                                        news_tagged_item['tag_type'] = news_tag
                                                        news_tagged_item['tag_reason_type'] = reason_word
                                                        
                                                        # 更改交易日期为去除周六周末、法定节假日的时间生成序列中小于等于新闻日期的最大日期
                                                        for date in dw:
                                                            if date <= news_list[i][3]:
                                                                news_tagged_item['trading_date'] = date
                                                                
                                                                if len(news_tagged_item['tag_type']) != 0:  # 标签字段为空的新闻资讯,不入库显示
                                                                    print(news_tagged_item)
                                                                    news_tagged_list.append(news_tagged_item)
                                                                    
                                                                    # 将标明利好利空与交易日期后的新闻资讯列表存储进数据库
                                                                    data_frame = pd.DataFrame(news_tagged_list)
                                                                    engine = create_engine(
                                                                        'mysql+mysqlconnector://****:*********@***.***.***.***:3306/****?charset=utf8')
                                                                    data_frame.to_sql(
                                                                        name='invest_report_tag',
                                                                        schema='stock_news',
                                                                        con=engine,
                                                                        index=False,
                                                                        if_exists='append'
    )