恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
东方财富网分红数据爬取与清洗分析实战
首页
资讯中心
/
东方财富网分红数据爬取与清洗分析实战
东方财富网分红数据爬取与清洗分析实战
发布时间:2026/10/10 20:26:22
做量化投研或者基本面分析的时候分红数据真的是一个“看起来很基础、真要用却处处卡壳”的数据源。付费终端贵免费网页版又不让批量下载这时候爬虫就成了最实际的手段。这篇文章就围绕“东方财富网分红数据爬取与分析”这个主题把从页面探查、接口定位、代码编写、数据清洗到结果解读的完整流程摊开讲代码可以直接改改跑起来不会只丢一段没法用的脚本。如果你刚接触爬虫和数据分析或者想拿公开市场数据做自己的研究这篇应该能帮你少走不少弯路。我需要先说清楚下面所有操作只针对公开数据的正常访问请求频率、抓取范围都保持在“个人研究”的合理尺度内。爬虫的边界在于你拿数据干什么、有没有对目标站点造成压力这两条守住了工具本身没有问题。1. 分红数据到底能解决什么问题需求拆解与方案选型1.1 分红数据在投研场景里的真实用法很多人一听“分红数据爬取”第一反应是“不就是把表格下载下来吗”。其实没那么简单。分红数据在投研里的用法至少有三种每一种对字段的要求都不一样。第一种是红利策略选股。你可能会关心“过去三年每年都分红、且股息率排名靠前的股票”这时候需要的字段是分红年度、每股派息、除权除息日、股权登记日。第二种是估值补充。有累计分红之后你才能判断一家公司到底是“铁公鸡”还是“现金奶牛”这需要把多年分红记录汇总。第三种是除权除息的价格处理。做量化回测时遇到除权除息日前复权或者后复权价格必须用分红送转数据来校正否则你的收益曲线会在除息那天出现一个莫名其妙的“跳空下跌”那不是市场在跌是价格口径没调。这三种需求决定了你爬数据的时候不能只抓一张“最新分红预览”表而是要把这家公司历史上每一次分红送配的方案明细拿到手包括公告日期、实施方案、每10股派息金额、送转股数量、除权除息日。这个字段宽度比大多数人想象的要大。1.2 为什么选东方财富网而不是其他数据源我当时对比了好几个公开数据源最后定了东方财富网原因很现实。证券交易所以官方公告为准数据最权威但它的公告是PDF和HTML混杂解析成本高。同花顺有类似的数据中心页面也不错但某些接口需要登录态批量拉取的时候容易被风控盯上。Tushare这类数据包确实方便但部分高质量字段需要积分积分又需要贡献或者付费对于只是想快速验证一个想法的人来说门槛有点高。东方财富网的优势在于免费、更新及时、页面结构高度表格化而且它在网页端有一整套数据中心的栏目很多数据本身就是按表格展示的后端接口返回的也往往是规整的JSON这对爬虫来说太友好了。当然它也有自己的问题比如页面结构偶尔调整、接口偶尔加参数校验、请求频繁了会被暂时限制。这些我都放在后面的“常见问题”里详细讲。1.3 技术栈选型requests pandas 就足够了有些人一上来就想用 Scrapy或者搞一套分布式采集框架。对这种量级的项目完全没必要。我用的就是最朴素的组合requests 负责发请求pandas 负责清洗和结构化正则表达式负责处理一些边界问题。三个工具各管一段代码短、调试方便、出了问题也好定位。requests 的优点是足够轻手动控制 headers、超时、重试都很直接。pandas 的 read_html 和 DataFrame 操作能省掉大量手工循环。如果页面是向后端接口要数据返回的 JSON 解析出来直接塞进 DataFrame字段名稍微改一改就能开始分析。整个过程像搭积木每一块都能独立验证这比写一个庞大而完整的爬虫类更符合“边看边调”的实际开发状态。2. 动手前的准备环境搭建与数据入口探查2.1 虚拟环境与依赖安装先把工程目录建好然后创建一个干净的 Python 虚拟环境。这一步别偷懒尤其是你机器上已经装了不少包的情况下虚拟环境能帮你隔离版本冲突。mkdir dividend_crawler cd dividend_crawler python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate激活环境后安装下面几个库。pandas 的表格解析依赖 lxml必须一起装上否则 read_html 会直接报错。这个坑我踩过后面还会强调。pip install requests pandas lxml如果还想做可视化顺手装一个 matplotlib后面画累计分红分布时用得到。pip install matplotlib2.2 用浏览器开发者工具确认数据到底从哪里来很多爬虫教程直接给你一段 URL让你复制粘贴就完事。但实际上网站的页面结构调整频率远比你想象的高今天能用的 URL下个月可能就换了一个参数名。所以我的习惯是拿到任何一个爬取需求先打开浏览器开发者工具亲自看一眼数据从哪里来。具体操作是这样的打开东方财富网的分红数据相关页面按 F12 进入开发者工具切到 Network 面板勾选上 XHR 过滤条件然后刷新页面。你会看到页面在加载过程中发了一堆请求其中有一些接口名字里带着 stock、dividend、bonus 之类的英文关键词或者干脆就是.*Data.*这样的规律命名。点开每一个看右侧 Response 返回的内容。如果返回的是jsonp_callback({...})这种结构那说明数据源就在这——它是一个 JSONP 接口外面裹了一层回调函数。找到接口之后把它的完整 URL 和请求头里的 User-Agent 都记下来。后面的代码里就是模拟这个请求然后把 JSONP 的外壳剥掉拿到真正的数据体。这里有个容易被忽略的细节有的接口返回数据时是按“每10股”为单位披露的字段叫“每股派息”但实际数值是10股的总额。后面做数据分析的时候一定要看清楚单位不然算出来的累计分红能差十倍。3. 核心代码实现从请求到清洗落盘3.1 第一版代码先摸清返回结构不管页面最终呈现的是什么第一步永远是先把返回内容完整打印出来而不是急着解析。就像做菜要先尝一下食材不能上来就猛火翻炒。import requests url https://data.eastmoney.com/... # 以开发者工具抓到的实际地址为准 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36, Referer: https://data.eastmoney.com/ } resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 print(resp.status_code) print(resp.text[:2000])第一次运行重点关注两部分状态码是否是200返回的前2000个字符里是不是包含了你需要字段的英文或中文关键词。如果状态码是403或者返回一个验证页面那说明请求被识别了需要补全请求头或者降低频率。如果看到的是 JSONP 格式的文本比如以jQuery...(开头那直接用下面的方式剥壳。import re import json content resp.text # JSONP 格式通常是 callback({...})把最外层函数调用剥掉 json_str re.sub(r^[^(]*\((.*)\);?\s*$, r\1, content, flagsre.S) data json.loads(json_str)3.2 把 JSON 数据转成 DataFrameJSONP 接口返回的数据结构一般比较规整。外层是一个大对象里面会有数据列表、总条数、页码这些字段。我实际处理的时候习惯先把整个 JSON 结构递归打印出来看清楚哪一层才是“真正的数据列表”。import pandas as pd # 假设 data 是解析后的 JSON 对象 # 打印顶层键方便定位列表字段 print(data.keys()) # 根据实际结构取出数据列表通常是某个 key 对应的 value 是 list rows data.get(data, {}).get(list, []) df pd.DataFrame(rows) print(df.columns.tolist()) print(df.head())如果运气好DataFrame 的列名已经是中文定义好的比如“证券代码”“证券简称”“分红年度”“每10股派息”等等。但很多时候列名是一堆英文字段缩写比如SECURITY_CODE、DIVIDENT_RATE这时候就需要做字段映射统一改成人话。rename_mapping { SECURITY_CODE: 证券代码, SECURITY_NAME: 证券简称, DIVIDENT_RATE: 每10股派息(元), EX_DIVIDEND_DATE: 除权除息日, RECORD_DATE: 股权登记日, ANNOUNCE_DATE: 公告日期, } df.rename(columnsrename_mapping, inplaceTrue)3.3 字段清洗单位、缺失值和类型接口拿到的原始数据看着整洁实际上全是坑。最常见的三个问题空值用短横线表示、数字列被识别成字符串、单位口径不统一。先处理类型转换。分红金额这种字段拿到手是字符串必须转成数值类型才方便计算。# 先把特殊字符替换成 NaN df[每10股派息(元)] df[每10股派息(元)].replace(-, pd.NA) df[每10股派息(元)] pd.to_numeric(df[每10股派息(元)], errorscoerce)这里用errorscoerce非常关键它会把无法转换的值统一变成 NaN而不是让整个程序抛异常中断。之后再统一单位。如果原始数据是“每10股派息”而你想要做每股维度的统计那就除以10df[每股派息(元)] df[每10股派息(元)] / 10.0日期字段也需要标准化。开发者工具里看到的分红日期可能是2024-06-18也可能是2024/06/18建议统一转成 pandas 的 datetime 类型df[除权除息日] pd.to_datetime(df[除权除息日], errorscoerce) df[公告日期] pd.to_datetime(df[公告日期], errorscoerce) df[分红年度] pd.to_numeric(df[分红年度], errorscoerce)做完类型处理再做去重。同一个股票在同一个分红年度内可能有多次分红方案比如中期分红和年末分红这是合法的。但有些情况是接口因为分页重复拉取导致完全相同的行出现两次这时候用drop_duplicates按主键去重df.drop_duplicates(subset[证券代码, 分红年度, 公告日期], inplaceTrue)3.4 保存到 CSV 和 SQLite清洗完之后先把数据存下来后续分析随时可以读回来不需要重复请求网站。df.to_csv(dividend_data.csv, indexFalse, encodingutf-8-sig)注意这里用了utf-8-sig如果用默认的utf-8存 CSVExcel 打开会乱码。如果数据量比较大或者你想做更复杂的 SQL 查询可以存成 SQLiteimport sqlite3 conn sqlite3.connect(dividend.db) df.to_sql(dividend, conn, if_existsreplace, indexFalse) conn.close()这样整张表就落在本地了后续筛选连续分红、计算累计金额都很快。4. 结果解读从表格数字到有效信息4.1 先看懂“每10股派息”和“每股分红”的关系代码跑通只是第一步结果解读才是真正见功夫的地方。我第一次处理分红数据时差点犯了一个低级错误把“每10股派息2元”直接当成“每股分红2元”算出来的股息率高了十倍。所以要记住这两者换算关系每10股派息2元相当于每股分红0.2元。我用一个示例说明。假设某股票的分红记录如下分红年度每10股派息(元)每股分红(元)除权除息日20213.000.302022-06-1520224.500.452023-06-2020234.500.452024-06-18从这个表你能做的第一层分析是三年累计每股分红1.2元说明这家公司保持了持续分红的习惯。第二层分析是结合股价假设当前股价12元那么每股分红0.45元对应的股息率是3.75%这个数字在红利策略里属于中上水平。但这里要注意用最近一年的分红算股息率是简化做法严格一点的算法是把过去四个季度或者整个财年的分红全部加起来再除以当前股价。4.2 按年度聚合识别连续分红企业接口返回的数据经常是一个公司一次分红记录一条看起来非常零散。要做“连续分红”筛选必须按年度做聚合。# 先过滤掉没有分红金额的记录 df df.dropna(subset[每股派息(元)]) # 每个公司每个年度累计分红总额 annual df.groupby([证券代码, 证券简称, 分红年度])[每股派息(元)].sum().reset_index()之后再统计每个公司有分红记录的年度数量stats annual.groupby([证券代码, 证券简称]).agg( 分红年数(分红年度, nunique), 累计每股分红(每股派息(元), sum) ).reset_index() # 筛选过去5年至少都有分红的公司 recent_years [2020, 2021, 2022, 2023, 2024] mask annual[annual[分红年度].isin(recent_years)].groupby(证券代码)[分红年度].nunique() 5 stable_dividend_stocks mask[mask].index.tolist() five_year_list stats[stats[证券代码].isin(stable_dividend_stocks)]这个筛选逻辑非常实用。你要找“连续分红型选手”本质上是找那些分红行为稳定、没中断过的公司而不是某一年突然大方一次的公司。4.3 股息率的近似计算与口径提醒股息率的计算还需要股价数据。如果不想再单独写一个行情爬虫可以用东财页面上的行情数据接口也可以在数据分析阶段手动补充一批最新收盘价。用 DataFrame 合并即可# 假设 price_df 里有证券代码和最新收盘价两列 merged pd.merge(five_year_list, price_df, on证券代码, howleft) merged[股息率_近似] merged[累计每股分红] / merged[最新收盘价] * 100这里我特意加了“近似”两个字因为严格来说用历史累计分红除以现价计算出来的只能作为参考。更严谨的股息率是用“过去12个月实际派息总额/当前总市值”来算。但在做初筛的时候近似指标的排序能力已经足够用了。我自己分析了一阵子之后有一个体会分红数据单独看没有太大意义它必须和股价、行业、公司成长性放在一起看才有效。一个股息率8%的公司可能市场预期它未来盈利大降股价被砸下来才导致股息率被动升高。所以爬下来的数据只是原料解读的时候一定不能脱离基本面背景。提示以上所有指标的计算方法都只是数据分析层面的演示不构成任何投资建议。市场有风险决策请理性。5. 常见问题与排查技巧实录5.1 read_html 解析出来是空表或者少列很多文章会推荐你用pandas.read_html直接抓页面表格因为它一行代码就能搞定。但在东方财富这种大量依赖异步加载的页面上直接读取 HTML 文本往往拿不到表格——因为表格是JS动态渲染的requests 拿到的 HTML 里根本没有那些行。我的建议是优先走接口方式而不是死磕静态 HTML。如果确实需要read_html可以先在开发者工具里确认页面是服务端渲染的还是客户端渲染的。服务端渲染的页面可以直接用客户端渲染的必须找接口。判断方法很简单在浏览器里“查看网页源代码”搜索表格里的某个股票代码搜得到就是服务端渲染搜不到就是动态加载。另外read_html报错大半是因为 lxml 没装。它并不是 pandas 自带的需要单独安装。如果你用的是精简版 Python 环境这一步很容易漏。5.2 JSONP 解析后数据为空接口返回正常json.loads 也成功了但 DataFrame 是空的这个现象很常见。原因是 JSON 的结构和你预期的不一样——数据不在你之前猜测的那一层里。解决方法是打印完整的 JSON 层级结构或者写一个小函数把所有层级的 key 递归打印出来def print_keys(obj, prefix): if isinstance(obj, dict): for k, v in obj.items(): print(f{prefix}.{k}) print_keys(v, prefix . str(k)) elif isinstance(obj, list): print(f{prefix}[0] ... 共{len(obj)}个元素) if obj: print_keys(obj[0], prefix [0])跑一次就知道数据到底藏在哪个字段里了。很多时候它不在data.list而在result.data或者data.datas各个接口之间的差异就是这么大。5.3 请求太频繁被限制我试过并发请求同一站点的接口刚开始好好的几十次之后就突然返回一个验证码页面或者状态码403。这其实就是服务器的访问控制策略。处理办法并不是去绕过而是主动降低存在感。一是加延迟import time for i in range(10): # 请求逻辑 time.sleep(1.5) # 每次请求之间至少间隔1.5秒二是加随机延迟避免规律性太强引起识别import random time.sleep(random.uniform(1.0, 2.5))三是准备好重试机制。遇到网络抖动请求失败之后不要立刻放弃休息几秒再试一次。但是重试次数要设上限避免死循环。def fetch_with_retry(url, headers, retries3): for attempt in range(retries): try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() return resp except Exception: if attempt retries - 1: raise time.sleep(2 * (attempt 1))我做数据采集的时候一直提醒自己我们是访客网站是主人。主人不给进的时候停一停比硬闯要体面得多。5.4 字段里的短横线和“暂无”怎么处理原始数据里经常出现-、暂无、这种占位符。如果不处理后续无法计算。处理方式是用字典统一替换成 NaNplaceholder_map {-: pd.NA, --: pd.NA, 暂无: pd.NA, : pd.NA} df.replace(placeholder_map, inplaceTrue)然后该转数值的列再转一次数值类型。5.5 接口字段突然变化怎么办网站接口的参数名和返回字段名不是一成不变的尤其是一次大版本升级之后很多 key 都会改。硬编码字段名很容易出问题代码突然就跑不动了。我的习惯是在代码里维护一个“字段别名映射表”把可能出现的英文别名都映射到统一的中文列名上alias { SECURITY_CODE: 证券代码, SCODE: 证券代码, STOCK_CODE: 证券代码, NAME: 证券简称, SECURITY_NAME_ABBR: 证券简称, DIVIDENT_RATE: 每10股派息(元), }这样即使接口字段变了只要改映射表后面的分析代码不用大动。6. 后续扩展这套脚本还能怎么用我实际跑完这个项目之后又做了一点扩展把脚本挂在了云服务器上每天收盘后定时抓一遍数据自动追加到 SQLite 库然后用 matplotlib 画了一个简单的红利策略候选池报表每周看一次。整个自动化的成本很低但省下来的时间非常可观。如果你也打算长期维护这套数据可以再加一个“增量更新”的逻辑每次抓取前先查库里已有的最大公告日期只增量抓取比这个日期新的记录既能减少请求量又能让数据积累变得可持续。还有一个方向是和多数据源交叉验证。分红方案以交易所公告为准东方财富的数据整体校对得不错但不排除个别记录有延迟或者错漏。对重点关注的股票建议人工抽查一下原始公告做到心中有数。爬虫只是手段分析才是目的。这个项目最大的收获不是我会用 requests 和 pandas 了而是理解了分红的完整链路公司赚钱、董事会提议、股东大会通过、发布公告、股权登记、除权除息、资金到账。每个环节都有对应的日期和金额字段把这些字段拼起来你才真正看懂了“分红”这两个字背后发生了什么。数据抓下来只是开始后面怎么清洗、怎么解读、怎么用才是真正拉开差距的地方。你可以从自己关注的一两只股票开始跑一跑分红数据算一算累计分红和股息率眼见为实比看一百篇教程都有用。