按照老师的要求,依据证监会所发布的最新一版的行业分类,笔者匹配到了电气机械和器材制造业的前10家公司。现将该行业前10家上市公司的基本信息列示如下。所属行业匹配规则的代码实现过程见后文附录部分
上市公司代码 | 上市公司简称 |
---|---|
000049 | 德赛电池 |
000070 | 特发信息 |
000333 | 美的集团 |
000400 | 许继电气 |
000521 | 长虹美菱 |
000533 | 顺钠股份 |
000541 | 佛山照明 |
000921 | 格力电器 |
000651 | 海信家电 |
002828 | 思源电气 |
该部分函数旨在定义爬取各大交易所交易所定期报告页面的函数, 并对爬取之后的页面进行信息提取。即提取出每一家公司的年报的名称、 pdf的链接等等,最终将这些指标保存为一个CSV文件。
import time
import re
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
def get_table_szse(code):
driver = webdriver.Edge()
driver.get("http://www.szse.cn/disclosure/listed/fixed/index.html")
driver.set_window_size(1552, 832)
time.sleep(3)
driver.find_element(By.ID, "input_code").click()
driver.find_element(By.ID, "input_code").send_keys(code)
time.sleep(3)
driver.find_element(By.ID, "input_code").send_keys(Keys.DOWN)
driver.find_element(By.ID, "input_code").send_keys(Keys.ENTER)
driver.find_element(By.CSS_SELECTOR, "#select_gonggao .c-selectex-btn-text").click()
time.sleep(3)
driver.find_element(By.LINK_TEXT, "年度报告").click()
time.sleep(3)
driver.find_element(By.CSS_SELECTOR, ".input-left").click()
driver.find_element(By.CSS_SELECTOR, "#c-datepicker-menu-1 .calendar-year span").click()
driver.find_element(By.CSS_SELECTOR, ".active li:nth-child(113)").click()
driver.find_element(By.CSS_SELECTOR, "#c-datepicker-menu-1 tr:nth-child(1) > .available:nth-child(3) > .tdcontainer").click()
driver.find_element(By.CSS_SELECTOR, "#c-datepicker-menu-2 tr:nth-child(2) > .weekend:nth-child(1) > .tdcontainer").click()
driver.find_element(By.ID, "query-btn").click()
element = driver.find_element(By.ID, 'disclosure-table')
# add
time.sleep(5)
table_html = element.get_attribute('innerHTML')
fname = '{}.html'.format(code)
f = open(r"E:\张艺轩\计算机实验\金融数据获取与处理\nianbao\src\nianbao\data\HTML_Table\{}".format(fname),'w',encoding='utf-8')
f.write(table_html)
f.close()
driver.quit()
#循环获取
def get_table_szse_codes(codes):
for code in codes:
get_table_szse(code)
#HTML解析
def get_szse_data(tr):
p_td = re.compile('(.*?)',re.DOTALL)
tds = p_td.findall(tr) #tds是一个列表,包含四个 标签对
# 代码
code_compile = re.compile('code.*?>(.*?)',re.DOTALL)
code = code_compile.findall(tds[0])[0]
# 名称
name_compile = re.compile('title=.*?>(.*?)')
name = name_compile.findall(tds[1])[0]
# pdf地址
pdf_compile = re.compile('(.*?)',re.DOTALL)
title = title_date_compile.findall(tds[2])[0]
date = title_date_compile.findall(tds[3])[0]
data = [code,name,herf,title,date]
return data
def parse_szse_table(fname):
f = open(fname,encoding = 'utf-8')
html = f.read()
f.close()
#
p = re.compile('(.+?) ',re.DOTALL)
trs = p.findall(html)
# 找出不为空的“tr对”
trs_new = []
for tr in trs:
if tr.strip() != '':
trs_new.append(tr)
#del trs_new[0]
# 应用get_data()函数和提取code、name、herf、title 和 date;
data_all = [get_szse_data(tr) for tr in trs_new[1:]]
df = pd.DataFrame({'code':[d[0] for d in data_all],
'name':[d[1] for d in data_all],
'href':[d[2] for d in data_all],
'title':[d[3] for d in data_all],
'date':[d[4] for d in data_all]})
# 将时间序列标准化
df['date'] = df['date'].apply(pd.to_datetime)
#
return df
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import time
def get_table_sse(code):
browser = webdriver.Edge()
#以下皆为用selenium.IDE录制的导出结果
browser.get("http://www.sse.com.cn/disclosure/listedinfo/regular/")
# 2 | setWindowSize | 1552x832 |
browser.set_window_size(1552, 832)
# 3 | click | id=inputCode |
browser.find_element(By.ID, "inputCode").click()
# 4 | type | id=inputCode | 601919
browser.find_element(By.ID, "inputCode").send_keys("601919")
# 5 | click | css=.sse_outerItem:nth-child(4) .filter-option-inner-inner |
time.sleep(5) #在此等待5秒
browser.find_element(By.CSS_SELECTOR, ".sse_outerItem:nth-child(4) .filter-option-inner-inner").click()
# 6 | click | linkText=年报 |
browser.find_element(By.LINK_TEXT, "年报").click()
# 7 | select | css=.dropup > .selectpicker | label=年报
dropdown = browser.find_element(By.CSS_SELECTOR, ".dropup > .selectpicker")
dropdown.find_element(By.XPATH, "//option[. = '年报']").click()
#
css_selector = "body > div.container.sse_content > div > div.col-lg-9.col-xxl-10 > div > div.sse_colContent.js_regular > div.table-responsive > table"
element = browser.find_element(By.CSS_SELECTOR, css_selector)
table_html = element.get_attribute('innerHTML')
f = open('601919年报.html','w',encoding='utf-8')
f.write(table_html)
f.close()
browser.quit()
#循环获取
def get_table_sse_codes(codes):
for code in codes:
get_table_sse(code)
def get_data(tr):
p_td = re.compile('(.*?)',re.DOTALL)
tds = p_td.findall(tr) #tds是一个列表,包含四个 标签对
#
s = tds[0].find('>')+1 #第一个标签对的起始索引和结束索引
e = tds[0].rfind('<')
code = tds[0][s:e]
#
s = tds[1].find('>')+1
e = tds[1].rfind('<')
name = tds[1][s:e]
#
s = tds[2].find('herf="') + 6
e = tds[2].find('.pdf"') + 4
herf = 'https://www.sse.com.cn' + tds[2][s:e]
s = tds[2].find('$(this))">') + 10
e = tds[2].find('')
title = tds[2][s:e]
date = tds[3].strip()
data = [code,name,herf,title,date]
return data
#用函数来封装上述代码
def parse_table(table_html):
p = re.compile('(.+?) ',re.DOTALL)
trs = p.findall(html)
trs_new = []
for tr in trs:
if tr.strip() != '':
trs_new.append(tr)
data_all = [get_data(tr) for tr in trs_new[1:]]
df = pd.DataFrame({'code':[d[0] for d in data_all],
'name':[d[1] for d in data_all],
'href':[d[2] for d in data_all],
'title':[d[3] for d in data_all],
'date':[d[4] for d in data_all]})
return df
import pandas as pd
from datetime import datetime
def filter_links(words,df,include = True):
ls = []
for word in words:
if include:
ls.append([word in f for f in df['title']])
else:
ls.append([word not in f for f in df['title']])
index = []
for r in range(len(df)):
flag = not include
for c in range(len(words)):
if include:
flag = flag or ls[c][r]
else:
flag = flag and ls[c][r]
index.append(flag)
df2 = df[index]
return df2
def filter_date(start,end,df):
start = pd.to_datetime(start)
end = pd.to_datetime(end)
date = df['date']
v = [d >= start and d <= end for d in date]
df_new = df[v]
return df_new
def start_end_10y():
df_now = datetime.now()
current_year = df_now.year
start = f'{current_year-9}-01-01'
end = f'{current_year}-12-31'
return (start,end)
#集成过滤函数
def filter_nb_10y(df,keepwords = ['年报','年度报告'],
exclude_word = ['摘要','更新后','已取消','更正后','英文版','英文']
,start = '',end = ''):
if start == '':
start,end = start_end_10y()
else:
start_y = int(pd.to_datetime(start[0:4]))
end = '{}-12-31'.format(start_y + 9)
#
df = filter_links(keepwords,df,include=True)
df = filter_links(exclude_word,df,include= False)
df = filter_date(start,end,df)
return df
def prepare_hrefs_years(df):
hrefs = list(df['href'])
years = [str(int(d[0:4])-1) for d in df['date']]
code = list(df['code'])
return pd.DataFrame([code,hrefs, years],index = ['code','href','year']).T
import requests
import pandas as pd
import time
import os
def download_pdf(href,code,year):
r = requests.get(href, allow_redirects=True)
path = "E:\\张艺轩\\计算机实验\\金融数据获取与处理\\nianbao\\src\\nianbao\\data"
if os.path.exists(path+'\\{}'.format(code)) == True:
fname = '{}_{}.pdf'.format(code,year)
f = open(path+'\\{}\\{}'.format(code,fname), 'wb')
f.write(r.content)
f.close()
r.close()
else:
os.mkdir(path)
fname = '{}_{}.pdf'.format(code,year)
f = open(path+'\\{}\\{}'.format(code,fname), 'wb')
f.write(r.content)
f.close()
r.close()
def download_pdfs(hrefs,code,years):
for i in range(len(hrefs)):
href = hrefs[i]
year = years[i]
download_pdf(href,code,year)
time.sleep(30)
return()
# 集成上述函数
def download_pdfs_code(list_hrefs,codes,list_years):
for i in range(len(list_hrefs)):
hrefs = list_hrefs[i]['href']
years = list_years
code = codes[i]
download_pdfs(hrefs,code,years)
return()
该部分利用之前定义的函数,先爬取HTML提取信息,过滤后保存为CSV文件。 而后通过读取CSV文件,访问每一张DataFrame的href列,获取pdf下载链接, 最终爬取到10家上市公司的10年的pdf格式的年报。其中,为了便于文件管理,笔者还引入了 os模块进行操作。运行结果如下所示。
from sse import get_table_sse,get_table_sse_codes,get_data,parse_table
from szse import get_table_szse,get_table_szse_codes,get_szse_data,parse_szse_table
from filter_url import filter_links,filter_date,start_end_10y,filter_nb_10y,prepare_hrefs_years
from download import download_pdf,download_pdfs,download_pdfs_code
import pandas as pd
import numpy as np
from pdf_parse import get_target_subtxt,get_th_span,get_bounds,get_keywords,parse_key_fin_data,file_name_walk,pdf_sum_pa,get_target
import fitz
import re
import os
# ----------------------------------------HTML爬取-------------------------------------------------------
# 建立股票列表,以便于进行调用
# 必须dtype = object,否则前面的0将省略
elec_stock = pd.read_csv(r'E:/张艺轩/计算机实验/金融数据获取与处理/nianbao/src/nianbao/股票代码.csv',
encoding= 'UTF-8',dtype = object)
elec_stock.pop('Unnamed: 0')
elec_stock = elec_stock[['上市公司代码', '上市公司简称']]
# 建立一个文件夹路径备用
paths = "E:\\张艺轩\\计算机实验\\金融数据获取与处理\\nianbao\\src\\nianbao\\data"
# 如果股票代码是以szcode列表里列示的开头方式,则使用深交所href获取方式,否则用上交所href获取方式
# 以CSV格式保存
szcode =('200','300','301','000','080','00')
for index,row in elec_stock.iterrows():
code = row[0]
name = row[1]
if code.startswith(szcode) ==True:
get_table_szse(code)
else:
get_table_sse(code)
# 过滤已经保存的CSV文件,去掉含有'摘要','已取消','英文版','英文'和'更新前'等字段的链接
obj = os.listdir(r"E:\张艺轩\计算机实验\金融数据获取与处理\nianbao\src\nianbao\data\HTML_Table")
for i in obj:
df = parse_szse_table(r'{}\HTML_Table\{}'.format(paths,i))
df = filter_nb_10y(df,keepwords = ['年报','年度报告','更新后','更正后'],
exclude_word = ['摘要','已取消','英文版','英文','更新前']
,start = '',end = '')
df.to_csv('{}\\HREF_Data\\{}.csv'.format(paths,i[0:6]))
#------------------------------------------下载年报-----------------------------------------------------
# 整理CSV,整理出符合要求的href列表
obj1 = os.listdir(r"E:\张艺轩\计算机实验\金融数据获取与处理\nianbao\src\nianbao\data\HREF_Data")
list_hrefs = []
for i in obj1:
df = pd.read_csv(paths+'\\HREF_Data\\{}'.format(i),dtype = object)
temp = prepare_hrefs_years(df)
list_hrefs.append(temp)
# 获取年报
download_pdfs_code(list_hrefs,elec_stock['上市公司代码'],list_hrefs[0]['year'])
所爬取的HTML文件列表、从CSV当中提取的信息组成的CSV文件及文件列表(以“美的集团”)以及10家公司年报列表如下所示。
import fitz
import re
import pandas as pd
import os
# 定位需要的内容
def get_target_subtxt(doc,bounds = (['主要会计数据及财务指标','主要会计数据和财务指标'],'归属于上市公司股东的净资')): #
# 默认设置为首页页码
start_pageno = 0
end_pageno = len(doc) - 1
# 获取上界页码
for n in range(len(doc)):
# texts = page.get_text()
if (bounds[0][0] in doc[n].get_text()) or (bounds[0][1] in doc[n].get_text()):
start_pageno = n
break
# 获取下界页码
for i in range(start_pageno,len(doc)):
p = re.compile('(?:\s*\n*归\s*\n*属\s*\n*于\s*\n*上\s*\n*市\s*\n*公\s*\n*司\s*\n*股\s*\n*东\s*\n*的\s*\n*净\s*\n*资\s*\n*产\s*\n*)',re.DOTALL)
a = p.findall(doc[i].get_text())
if len(a)==0:
continue
elif bounds[1] in a[0].replace('\n','').replace(' ',''):
end_pageno = i
break
# 获取界定的内容
txt = ''
if start_pageno == end_pageno:
txt = doc[start_pageno].get_text()
else:
for _ in range(start_pageno,end_pageno + 1):
page =doc[_]
txt += page.get_text()
return txt
# 获取界定
def get_th_span(txt):
nianfen = '(20\d\d|199\d)\s*年末?' #
s = '{}\s*{}.*?{}'.format(nianfen,nianfen,nianfen)
p = re.compile(s,re.DOTALL)
matchobj_ = p.search(txt)
#
end = matchobj_.end()
year1 = matchobj_.group(1)
year2 = matchobj_.group(2)
year3 = matchobj_.group(3)
#
flag = (int(year1) - int(year2) == 1) and (int(year2) - int(year3) == 1)
while not flag:
matchobj_.search(txt[end:])
end = matchobj_.end()
year1 = matchobj_.group(1)
year2 = matchobj_.group(2)
year3 = matchobj_.group(3)
flag = int(year1) - int(year2) == 1
flag = flag and (int(year2) - int(year3) == 1)
return ([year1,year2,year3],matchobj_.span())
def get_bounds(txt):
th_span_1st = get_th_span(txt)[1]
end = th_span_1st[1]
th_span_2nd = get_th_span(txt[end:])[1]
th_span_2nd = (end + th_span_2nd[0],end + th_span_2nd[1])
#
s = th_span_1st[1]
e = th_span_2nd[0]-1
while txt[e] not in '0123456789':
e = e-1
return (s,e+1)
# 提取关键字
def get_keywords(subtxt):
p = re.compile(r'\d+\s*?\n\s*?([\u2E80-\u9FFF]+)')
keywords = p.findall(subtxt)
if '营业收入' not in keywords:
keywords.insert(0,'营业收入')
for i in keywords:
if len(i) <= 3:
keywords.remove(i)
for x in keywords:
if ('股份有限公司' or '年度报告') in x:
keywords.remove(x)
return keywords
# 提取成表格
def parse_key_fin_data(subtxt,keywords):
'''
将已知的数据提取成表格
'''
ss = []
s= 0
for kw in keywords:
n = subtxt.find(kw,s)
ss.append(n)
s = n +len(kw)
ss.append(len(subtxt))
data = []
p = re.compile('\D+(?:\s+\D*)?(?:(.*)|\(.*\))?')
p2 = re.compile('\s')
p3 = re.compile('(\s*\n*\-*(\d{1,3}(?:,\d{3})*(?:\.\d+)?(?:\%)?)\s*){3,4}',re.DOTALL)
for n in range(len(ss)-1):
s = ss[n]
e = ss[n+1]
line = subtxt[s:e]
# 获取可能换行的账户名
matchobj = p.search(line)
account_name = matchobj.group().replace('\n','')
account_name = p2.sub('',matchobj.group())
# 获取3年数据
#amnts = line[matchobj.end():].split()
#
matchobjs = p3.search(line)
amnts = matchobjs.group().split()
#加上账户名称
amnts.insert(0,account_name)
#追加到总数据
data.append(amnts)
return data
#
def pdf_sum_pa(paths,fil_name):
'''
将上述函数汇总,并将解析出来的结果
保存为一个csv文件
'''
pdf_path = '{}\{}'.format(paths,fil_name)
doc = fitz.open(r'{}'.format(pdf_path)) # r'E:/张艺轩/计算机实验/金融数据获取与处理/nianbao/src/nianbao/data/pdf_data/002028/002028_2022.pdf'
# 获取大致范围的文本
txt = get_target_subtxt(doc)
# 建立列名
col = [ x for x in get_th_span(txt)[0]]
col.insert(-1,'变动')
col.insert(0,'指标')
# 获取精确范围
span = get_bounds(txt)
subtxt = txt[span[0]:span[1]]
# 获取项目名称
keywords = get_keywords(subtxt)
# 建立成列表族
datas = parse_key_fin_data(subtxt,keywords)
# 换成DataFame
df = pd.DataFrame(datas,columns=col)
new_dirs = 'E:/张艺轩/计算机实验/金融数据获取与处理/nianbao/src/nianbao/data/csv_data'
dir_name = new_dirs+'\{}'.format(fil_name[0:6])
if os.path.exists(dir_name) == False:
os.makedirs(dir_name)
df.to_csv(r'{}\{}_{}.csv'.format(dir_name,fil_name[0:6],fil_name[7:-4]))
else:
df.to_csv(r'{}\{}_{}.csv'.format(dir_name,fil_name[0:6],fil_name[7:-4]))
#
def file_name_walk(file_dir):
'''
定义一个返回文件所在的绝对路径和
文件名的列表的函数
'''
file_path_lst = []
for x in os.walk(file_dir):
file_path_lst.append((x[0],x[2]))
return file_path_lst
# 临时定义一个起始页码的函数
def get_target(doc,bounds =['股票简称','信息披露及备置地点']):
# 默认设置为首页页码
start_pageno = 0
# 获取上界页码
for n in range(len(doc)):
# texts = page.get_text()
if (bounds[0] or bounds[0]) in doc[n].get_text():
start_pageno = n
break
return start_pageno
该部分内容,充分利用正则表达式,旨在按要求提取出10家上市公司“公司简称”“股票代码”“办公地址”“公司网址”“董秘电话”“董秘&公司电子信箱”等指标的关键信息。 指标数据以每家公司最新的年报为准。
# ---------------------------------------提取公司信息------------------------------------------------
# 利用file_name_walk找出pdf_data下所有文件的路径和文件名
paths = "E:\\张艺轩\\计算机实验\\金融数据获取与处理\\nianbao\\src\\nianbao\\data\\pdf_data"
paths_pos = file_name_walk(paths)
del paths_pos[0]
# 创建一个只包含每只股票2022年年报绝对路径的列表,然后遍历调用
abs_pos_lst = []
for i in file_name_walk(paths):
for n in i[1]:
if '2022' in n:
abs_pos = '{}/{}'.format(i[0],n)
abs_pos_lst.append(abs_pos)
else:
continue
# 提前创建一个表格,以便于存储数据
keys_Data = pd.DataFrame(columns=['公司简称','股票代码','办公地址','公司网址','董秘电话','董秘&公司电子信箱'])
for i in abs_pos_lst:
docs = fitz.open(i)#i[0]+'\\{}'.format(n)
brief_ =docs[get_target(docs,bounds =['股票简称','信息披露及备置地点'])].get_text()
# name
cor_name_compile = re.compile('.*股票简称.*?\n(.*?)\s*\n*股票代码.*',re.DOTALL)
name = cor_name_compile.findall(brief_)[0].strip()
# code
cor_code_compile = re.compile('.*股票代码.*?\n(.*?)\s.*',re.DOTALL)
code = cor_code_compile.findall(brief_)[0].strip()
# address
cor_address_compile = re.compile('办公地址.*?\n(.*?)\s*\n.*?',re.DOTALL)
address = cor_address_compile.findall(brief_)[0].strip()
# web
cor_web_compile = re.compile('公司网址.*?\n\s*(.*?)\n电子信箱',re.DOTALL)
web = cor_web_compile.findall(brief_)[0].strip()
# secretary_telephone
sec_tel_compile = re.compile('.*电话.*?\n\s*(.*?)\s*\n.*?',re.DOTALL)
tele = sec_tel_compile.findall(brief_)[0].strip()
# secretary_mail
sec_mail_compile = re.compile('.*电子信箱.*?\n\s*(.*?)\n.*',re.DOTALL)
mail = sec_mail_compile.findall(brief_)[0].strip()
keys_Data.loc[len(keys_Data.index)] = {'公司简称':name,'股票代码':code,
'办公地址':address,'公司网址':web,
'董秘电话':tele,'董秘&公司电子信箱':mail}
keys_Data.to_csv('E:/张艺轩/计算机实验/金融数据获取与处理/nianbao/src/nianbao/data/corperate_info.csv',encoding = 'ANSI')
此处在提取会计数据表格时,笔者使用了“try-except”异常处理结构:如果格式正常,则正常执行“try”后的语句; 如果解析格式异常,则打印错误类型,执行except后面的内容(用pdfplumber提取)。这样就保证了保证程序的正常运行。
在本例中,只有代码为002028的公司2020年与2021年的年报解析失败,原因用fitz读取换行符太多。利用异常处理结构对该错误进行修正的结果见下文的“错误解决.png”。
#-------------------------------------------表格提取------------------------------------------------------
# 提取每家公司每年的主要会计数据和财务指标,并保存为csv数据
file_dir = 'E:/张艺轩/计算机实验/金融数据获取与处理/nianbao/src/nianbao/data/pdf_data'
pdf_path = file_name_walk(file_dir)
del pdf_path[0]
for ele in pdf_path[-2:]:
for name_ in ele[1]:
# 利用异常处理结构,保证程序正常运行
try:
pdf_sum_pa(ele[0],name_)
except Exception as e:
# 002028的2020与2021解析失败,原因用fitz读取换行符太多
print('--------------------------------------------------')
print(name_,'出现异常.错误类型为:',e)
# 利用pdfplumber进行提取
new_pos = '{}/{}'.format(ele[0],name_)
new_dirs = 'E:/张艺轩/计算机实验/金融数据获取与处理/nianbao/src/nianbao/data/csv_data'
dir_name = new_dirs+'\{}'.format(name_[0:6])
import pdfplumber
page = pdfplumber.open(new_pos).pages
for i in page:
if '主要会计数据和财务指标' in i.extract_text():
df = pd.DataFrame(i.extract_table())
df.columns = df.iloc[0]
df.drop(0,inplace = True)
df.to_csv(r'{}\{}_{}.csv'.format(dir_name,name_[0:6],name_[7:-4]))
#
print(name_,'错误已解决,内容成功为pdfplumber所提取')
接下来,从提取出来的表格提取“营业收入”“归属于上市公股东的净利润” 等字段在当年的数据,按照公司为类别合并成时间序列类型的表格。 执行代码如下所示。具体结果见下文“执行结果”一栏。
# ----获取每一家公司由10年“营业收入”“归属于上市公司股东的净利润”以及“基本每股收益”组成的的时间序列表格------
file_dir = 'E:/张艺轩/计算机实验/金融数据获取与处理/nianbao/src/nianbao/data/csv_data'
# file_name_walk获取文件名和路径,返回一个列表
file_pos = file_name_walk(file_dir)
del file_pos[0]
Series_Data_Path = 'E:/张艺轩/计算机实验/金融数据获取与处理/nianbao/src/nianbao/data/Series_data'
for i in file_pos:
# 对于每家公司,建立一个合并的对象(列表)
joint_lst = []
for x in i[1]:
df = pd.read_csv('{}/{}'.format(i[0],x),dtype = object,encoding = 'UTF-8')
if 'Unnamed: 0' in df.columns:
df = df.drop('Unnamed: 0', axis=1)
# 每一张表可能有不同的情形,可以将所有的情况进行列示
lst = ['营业收入(元)-','营业收入(元)','营业收入','营业收入(千元)',
'归属于上市公司股东的净利润(元)-',
'归属于上市公司股东的净利润(元)','归属于上市公司股东的净利润(千元)',
'归属于上市公司股东的净利润','基本每股收益(元/股)-','基本每股收益(元/股)']
# 用isin函数进行提取
df = df[df['指标'].isin(lst)].iloc[:,0:2]
# 整理表格
df.index = range(len(df))
df = df.T
df.columns = df.iloc[0]
df.drop(index = '指标',inplace = True)
# 添加合并对象
joint_lst.append(df)
# 由10年“营业收入”“归属于上市公司股东的净利润”以及“基本每股收益”组成的的时间序列表格
Series_Data = pd.concat(joint_lst,axis = 0)
#
for i in Series_Data.index:
# 用any判断是否有些行的内容与列名相同,如果True,则删去与列相同的行
if any(Series_Data.columns == Series_Data.loc[i]) == True:
Series_Data.drop(i,inplace = True)
# 以CSV的格式进行保存
Series_Data.to_csv('{}/{}.csv'.format(Series_Data_Path,x[0:6]),encoding = 'ANSI')
将每一个指标(以“营业收入”为例)时间序列按公司合并, 整理成时间序列类型的数据,方便后续绘图调用。
series_pos = 'E:/张艺轩/计算机实验/金融数据获取与处理/nianbao/src/nianbao/data/Series_data'
csv_name = os.listdir(series_pos)
# 创建包含代码名称的映射字典
code_ = ['000049','000070','000333','000400','000521','000533','000541','000921','000651','002028']
values = ['德赛电池','特发信息','美的集团','许继电气','长虹美菱','顺钠股份','佛山照明','格力电器','海信家电','思源电气']
nvs = zip(code_,values)
nvDict = dict( (name,value) for name,value in nvs)
# 并表
merge_lst = []
for _ in csv_name:
df = pd.read_csv('{}/{}'.format(series_pos,_),encoding = 'ANSI',dtype = object,index_col = 0)
df_revenue = df.iloc[:,0] #这里只提取营业收入,改变列索引,就可以提取其他的列
df_revenue.rename(nvDict[_[0:-4]],inplace = True)
df_revenue.columns = [nvDict[_[0:-4]]]
merge_lst.append(df_revenue)
revenue_series = pd.concat(merge_lst,axis = 1)
revenue_series = revenue_series.applymap(lambda x : x.replace(',','')).applymap(pd.to_numeric)
revenue_series['美的集团'] = revenue_series['美的集团'].apply(lambda x :x*1000)
revenue_series = revenue_series.applymap(lambda x : x/1000000000)
# 合并上市公司股东净利润的时间序列表格
merge_lst = []
for _ in csv_name:
df = pd.read_csv('{}/{}'.format(series_pos,_),encoding = 'ANSI',dtype = object,index_col = 0)
df_profit = df.iloc[:,1]
df_profit.rename(nvDict[_[0:-4]],inplace = True)
df_profit.columns = [nvDict[_[0:-4]]]
merge_lst.append(df_profit)
profit_series = pd.concat(merge_lst,axis = 1)
profit_series = profit_series.applymap(lambda x : x.replace(',','')).applymap(pd.to_numeric)
profit_series['美的集团'] = profit_series['美的集团'].apply(lambda x :x*1000)
profit_series = profit_series.applymap(lambda x : x/1000000000)
公司简称 | 股票代码 | 办公地址 | 公司网址 | 董秘电话 | 董秘& 公司电子信箱 |
---|---|---|---|---|---|
德赛电池 | 000049 | 深圳市南山区高新科技园南区高新南一道德赛科技大厦东座 26 楼 | www.desaybattery.com.cn | (0755)862 99888 | IR@desaybattery.com |
特发信息 | 000070 | 深圳市南山区高新区中区科丰路 2 号特发信息港大厦 B 栋 18 楼 | www.sdgi.com.cn | 0755-66833901 | sdgi_dmc@sdgi.com.cn |
美的集团 | 000333 | 广东省佛山市顺德区北滘镇美的大道 6 号美的总部大楼 | http://www.midea.com | 0757-22607708 | IR@midea.com |
许继电气 | 000400 | 河南省许昌市许继大道 1298 号 | http://www.xjec.com/ | 0374-3213660 | xjdqzqb@163.com |
长虹美菱、虹美菱 B | 000521、200521 | 合肥市经济技术开发区莲花路 2163 号 | http://www.meiling.com | 0551-62219021 | lixia@meiling.com |
顺钠股份 | 000533 | 广东省佛山市顺德区大良街道逢沙萃智路 1 号车创置业广场 1 栋 18 楼 02—07 单元 | www.shunna.com.cn | 0757-22321218 | weihg@shunna.com.cn |
佛山照明/粤照明 B | 000541、200541 | 广东省佛山市禅城区汾江北路 64 号 | www.chinafsl.com | (0757)82810239 | fsldsh@chinafsl.com |
海信家电 | 000921 | 广东省佛山市顺德区容桂街道容港路 8 号 | http://hxjd.hisense.cn/ | (0757)28362570 | hxjdzqb@hisense.com |
格力电器 | 000651 | 广东省珠海市前山金鸡西路 | http://www.gree.com.cn | 0756-8669232 | gree0651@cn.gree.com |
思源电气 | 002028 | 上海市闵行区华宁路 3399 号 | www.sieyuan.com | 021-61610958 | IR@SIEYUAN.COM |
本部分绘图的数据来自于第二部分整理合并的各指标时间序列。笔者从时间序列和横截面横向对比两个角度,对营业收入和上市公司股东净利润进行了可视化处理。
其中,因为美的集团和格力电器的财务指标量级在千亿以上,与其余8家公司大相径庭。为了绘图的显著性,笔者在进行10家上市公司的财务指标可视化的基础上,又进一步剔除了这两家公司,以显示剩余8家公司的时序关系和相互对比关系。
# 各家公司营业收入绘图
from pylab import mpl
import matplotlib.pyplot as plt
mpl.rcParams['font.sans-serif'] = ['KaiTi']
mpl.rcParams['axes.unicode_minus'] = 0
plt.style.use('ggplot')
plt.figure(figsize=(15,9))
for i in range(len(revenue_series)):
# 绘制子图
plt.subplot(2,5,i+1)
plt.plot(revenue_series.iloc[:,i],label = revenue_series.iloc[:,i].name)
plt.xlabel(u'年 份',fontsize = 12)
plt.ylabel(u'营业收入(单位:10亿元)',fontsize = 12)
plt.xticks(fontsize = 12)
plt.yticks(fontsize = 12)
plt.legend(loc = 0,fontsize =13)#bbox_to_anchor=(1.01, 1), loc=0, borderaxespad=0
plt.tight_layout()
plt.suptitle('电器行业10家上市公司2013-2022年营业收入时序图',fontsize = 18,fontweight='heavy')
plt.show()
# 电器行业10家上市公司10年营业收入纵向对比图'
plt.figure(figsize=(15,6))
ro = 0
labels = list(revenue_series.index)
x = np.arange(len(labels))
for i in range(len(revenue_series[:])):
labels = list(revenue_series.index)
plt.bar(x+ro,revenue_series.iloc[:,i],width=0.08,label = revenue_series.iloc[:,i].name,edgecolor = 'black')
ro += 0.08
plt.xticks(x+0.3,labels,fontsize = 13,rotation = 22.5)
plt.yticks(fontsize = 13)
plt.xlabel(u'年 份',fontsize = 15)
plt.ylabel(u'营业收入(单位:10亿元)',fontsize = 15)
plt.legend()
plt.title(u'2013-2022年电器行业10家上市公司营业收入横向向对比图',fontsize = 18,fontweight='heavy')
plt.show()
“归属于上市公司股东的净利润”时序图和纵向对比图绘制原理与之前类似。故在此不再赘述。
首先,由整体的横向对比图可知,由于“美的集团”和“格力电器”所占市场份额较大,其营业收入和净利润与其他公司不在一个量级,故而笔者在后面又将“美的集团”和“格力电器”剔除,单独对8家进行了横向对比分析。
通过对比可知,“美的集团”“格力电器”“海信家电”以及“许继电气”占据了市场90%以上的份额,且营业收入和净利润连年稳定增长。其余几家公司所占市场份额较少,营业收入和净利润波动较大。其中,像“长虹美菱”“顺钠股份”和“特发信息”等企业在2018年出现了较大幅度的下滑,营业收入甚至出现了负增长。
综上可知,在2018年中美贸易战和我国全面推进供给侧结构性改革的背景下,电器行业的发展连续3年下行压力巨大。而在2022年,电器行业复苏势头明显,各家公司营业收入均出现了不同程度回暖。
如图所示,从时间上来看,在10家上市公司中,除了“特发信息”和“顺纳股份”外,其他公司的营业收入均处于波动上升的态势。尤其是“德赛电池”“美的集团”和“思源电气”,仅10年营业收入上涨波动最小,上涨持续时间最久
众所周知,电器行业β值较大,其发展与经济周期存在着密切的关系。自2020年新冠疫情爆发以来,经济下行压力持续加大,对电器行业各公司产生了不同程度的冲击。但是该行业营业收入普遍波动的根本原因并非全由新冠疫情导致,疫情因素只能算作是短期冲击因素。根本原因应在于自2018年以来经济下行周期的开始。
自2018年以来,图中电器行业所呈现的电器行业各家公司营业收入均出现了不同程度的下滑。者一方面是居民需求的转变,居民购买需求转变为以更新换代需求为主,且释放缓慢;另一方面,受政策空窗、经济放缓、原材料价格持续高位、房地产遇冷等等外部因素影响,家电市场规模增长失速。
由图可知,归属于上市公司股东的净利润(以下简称“净利润”)走势基本与营业收入成正比例关系。但是波动幅度明显大于营业收入。这一方面是由于2018年以来,成本端生产技术不断革新、大宗商品价格波动剧烈导致的;另一方面根据默顿关于股利政策和公司价值的理论(MM理论),2018年以来,经济波动幅度增大,企业投资机会也随之波动剧烈。因此,企业的鼓励政策存在着较大的不确定性。然而,从图中可知,对于市场份额持有较大的公司,股利政策要比市场份额持有较小的公司更加稳定,且受周期波动的影响较小。
综上,要实现电器行业市场规模的稳步增长,必须从需求供给两端发力。2023年,在宏观经济基本面持续向好的支撑下,家电国内销售规模有望“企稳回升”。首先,短期内消费意愿的恢复和提升,房地产市场预期和信心的回暖有助家电消费回温;其次,中长期来看,城镇化进程继续推进,部分家电保有量仍有较大空间。而中国家电企业坚持不懈的创新,则为行业的健康发展输送着源源不断的内生动能。
本次实验,从编写到测试,历时1周,最终得以顺利完成。自己虽然经历了无数次报错,但是在我对错误的分析处理当中,自己对Python编程又有更高层次的认识和掌握。自己在这门课程当中主要的收获有以下几点:
首先,在吴燕丰老师的指导下,我对面向对象式的编程有了初窥门径的认识,代码的编写也比以往更有逻辑、更加精简,更加友好。
其次,在吴老师的带领下,自己对于文本爬虫、HTML5和正则表达式的编写有了初步的掌握。上学期,我在学习《金融建模基础》的时候,自己就觉得在数据获取方面存在着很大不足,而这门课正好为我弥补这方面的工具的缺失提供了不错的路径。
最后,希望在这门课的基础上,自己能够继续努力,不断拓展对网络爬虫的认识,更加深入地掌握一些更加面向实践的数理算法。
证监会发布的《2021年行业分类》较多,若采用手动搜索的方式寻找学生所属的行业,则十分繁琐。因此,笔者按照吴老师的匹配规则要求,编写了所属行业自动匹配的Python脚本,充分运用计算机的优势,以实现系统化、快速化、自动化的处理。代码和结果展示如下:
# -*- coding: utf-8 -*-
import pandas as pd
import pdfplumber
page = pdfplumber.open(r"行业.pdf").pages
hangye = []
# 提取pdf每一页的表格
for i in page:
df = pd.DataFrame(i.extract_table()).fillna(method = 'ffill') # 每一列nan值以上面不为nan的值填充
hangye.append(df)
# 将4388张提取的表格合并成一个表格
industry = pd.concat(hangye,ignore_index = True)
# 以合并后的表的第一行重新命名
industry.columns = df.iloc[0]
industry.drop(0,inplace = True)
# index从1开始
for i in industry.index:
# 用any判断是否有些行的内容与列相同,如果True,则删去与列相同的行
if any(industry.columns == industry.loc[i]) == True:
industry.drop(i,inplace = True)
'''
将已经更新的industry数据按照'行业大类代码'这一列分类聚合,
然后按照size()找出每一个行业所包含上市公司数,最后按照行业代码原本的顺序排列,
形成一个名为'industry_group_data'的Series.
'''
industry_group_data = industry.groupby('行业大类代码').size().sort_index()
# 从industry_group_data找出所在行业上市公司数目大于10家的行业,并转换成DataFtame.
more_than_10 = pd.DataFrame(industry_group_data[industry_group_data >= 10])
# 更改原始列名
more_than_10 = more_than_10.rename(columns = {0:'行业公司个数'})
# 新建两列,将http://www.yyschools.com上的学号变为索引,与行业进行匹配.
more_than_10['行业大类代码'] = more_than_10.index
more_than_10['学号'] = list(range(1,len(more_than_10)+1))
more_than_10 = more_than_10.set_index('学号')
# 检索出证监会行业大类名称的划分
ind_div = industry[['行业大类代码','行业大类名称']].drop_duplicates('行业大类代码')
# 将 more_than_10与ind_div进行合并
more_than_10 = pd.merge(more_than_10,ind_div,on = '行业大类代码')
# 交互程序
print('-------------------------')
stu_code = input('请输入学生序号:')
ind_code = more_than_10.loc[int(stu_code)-1]['行业大类代码']
ind_name = more_than_10.loc[int(stu_code)-1]['行业大类名称']
print('--------------------------------------------')
print('编号为"{}"的学生,所在行业代码为"{}",即"{}"行业.'.format(stu_code,ind_code,ind_name))
# 获取自己所在行业的股票代码和公司简称
df = industry[industry['行业大类名称']==ind_name].head(10)
df['行业大类代码'] = df['行业大类代码'].apply(lambda x: str(x))
df.to_csv('股票代码.csv')
print('--------------------------------------------')
print('股票代码和公司简称表格已保存.')
结果展示如下: