[1]吴非,胡慧芷,林慧妍,任晓怡.企业数字化转型与资本市场表现——来自股票流动性的经验证[J].管理世界,2021,37(07)
[2]韩峰,姜竹青.集聚网络视角下企业数字化的生产率提升效应研究[J].管理世界,2023,39(11)
复刻论文缘由本人为大二学生,上数据科学导论的文本分析课程时,希望顺手锻炼一下学术能力,遂选取以上两篇论文进行参考,希望复刻企业数字化的数据提取过程。
然而在上网搜索的过程中,遇到的很多问题无法得以解决,或者说,有分享出来的都是直接分享爬取好的excel结果,没有看到直接分享代码的;或者说,都是需要收费的。
基于此,本人希望把整个作业结果分享出来,和大家共同研究探讨。
遇到的问题主要如下:
1、如何爬取上市公司年报数据
--> 参考了社区里面另一个大佬的:
2、如何将pdf转换成txt
--> 自行探索综合了几个方案,再调整得出的可运行的版本
3、在此基础上,剔除关键词前存在“没”“无”“不”等否定词语的表述,同时也剔除非本公司(包括公司的股东、客户、供应商、公司高管简介介绍在内)的“数字化转型”关键词
--> 本人只剔除了否定表述,对于后者,希望有大佬指点一番
词典 数据说明本文基于文本挖掘的数字经济词频方法来测算企业数字化水平。
本文根据吴非等(2021)的方法,利用爬虫技术批量搜索了中国沪深A股上市制造业企业2010~2022年期间的年报文本数据,并且使用其词典进行词频统计。
进而借鉴洛克伦和麦克唐纳(2014)及阿西莫格鲁等(2021)的研究,利用上市公司年报文本数据和基于机器学习的“词频—逆文本频率”方法测算企业数字化水平。
企业数字化指标可以表示为:
1、爬取年报在CSMAR上自行下载所你需要的股票代码,将文件命名为“stockcode.xlsx”,表格内容如图所示:
调整文件夹路径,即可直接运行代码。我这里爬取的是巨潮资讯网的年报数据。
import os os.chdir(r"E:\大学课程相关\大二下学期\5 数据科学导论\文本分析") os.getcwd()
import requests,time,random,json import pandas as pd def req(stock,year,org_dict): # post请求地址(巨潮资讯网的那个查询框实质为该地址) url = "" # 表单数据,需要在浏览器开发者模式中查看具体格式 data = { "pageNum":"1", "pageSize":"30", "tabName":"fulltext", "stock":stock + "," + org_dict[stock] ,# 按照浏览器开发者模式中显示的参数格式构造参数 "seDate":f"{str(int(year)+1)}-01-01~{str(int(year)+1)}-12-31", "column":"szse", "category":"category_ndbg_szsh", "isHLtitle": "true", "sortName":"time", "sortType": "desc" } # 请求头 headers = {"Content-Length": "201","Content-Type":"application/x-www-form-urlencoded"} # 发起请求 req = requests.post(url,data=data,headers=headers) if json.loads(req.text)["announcements"]:# 确保json.loads(req.text)["announcements"]非空,是可迭代对象 for item in json.loads(req.text)["announcements"]:# 遍历announcements列表中的数据,目的是排除英文报告和报告摘要,唯一确定年度报告或者更新版 if "摘要" not in item["announcementTitle"]: if "英文" not in item["announcementTitle"]: if "修订" in item["announcementTitle"] or "更新" in item["announcementTitle"]: adjunctUrl = item["adjunctUrl"] # "finalpage/2019-04-30/1206161856.PDF" 中间部分便为年报发布日期,只需对字符切片即可 pdfurl = "" + adjunctUrl r = requests.get(pdfurl) f = open("年报" +"/"+ stock + "-" + year + "年度报告" + ".pdf", "wb") f.write(r.content) print(f"{stock}-{year}年报下载完成!") # 打印进度 break else: adjunctUrl = item["adjunctUrl"] # "finalpage/2019-04-30/1206161856.PDF" 中间部分便为年报发布日期,只需对字符切片即可 pdfurl = "" + adjunctUrl r = requests.get(pdfurl) f = open("年报" +"/"+ stock + "-" + year + "年度报告" + ".pdf", "wb") f.write(r.content) print(f"{stock}-{year}年报下载完成!") # 打印进度 break
# 该函数主要是通过该json数据,找到每个stock对应的orgid,并存储在字典org_dict中 def get_orgid(): org_dict = {} org_json = requests.get("").json()["stockList"] for i in range(len(org_json)): org_dict[org_json[i]["code"]] = org_json[i]["orgId"] return org_dict
# 获取年报PDF的路径 def load_pdf(code, year): file_path = r'E:\大学课程相关\大二下学期\5 数据科学导论\文本分析\年报' # 自行修改 file_name = "{}-{}年度报告".format(code,year) pdf_path = os.path.join(file_path,file_name) return pdf_path
import pandas as pd import time import random import os if __name__ == "__main__": # 读取股票代码信息 data = pd.read_excel("stockcode.xlsx", converters={'stockcode': str}) stockcodes = data['stockcode'].tolist() org_dict = get_orgid() # 年份范围从2010到2022 years = [str(year) for year in range(2010, 2023)] # 遍历股票代码及年份列表 for stock in stockcodes: for year in years: pdf_path = load_pdf(stock, year) + ".pdf" txt_path = load_pdf(stock, year) + ".txt" # 检查对应的PDF文件是否已经存在,如果存在则跳过下载步骤 if os.path.exists(pdf_path): print("PDF文件已存在,跳过下载步骤。") else: req(stock, year, org_dict) time.sleep(random.randint(0, 2))
2、文件转换:将PDF转换成TXT文件import pandas as pd import os from pdfminer.converter import PDFPageAggregator from pdfminer.layout import * from pdfminer.pdfparser import PDFParser from pdfminer.pdfdocument import PDFDocument from pdfminer.pdfpage import PDFPage,PDFTextExtractionNotAllowed from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
# 获取年报PDF的路径 def load_pdf(code, year): file_path = r'E:\大学课程相关\大二下学期\5 数据科学导论\文本分析\年报' # 自行修改 file_name = "{}-{}年度报告".format(code,year) pdf_path = os.path.join(file_path,file_name) return pdf_path
def parsePDF(pdf_path,txt_path): # 以二进制读模式打开pdf文档 fp = open(pdf_path,'rb') # 用文件对象来创建一个pdf文档分析器 parser = PDFParser(fp) # pdf文档的对象,与分析器连接起来 doc = PDFDocument(parser=parser) parser.set_document(doc=doc) # 如果是加密pdf,则输入密码,新版好像没有这个属性 # doc._initialize_password() # 创建pdf资源管理器 来管理共享资源 resource = PDFResourceManager() # 参数分析器 laparam=LAParams() # 创建一个聚合器 device = PDFPageAggregator(resource,laparams=laparam) # 创建pdf页面解释器 interpreter = PDFPageInterpreter(resource,device) # 用来计数页面,图片,曲线,figure,水平文本框等对象的数量 num_page, num_image, num_curve, num_figure, num_TextBoxHorizontal = 0, 0, 0, 0, 0 # 获取页面的集合 for page in PDFPage.get_pages(fp): num_page += 1 # 页面增一 # 使用页面解释器来读取 interpreter.process_page(page) # 使用聚合器来获取内容 layout = device.get_result() # 这里layout是一个LTPage对象 里面存放着 这个page解析出的各种对象 一般包括LTTextBox, LTFigure, LTImage, LTTextBoxHorizontal 等等 想要获取文本就获得对象的text属性, for x in layout: if isinstance(x,LTImage): # 图片对象 num_image += 1 if isinstance(x,LTCurve): # 曲线对象 num_curve += 1 if isinstance(x,LTFigure): # figure对象 num_figure += 1 if isinstance(x, LTTextBoxHorizontal): # 获取文本内容 num_TextBoxHorizontal += 1 # 水平文本框对象增一 # 保存文本内容 with open(txt_path, 'a',encoding='UTF-8',errors='ignore') as f: results = x.get_text() f.write(results + '\n')
# 读取股票代码信息 rawdata = pd.read_excel(r'E:\大学课程相关\大二下学期\5 数据科学导论\文本分析\stockcode.xlsx', sheet_name=0, dtype={'stockcode': str}) # 获取DataFrame的行数 num_rows = len(rawdata) print(num_rows) # 年份范围从2010到2022 years = [str(year) for year in range(2010, 2023)] for iloc in range(num_rows): code = rawdata.loc[iloc, 'stockcode'] print(code) # 遍历固定的年份范围 for year in years: print(year) pdf_path = load_pdf(code, year) + ".pdf" txt_path = load_pdf(code, year) + ".txt" # 检查对应的TXT文件是否已经存在,如果存在则跳过转换步骤 if not os.path.exists(txt_path): print(pdf_path) parsePDF(pdf_path, txt_path) else: print("TXT文件已存在,跳过转换步骤。")
3、计算指标import jieba import pandas as pd from collections import Counter import re import os import numpy as np def remove_negations(words): negations = set(['非','别','不','没','无','忽','莫','否','没有','还没','毫无','无需','无关']) filtered_words = [] skip = False for word in words: if word in negations: skip = True elif skip: skip = False else: filtered_words.append(word) return filtered_words def word_frequency_by_category(text, stopwords, custom_dict, categories): words = jieba.cut(text) filtered_words = [word for word in words if word not in stopwords] filtered_words = remove_negations(filtered_words) filtered_words = [word for word in filtered_words if word in custom_dict] category_freq = Counter() for word in filtered_words: for category, words_in_category in categories.items(): if word in words_in_category: category_freq[category] += 1 break return category_freq def preprocess_text(text): # 去除非中文字符和数字 text = re.sub(r'[^\u4e00-\u9fa5]', '', text) text = re.sub(r'\d+', '', text) return text # 计算指标函数 def calculate_indicators(df): # 计算数字化关键词在上市企业 i 第 t 年的年报中的词频 df['ln(hkit+1)'] = np.log1p(df['词频'].astype(float)) # 计算第 t 年上市企业年报文本总数 Qt total_reports = df.groupby('year').size().reset_index(name='Qt') df = pd.merge(df, total_reports, on='year', how='left') # 计算第 t 年包含第 k 个数字化关键词的年报文本数量 qkt keyword_counts = df.groupby(['year']).size().reset_index(name='qkt') df = pd.merge(df, keyword_counts, on='year', how='left') # 计算包含第 k 个数字化关键词的逆文本频率 ln(Qt/qkt+1) df['ln(Qt/qkt+1)'] = np.log1p(df['Qt'] / (df['qkt'] + 1).astype(float)) # 计算指标 kit =∑k[ln(hkit+1)*ln(Qt/qkt+1)] df['k'] = df['ln(hkit+1)'] * df['ln(Qt/qkt+1)'] kit = df.groupby('year')['k'].sum().reset_index(name='kit') df = pd.merge(df, kit, on='year', how='left') return df # 读取停用词表 with open('stopwords.txt', 'r', encoding='utf-8') as f: stopwords = set(f.read().splitlines()) # 读取自定义词典 custom_dict = set([ "人工智能", "商业智能", "图像理解投资决策辅助系统", "智能数据分析", "智能机器人", "机器学习", "深度学习", "语义搜索", "生物识别技术", "人脸识别", "语音识别", "身份验证", "自动驾驶", "自然语言处理","大数据", "数据挖掘", "文本挖掘", "数据可视化", "异构数据", "征信", "增强现实", "混合现实", "虚拟现实","云计算", "流计算", "图计算", "内存计算", "多方安全计算", "类脑计算", "绿色计算", "认知计算", "融合架构", "亿级并发", "EB级存储", "物联网", "信息物理系统","区块链", "数字货币", "分布式计算", "差分隐私技术", "智能金融合约","移动互联网", "工业互联网", "移动互联", "互联网医疗", "电子商务", "移动支付", "第三方支付", "NFC支付", "智能能源", "B2B", "B2C", "C2B", "C2C", "O2O", "网联", "智能穿戴", "智慧农业", "智能交通", "智能医疗", "智能客服", "智能家居", "智能投顾", "智能文旅", "智能环保", "智能电网", "智能营销", "数字营销", "无人零售", "互联网金融", "数字金融", "Fintech", "金融科技", "量化金融", "开放银行" ]) # 自定义不同板块及其对应的词语 categories = { "人工智能技术": {"人工智能", "商业智能", "图像理解投资决策辅助系统", "智能数据分析", "智能机器人", "机器学习", "深度学习", "语义搜索", "生物识别技术", "人脸识别", "语音识别", "身份验证", "自动驾驶", "自然语言处理"}, "大数据技术": {"大数据", "数据挖掘", "文本挖掘","数据可视化", "异构数据", "征信", "增强现实", "混合现实", "虚拟现实"}, "云计算技术":{"云计算", "流计算", "图计算", "内存计算", "多方安全计算", "类脑计算", "绿色计算", "认知计算", "融合架构", "亿级并发", "EB级存储", "物联网", "信息物理系统"}, "区块链技术":{"区块链", "数字货币", "分布式计算", "差分隐私技术", "智能金融合约"}, "数据技术运用":{"移动互联网", "工业互联网", "移动互联", "互联网医疗", "电子商务", "移动支付", "第三方支付", "NFC支付", "智能能源", "B2B", "B2C", "C2B", "C2C", "O2O", "网联", "智能穿戴", "智慧农业", "智能交通", "智能医疗", "智能客服", "智能家居", "智能投顾", "智能文旅", "智能环保", "智能电网", "智能营销", "数字营销", "无人零售", "互联网金融", "数字金融", "Fintech", "金融科技", "量化金融", "开放银行"} } # 将自定义词典中的词语添加到分词词典中 for word in custom_dict: jieba.add_word(word) # 指定年报文本文件夹路径 folder_path = r'C:\Users\leon\data\03 数据科学导论\第二次小组作业数据\年报' # 创建一个空的 DataFrame 用于存储所有词频统计结果 all_results_df = pd.DataFrame(columns=['code', 'year', '板块', '词频']) # 定义预处理函数并计算词频 def preprocess_and_calculate(text): text = preprocess_text(text) freq_dict = word_frequency_by_category(text, stopwords, custom_dict, categories) return freq_dict # 遍历文件夹中的所有txt文件 for filename in os.listdir(folder_path): if filename.endswith(".txt"): file_path = os.path.join(folder_path, filename) code, year_str = filename.split('-') year = int(year_str[:4]) # 从文件名中提取年份 with open(file_path, 'r', encoding='utf-8') as f: text = f.read() # 预处理文本并计算词频 category_freq = preprocess_and_calculate(text) # 将词频结果存入 DataFrame df = pd.DataFrame(category_freq.items(), columns=['板块', '词频']) # 添加股票代码和年份列 df['code'] = code df['year'] = year # 调整列的顺序 df = df[['code', 'year', '板块', '词频']] # 将结果添加到 all_results_df 中 all_results_df = pd.concat([all_results_df, df], ignore_index=True) # 存储指标数据 all_results_with_indicators = calculate_indicators(all_results_df) # 根据公司代码和年份分组,保留每个公司每年的唯一一个 kit 数据 unique_kit = all_results_with_indicators.groupby(['code', 'year'])['kit'].first().reset_index() # 存储带有指标的数据 unique_kit.to_excel('指标构建.xlsx', index=False)
欢迎评论区批评指正!