【财报网站(开源)笔记一】Python获取A股上市公司财报并写入数据库

1,539 阅读5分钟

一、财报数据源(python)

有两大块数据需要入库:上市公司基本信息、上市公司报表。

1.1 上市公司的基本信息

通过tushare库可以直接获取A股上市公司的基本信息,详见:tushare.pro/document/2?… 注册后获取自己的【接口token】

tushare-接口token.png

代码如下:

import tushare
pro = tushare.pro_api(接口token)
allStockSourceData = pro.query('stock_basic', exchange='', list_status='L', fields='ts_code,symbol,name,area,industry,list_date')

以上得到的allStockSourceData数据结构是DataFrame,它的columns可以读出query方法里传入的fields的6个字段,即:ts_code,symbol,name,area,industry,list_date。这些(加上start_date、end_date)将作为数据库的字段名,写入到上市公司基本信息表里。而allStockSourceData里的vaules,则包含了所有股票对应的6个数据。创建数据库代码片段、效果如下:

def createDB(dbName, dbcursor):
    try:
        createDBSentence = "create database if not exists " + dbName
        dbcursor.execute(createDBSentence)
        dbcursor.execute("use " + dbName + ";")
        # print('create db', dbName, ' successfully!')
    except Exception as e:
        print('create db {} fail , e={}'.format(dbName, e))

所有上市公司基本信息.png

问题1、为什么从tushare取得6个字段,还要加上 start_date、end_date 两个字段呢?

  • 每家公司的财报,都有起始时间、目前最新报表时间。前端显示时,让用户选取年份报表,需要有选择范围,所以需要这两个字段。如下图:

报表年份范围.png

问题2、这两个字段的数据又是怎么来的呢?

  • 后面将爬取每个公司历年来所有报表,取最久远的日期作为start_date的值,最新的作为end_date的值*

问题3、得到这两个数据又怎么加入数据库呢?

  • python直接更新数据库:
"update all_stock_info set start_date='%s', end_date='%s' where symbol='%s';" % (s, e, code)

1.2 三大报表

本节三大重点:1、下载报表;2、读报表文件-解析数据-写入数据库;3、创建标题名字文件(前端用来显示)

1.2.1 下载报表

三大报表在tushare上数据很丰富,但要很高积分才能调取接口。所以……

于是找了免费的数据源 -- 网易财经。三大报表地址如下(以“平安银行,symbol=000001为例):

利润表:quotes.money.163.com/service/lrb…

资产负债表:quotes.money.163.com/service/zcf…

现金流量表:quotes.money.163.com/service/xjl…

现在三大表地址已经齐了,全部上市公司基本信息也有了(即有了symbol),接下来遍历上文 1.1 拿到的所有上市公司数据,取其symbol,串成这家公司的三张报表地址,python把所有报表下载下来,数据源就齐了!就等入库!

代码片断:

def downloadReport(stockList, url, path):
    failList = []
    for symbol in stockList:
        loadUrl = str(url).replace('%name%', str(symbol))
        fileName = str(symbol)
        while True:
            try:
                savePath = str(path).replace('%name%', fileName)
                urllib.request.urlretrieve(loadUrl, savePath)
                time.sleep(0.3)
                break
            except Exception as e:
                failList.append(fileName)
                if str(e) == 'HTTP Error 404: Not Found':
                    break
                else:
                    print('load {} fail ~ {}'.format(str(symbol), e))
                    continue
incomeUrl = "http://quotes.money.163.com/service/lrb_%name%.html"
incomeFolder = "./income_statements/income_%name%.csv"
downloadReport(stockList, incomeUrl,incomeFolder)

1.2.2 读报表文件-解析数据-写入数据库

三大报表都下载完成,开始解析这些表,并写入数据库

1、数据库结构

三大表对应三个数据库(资产负债表数据库finance_report_balance、利润表数据库finance_report_income、现金流量表数据库finance_report_cash。),全部公司的三大报表各自写到对应库中。创建数据库请看上文createDB方法。

三大报表数据库.png

2、数据入库 三大表文件格式为csv,python直接读表,思路如下(以利润表为例):

报表解析01.png

(1)得到数据库字段名insertTitleHead:数据库title转为英文,因为所有公司的同一类型报表,此列所有字段都一样且全部齐全,所以直接按顺序从上到下把第一个【报告日期】译成"report_date"之后,其他所有译成income_1、income_2......(资产负债表balance_1、balance_2...;现金流量表cash_1、cash_2...),而每个字段对应的中文写到一个文件里,这就是下文《1.2.3 创建标题名字文件》。

(2)得到数据库全部数据:除了第一列作为字段名,剩余的就是一条数据的内容。每条数据数量、值与上面insertTitleHead(report_date、income_1、income_2...)一一对应。

(3)、写入数据库(写入title、data)

创建表~create table if not exists income_000001 (report_date VARCHAR(767), primary key (report_date), income_1 float, income_2 float, ..., income_45 float)

写入单条data~insert into income_000001 (report_date, income_1, income_2, ..., income_45) values ("2020-12-31", 15354200, null, ..., 1.4) 重复写入全部data

写完后数据库如下:

报表数据库01.png

请参照源码:

1.2.3 创建标题名字文件(前端用来显示)

每解析一种表,都会保存第一列标题到一个字典里。

资产负债表:{"report_date":"报告日期","balance_1":"货币资金(万元)","balance_2":"结算备付金(万元)",...}

利润表:{"report_date":"报告日期","income_1":"营业总收入(万元)","income_2":"营业收入(万元)",...}

现金流量表:{"report_date":"报告日期","cash_1":" 销售商品、提供劳务收到的现金(万元)","cash_2":" 客户存款和同业存放款项净增加额(万元)",...}

当全部表完成了,合并三个对象,并转成report_title.json保存下来,前端显示需要先加载此文件。服务器只返回数据表的字段名:income_1, income_2, ..., web端需要把这些字段名转译成中文。

问题:

(1)为什么这些字段直接用_1、2这样,而不是直接翻译中文意思或是用拼音呢? ~~因为简洁一点,如果用翻译,工作量太大,如果以后再一加项指标,就需要人工再去翻译,而做不到自动化了。不翻译以后加一项,就会加一个income** : "中文意思",pyhon照常入库,java照常读表,web照常读json解析对应中文。

(2)为什么要写成report_title.json保存下来,而不入库? ~减小服务器的访问量

三、脚本部署

1、crontab定时执行以上的python。

2、如何避免每次都全量执行

第一次执行python,全下载所有报表,并生成数据库。以后只做增量,不能再全量。

避免全量方法:

1、检测是否第一次: 在获取所有上市公司信息的时候,尝试创建table,此时如果table已经存在,python会返回异常,说明已经全量过,此次不用全量,标记isWhole=false。

2、对比每家上市公司上市日期: 如果日期小于今天,则已经下载过。否则是今天上市,需要将此公司入库详细信息数据库,并且尝试去下载此公司的三大报表(确定上市当天没有报表的话就不用下载)。