做公司金融和资产定价这类实证研究的人,每年都会遇到类似的问询:媒体关注度数据去哪找,怎么清洗成公司-年度面板,Stata代码怎么处理才算规范。尤其是2001—2024年这么长一个窗口,横跨主板、中小板、创业板的几千家上市公司,光是新闻匹配、去重、日期对齐就能把人绕晕。这篇内容适合正在写论文或者在复现经典文献的同学,我会把从原始新闻表一路清洗到可直接回归的面板数据这个过程,拆成一套可以反复用的思路,顺带把配套代码里的关键命令讲清楚。
先说结论:媒体关注度不是一个只有“新闻条数取对数”这种单一口径的变量。它可以是年度报道总量,可以是是否被报道的虚拟变量,可以是不同情感倾向的报道数量,甚至可以做“异常关注度”做增量信息。选择哪一种,取决于你的研究问题。但无论如何,前期的数据处理部分高度相似:把一篇篇文本新闻转成公司-年份层面的统计量,同时保证每个公司在每个年度都有对应的观测值,没有新闻的年份不能被随手丢掉。
1. 媒体关注度在实证里的价值,以及它对数据清洗的隐藏要求
媒体关注度这个概念,学术界讨论了几十年,从经典的有效市场假说,到后来的有限注意力理论,再到中文文献里特别爱用的“媒体治理”“舆情冲击”,本质上都在利用同一个事实:新闻不是均匀地落在每家公司头上的。某些公司一年被报道几百次,另一些公司常年没有一条新闻,这种差异本身就是信息。
在回归模型里,它的身份非常多。最常见的是当解释变量,去解释股价崩盘风险、盈余管理、过度投资;也能当被解释变量,研究哪些公司特征吸引了媒体注意力;还能在机制检验里当中间变量,比如高管减持、信息披露违规这类事件先引起媒体关注,再进一步影响市场反应。如果数据质量不过关,后面所有结果都会被审稿人一句话打回来:你的媒体关注度变量是怎么测的?
这恰恰是对数据清洗提出了很高的要求。
打个比方,你拿到的原始数据通常不是干净的股票代码,而是一堆新闻表的记录,里面写着“贵州茅台”“五粮液”“宁德时代”这样的公司简称,甚至还有“茅台集团”“五粮液集团”这种关联主体。直接按字符串匹配,就会把集团和上市公司的报道混在一起。还有转载新闻的问题:一条新闻被新浪、网易、搜狐同时转载,计数时如果不去重,同一事件在一天内会被统计成多条报道,你的年度关注度就虚增了。
所以,媒体关注度数据的处理流程,至少要回答三件事: 第一,新闻主体如何对应到具体的上市公司代码; 第二,多源转载之后如何定义“一条真正的报道”; 第三,没有新闻的年份应该如何补零,避免样本缺失导致选择性偏差。
这些问题看似琐碎,但对最终变量的有效性和稳健性非常关键。下面的内容就是围绕这三件事展开的。
2. 数据源选型:为什么2001—2024这个窗口需要分阶段看待
先说数据源。目前做中文上市公司媒体关注度,主流的三条路是:中国研究数据服务平台(CNRDS)的“中国上市公司财经新闻数据库”、知网的中国重要报纸全文数据库、以及自己用爬虫程序从新闻门户抓取的历史存档。三者各有优缺点,但一个共同点是:它们的收录起始年份往往和公开数据库的覆盖范围有关,2001年这个起点刚好与中国资本市场信息披露制度逐步完善的时间段重合,能覆盖较完整的两轮牛熊周期,所以很多研究都把起点设在2001年。
CNRDS的数据结构比较适合直接做研究,它一般会列出新闻标题、发布日期、来源媒体、公司名称等字段,有些版本还自带了情感倾向标识。这类结构化数据是最省事的,处理重点放在去重和公司名称归一化上。知网报纸库的历史跨度长,2001年左右的报纸资料很全,缺点是文本字段依赖人工或者正则清洗,而且它主要是报纸,网络媒体的覆盖度不够。
如果要做2001—2024年这么长的时间序列,特别需要警惕数据源在不同年份的收录逻辑变化。2005年之前,网络媒体远不如报纸活跃,很多新闻源收录的是纸质媒体扫描件;2005年之后网络新闻逐渐兴起;到了移动互联网时代,新闻发布数量更是爆发式增长。如果你的统计口径没有控制“媒体类型”这个维度,那么2010年之后的高被关注度很可能不是因为公司真的更重要了,而是因为媒体总量增加了。这一点在数据处理阶段就需要留好来源字段,后续分析时可以按报纸新闻和网络新闻分组,或者单独报告排除网络新闻后的稳健性结果。
我在实际项目里用的是CNRDS公司财经新闻的年度下载数据,再手工补充了一部分上市公司公告和交易所处罚公告。标题里的2001-2024窗口,我也是这样处理的:凡是新闻日期落在该时间窗口内,就进入样本;公司的上市状态按年报期判断,退市公司当年如果还在交易也保留。这样做的好处是样本更干净,不会出现公司在上市之前就有“上市公司媒体关注度”记录的矛盾。
3. Stata数据清洗主线:从原始新闻表到公司-年度面板
这一节是我最想和你分享的实操内容。拿到原始新闻表后,很多人第一反应是直接分组计数,然后写一句collapse (count)了事。这样写当然也能出数字,但凡是碰上公司简称有变化、新闻里出现多个公司主体、或者源数据有重复导入的情况,就会得到一份看似完美实则充满噪声的结果。
我一般把清洗流程拆成四个环节:公司简称库准备、日期标准化、主体识别、加总合并。
3.1 准备一个带历史简称的上市公司基本信息表
第一步非常关键:准备一张以股票代码为唯一标识的“公司名时间表”。不是每个公司只有一个简称,A股里绝大多数公司都改过名,比如“贵州茅台”虽然没有改名,但大量公司经历过从“某某科技”到“ST某某”再到“某某科技”的变化。媒体新闻标题里通常用的是当年对应的简称。如果只用最新简称去匹配,好几年前的新闻都会被漏掉。
这张公司名时间表可以手动整理,也可以从CSMAR或Wind导出,关键是字段要包含:股票代码、股票简称、起始日期、结束日期。也就是说每个股票代码在不同时间段可以对应多个简称,但同一时间段只能有一个有效简称。后续去匹配新闻标题时,不是简单地strpos(news_title, company_name),而是要先判断新闻发布日期落在哪个时间窗口,再用那个窗口的简称去匹配。这个细节非常影响准确率。
用Stata实现时,我通常是先把基本面信息读进来:
* 读取公司名字-时间表 import excel "company_name_history.xlsx", firstrow clear rename 证券代码 stock_code rename 证券简称 stock_name rename 开始日期 name_start_date rename 结束日期 name_end_date gen date_start = date(name_start_date, "YMD") gen date_end = date(name_end_date, "YMD") format date_start date_end %td keep stock_code stock_name date_start date_end save "company_name_history.dta", replace这样每个股票代码就对应了一串时间区间,匹配的时候按年份区间取当时有效的简称。
3.2 新闻日期与文本字段的清洗
新闻表里最怕的是日期字段不是标准格式。有的写成“2020年8月5日”,有的写成“2020/8/5”,还有的是“20200805”。我的习惯是先把它们统一转换成Stata的整数日期格式,这样后面按年份加总会非常快。对于中文日期文本,可以用正则表达式先做replace再转换成date()函数。
遇到编码问题也要注意,很多老库导出的是GBK编码的中文内容,Stata直接读取会全是乱码。这个场景下,推荐先统一转成UTF-8编码再导入,具体到Stata操作可以用import delimited ..., encoding("utf-8"),或者使用unicode encoding set gb18030这类设置。中文数据分析的人应该都碰到过这个坑,代码报错反而不是逻辑问题,而是读取阶段文本就错了。
举个例子:
import delimited "news_gbk.csv", varnames(1) encoding("gb18030") clear如果读取后中文正常,那就继续;如果还不正常,就先用文本编辑器另存为UTF-8版本再读。这个步骤不要省,因为后续所有匹配都是靠中文公司简称进行的,任何编码污染都会导致匹配率严重偏低。
3.3 识别新闻主体,生成公司代码候选列
主体识别是媒体关注度处理中最核心的环节。我的做法是:先对新闻标题和正文建立索引,然后把每家公司当年的有效简称放进去,用模糊匹配找交集。为了控制速度,一般不会让所有简称去匹配所有新闻,而是先把新闻按年份分组,再把该年份有效的公司简称集合放进内存做遍历。
简化版的Stata思路如下:
* 假设数据已包含 news_title, news_date, year * 先生成日期对应的年份 gen year = year(date) * 对公司-新闻做交叉匹配 joinby year using "company_name_history.dta", unmatched(both) gen is_mentioned = strpos(news_title, stock_name) > 0 keep if is_mentioned == 1这个写法背后的逻辑是基于year做了笛卡尔积,再用公司当年简称到新闻标题里去检索。如果新闻标题中提到了当时简称,就认为该新闻关注到了该公司。一个新闻标题可以同时命中多家公司,这种是正常的,后续统计时保留即可。这里不用strpos也可以改用更复杂的regexm以应对带“ST”前后缀的情形,但strpos更直观。
3.4 去重:注意转载和重复报道
经过主体识别之后,你会得到一张“新闻标题—公司代码”的长表。这张表里存在大量重复条数:同一条新闻被不同媒体转载,进而被多条记录;也可能同一个媒体在一天内对同一事件连续发布了几乎相同的快讯。
我的去重策略按两个层级执行。 第一层,对同一公司、同一新闻标题、同一日期的记录,只保留一条。如果新闻标题完全相同可以认为是同一条新闻的转载。 第二层,对同一公司、同一天内、标题相似度极高的记录做人工抽样确认。有些数据库或者爬虫会加个“【快讯】”前缀,正文一样但标题微调,这类属于转载。
实际的Stata命令往往是这个模式:
* 生成唯一识别键 duplicates drop company_code news_title news_date, force * 更严格的指纹匹配时可以先生成清洗后的标题变量 gen clean_title = lower(trim(news_title)) egen news_id = group(company_code clean_title news_date) duplicates drop news_id, force如果你用的数据自带新闻ID,也可以用那个ID去重,但我发现自带ID在不同数据库之间并不统一,还是自己生成一个标准键来得放心。
3.5 加总成面板:不要丢掉零值
去重后的每条记录就是一次“公司-年度”的有效报道。接下来最常规的操作就是按公司和年份加总媒体报道次数。
collapse (count) n_news = news_id, by(company_code year)但问题来了:很多公司在某些年度没有任何新闻记录,上面的collapse只会保留至少有1条新闻的公司-年度,那些没有被媒体报道的公司-年度会被直接省略掉。如果直接拿那张表去和财务数据merge,这些公司年度就会变成非匹配样本,被当作缺失值。这就会把“没有媒体关注”和“数据缺失”混在一起,是非常大的隐患。
正确做法是,先生成完整的公司-年度面板框架,再把新闻统计量匹配上去,缺失值替换成0:
* 从公司代码表生成每年的平衡框架 expand 24 bysort company_code: gen year = _n + 2000 * 合并加总统计量 merge 1:1 company_code year using "news_agg.dta", keep(1 3) nogen replace n_news = 0 if missing(n_news)这一步操作看似简单,但它决定了你后续回归时的样本量。很多做面板研究的人最后样本里只有被报道过的公司,原因就是漏了补零这一步,后果是样本中系统性漏掉了冷门公司,产生了选择偏差。
4. 关注度变量的设计层次:不是只有新闻条数取对数
有了 n_news 这个基础计数,后面能派生出多个变量口径。
4.1 基础口径:年度报道数量与对数化
最常用的是ln_media = ln(n_news + 1)。为什么要加1取对数?因为媒体报道次数是典型的右偏分布。某些热门公司一年几千条,而多数公司一年只有个位数,直接放进回归模型,极端值影响会非常大,残差也远非正态。取对数能把这些极端值压缩,让变量更接近对称分布。加1是为了避免0取对数无意义。
gen ln_media = ln(n_news + 1)这个变量解释起来也方便:它度量的是媒体关注度的强度,每增加1个百分点,意味着样本内媒体报道的相对比例提升。系数大小甚至可以在不同论文之间做个粗略比较。
4.2 倾向口径:是否关注
有些研究关心的是“有或无”,不是“多或少”。比如用PSM做处理效应分析时,需要把样本划分为被关注组和非关注组,这时可以生成:
gen media_dum = (n_news > 0)这类变量更适合做匹配时的分组变量,或者做probit模型的被解释变量。
4.3 增量口径:异常媒体关注度
学术界越来越常用的是“异常媒体关注度”,也就是剔除公司基本面、行业、市场整体环境之后,媒体关注度不能被解释的残差部分。这个思想的来源是注意力分配理论——媒体有限的版面需要分配给“有新闻价值”的公司,所以一家公司的关注度有一部分是系统性因素决定的,我们不能把那一部分当作外生冲击。
计算异常关注度时,一般会先对原始关注度做回归,控制住公司上期规模、账面市值比、上期市场收益、行业效应、年度效应等等,然后把残差保存下来:
xtset company_code year xtreg ln_media L.ln_size L.bm L.ret i.year, fe robust predict e_media, e这个e_media就可以作为异常媒体关注度指标,在因果识别中被当作外部信息冲击的代理变量。很多做股价崩盘和媒体报道的论文其实用的是这个口径,而不是单纯的总报道次数。
4.4 情绪口径:区分正面、中性、负面
如果数据库提供了情感倾向字段,或者你自己用情感词典做了文本分类,就可以把篇新闻拆成正面报道数、中性报道数和负面报道数,得到更细的指标。常见的处理是把正面减负面再除以总数,得一个媒体情绪净倾向指标;也可以分开统计后分别取对数。
这个方向虽然更花时间,但它在做“媒体监督”和“市场情绪传导”研究时,很多场景下比总关注度更有解释力。同一个公司被100条正面新闻刷屏和被100条负面新闻密集轰炸,市场反应完全不一样,如果你只用一个总数,等于把这个最重要的信息维度对冲掉了。
5. 实操中常见的坑和对应的排查套路
数据和代码本身不复杂,复杂的是你会遇到各种版本迭代后产生的脏数据。下面几个坑,我踩过的概率基本是百分之百,每个项目都会碰到一两个。
5.1 公司更名导致的匹配年份错位
最典型的例子是某个公司在2015年改了名字,但新闻库里很多编辑依然会用旧简称发稿,甚至同年内新旧名称混用。如果我的“公司简称历史表”里新名称从2015年3月1日生效,那么2015年上半年发布的旧名称新闻就匹配不上,最终关注度被低估。
我的处理办法是:在标准化的公司简称外,再加一组“媒体别名”。像“五粮液”有时会在标题中被写成“五粮液股份”,“中信证券”有时直接被写成“中信”,还有的公司简称是“中航光电”而新闻标题里写“中航光电科技”。这种别名并不是简单的更名,它是媒体在文本里自我缩写的结果。最好整理一列别名,让多个词条都能指向同一个股票代码。
* 别名表结构示例:company_code, alias, alias_start, alias_end别小看这个过程,它通常能把匹配率提升大约3到8个百分点。
5.2 一篇新闻报道多家公司,去重会不会误删?
一家公司的年报发布会现场新闻,标题往往把行业内的重点公司都列了一遍:“多家机构调研贵州茅台、五粮液、泸州老窖”。如果我按“新闻标题”作为去重键,同一条标题命中三家公司就会被保留成三行,这个是正确的,因为这三家公司确实都在同一篇报道中被关注了。
如果使用新闻数据库自带的新闻ID进行去重,问题就出现了——同一ID很可能因为标题中包含多个公司而被记录下来多次,直接用ID去重会把另外两家的记录删掉。所以我的原则是:去重键必须包含“公司代码 + 新闻ID + 新闻日期”,而不是只对新闻ID去重。
5.3 非上市公司主体被误纳入
新闻标题里经常出现“某集团”而不是“某股份有限公司”。“五粮液集团”不是上市公司五粮液,但新闻标题常写“五粮液集团召开会议”,这类记录如果匹配上就错了。解决办法是在文本匹配时尽量用“证券简称”,即不含“集团”“控股”等后缀形式的短名称,再配合标题中出现该短名称但上下文没有“集团”等排除词时,需要人工抽查判断。
比较省事的替代方案是采用“精确简称 + 前后位置不可出现集团字样”的正则规则。这个思路在Stata里可以写成:
gen is_mention_com = regexm(news_title, "五粮液") & !regexm(news_title, "五粮液集团")逐家公司做太慢,可以生成一个公司简称的临时循环批处理,这里不展开,但思路是一致的。
5.4 同一事件被其他媒体转发带来的计数翻倍
一条公司公告本身并不是媒体关注度,但当它被各大财经门户转载后,新闻数会激增。这时候最好分清楚“原创报道”和“纯转载”。如果数据源里给了媒体转载关系,就保留主报道,剔除后续的重复新闻;如果没有,退而求其次,至少对“同一标题、同一公司、同日”的记录去重。这样虽然不能完全消除转载问题,但已经能降低计数里的噪声。
5.5 面板两端年份的冷启动问题
2001—2024年的数据跨度很大,最前和最后的一段年份经常有特殊现象。2001年偏早期,证券类报纸数量有限,媒体报道总量少,关注度均值偏低;2024年最新的数据可能存在“半年度截断”,即当年还没结束,新闻记录不全。如果不做处理,直接用2001和2024两个端点年份做对比时会得出误导性结论。
一个常见做法是去掉首尾各一年做稳健性检验,尤其是当回归结果对极端年份特别敏感时。另一个做法是把年度虚拟变量放进模型,让近年整体的媒体数量激增效应被年份固定效应吸收。
6. 一些值得在项目一开始就想清楚的设计决策
除了上面这些技术层面的内容,有几件事是决定后续工作效率的关键,如果你现在是从头搭建这个项目,趁早想清楚能省很多返工时间。
第一个是每年都备份一份去重前的原始长表。Stata处理流程中每一步都可能出错,但原始表是所有统计量的底线。我通常跑完主体识别后先保存一版company_news_long.dta,里面只有四列:公司代码、年份、新闻标题、新闻日期。后面无论是画数据的增长趋势,还是重算指标口径,都只需要回到这一层,而不必重新从CSV开始。
第二个是统计维度上把“所有新闻”“报纸新闻”“网络新闻”同时列出来。上面讲数据源时提过这两年“媒体泛滥”带来的统计口径问题,如果你从一开始就在原表里保留来源分组字段,那么至少在后续稳健性检验里你可以很快地证伪“关注度上升只是因为媒体总量增加”这种质疑。
第三个是把变量生成代码和样本清洗代码分开保存。我发现很多研究助理会把这些操作都写进同一个do文件,等到换样本、换极值剔除方式的时候,改一个参数就会连累前面的数据清洗。更合理的做法是第一个do文件专门输出回归面板,字段只保留公司代码、年份、基础统计量和几个辅助变量;第二个do文件才开始跑描述性统计、相关系数、主回归、稳健性和机制检验。
第四个也是最后一点,这组数据不要只拿来跑一条回归就结束。媒体关注度做出来的面板可以和很多数据库做交叉验证,比如把极值年份的关注度数据和重大行政处罚公告日期做对齐,看关注度峰值是否来自公司发布的违规公告或问询函回复。这类工作不仅能加强对数据的信任感,还能给后面的论文找到非常好的案例切入点。
从我这些年实际体会看,媒体关注度数据处理的难点从来不是统计命令本身,而是如何用一套可复现、可追溯的逻辑,把成千上万的文本清洗成一个可信度的变量。只要你手里的公司代码和历史简称对应关系建得足够完整,去重规则定义得足够清晰,后面再遇到什么衍生需求都不慌。如果你准备拿这套数据做实证,建议先别急着写主回归,先把你洗出来的年度平均关注度和现实中的大事记做一次目视化对比,数据质量其实一眼就能看得出来,比任何统计检验都直观。