ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Selenium爬取东方财富财报实战:从动态页面解析到数据落地

2026/10/2 15:18:33 拓冰建站 浏览量
Selenium爬取东方财富财报实战:从动态页面解析到数据落地 简介压缩包提供了一套完整的东方财富网上市公司财务报表数据爬取方案适合Python爬虫学习者、金融数据分析师及量化投资爱好者使用。项目中包含两个爬虫脚本eastmoney_crawler.py基于Selenium适合处理动态加载页面但速度较慢eastmoney_crawler2.py基于Requests速度提升上百倍推荐日常使用。配套的CSV示例数据涵盖资产负债表、利润表、现金流量表、业绩报表、业绩预告表、业绩快报表和预约披露时间表等常用财务表格可直接用于后续分析或建模。资源共12个文件以Python脚本和CSV数据文件为主压缩包大小约19.88MB目前已有356人学习过。通过该资源可以掌握财报类网站爬虫的基本思路、数据解析与存储方法并能在命令行中自定义年份、季度、报表类型及页码范围快速生成指定格式的本地数据文件为量化研究或财务分析提供数据基础。1. 这份爬东方财富财报的 selenium 项目能落地多少值不值得装环境网络爬虫最怕的不是抓不到数据而是抓下来的数据没法直接用。这份「爬取东方财富网上市公司财务报表数据」的人工智能项目实践目标非常明确用 selenium 把东方财富 F10 页面的资产负债表、利润表、现金流量表自动抓下来按股票代码和报告期组织成结构化数据喂给后续的分析和建模。它解决的是手动逐家复制财务数据的重复劳动——一家公司五分钟一个几百只股票的组合就是几十个小时。适合三类人正在做人工智能项目实践或大作业的同学需要批量财报做因子分析的量化入门者以及想系统学 selenium 面对动态页面怎么处理 iframe、懒加载和翻页的爬虫新手。zip 解压之后是一个完整的 Python 工程依赖少、改改股票列表就能跑。下面我从页面结构开始拆尽量把每一步的选型理由和坑都说清楚。2. 先摸清 F10 财报页面的结构iframe、动态渲染与抓取边界2.1 财报数据在 F10 里怎么组织iframe、懒加载与动态拼接拿到这类项目的第一件事不是写爬虫而是把目标页面看明白。东方财富网 F10 的财务分析模块入口是 emweb.securities.eastmoney.com 下面的 PC_HSF10/NewFinanceAnalysis地址里的 code 参数区分市场和股票沪市是 SH600519 这种带前缀的写法深市是 SZ000001。页面本身是一个典型的单页应用——外层框架、内层内容放在 iframe 里报表表格是 JS 通过 ajax 拉数据后动态拼出来的。这意味着两件事。第一直接查看页面源代码是拿不到数据的源代码里只有一堆 script 标签真正的表格要等浏览器执行完脚本才会出现在 DOM 里第二用 requests 裸请求这个 HTML 地址返回的也是空壳。所以这类场景反而 selenium 最直接——它驱动一个真实浏览器让页面完整渲染再对最终 DOM 做解析天然绕开了数据藏在 JS 里这个坎。从网络爬虫原理的角度看爬虫的三段式——下载、解析、存储——在这个项目里对应得非常清晰selenium 负责下载拿到渲染完成后的页面pandas 的 read_html 负责解析把 table 标签转成结构化数据CSV 或 Excel 负责存储。理解这个映射关系后面调参和改代码就有了坐标系不会出现报错不知道去哪查的茫然。顺便说一句数据源选型。同样能拿到上市公司财报的还有巨潮资讯和交易所官网巨潮的数据最权威但要下载 PDF 再解析交易所官网的接口结构相对复杂。东方财富的 F10 页面把三大报表整理成了现成的 HTML 表格不需要登录就能看对爬虫项目来说是性价比最高的选择这也是这份资源选它做数据源的原因。2.2 selenium 与直接调接口的取舍速度和可靠性的权衡实际动手前还有一个选型问题也是很多人会问的财报数据明明有 JSON 接口为什么不直接用 requests 调我整理了一张对比表基本覆盖了两种方案的差异对比项selenium 方案直接调接口方案JS 渲染依赖不需要自己处理浏览器自动执行需要抓包分析 ajax 请求并模拟单次抓取速度慢单页 3-8 秒快毫秒级页面改版抗性弱DOM 变了要改定位更弱接口返回结构变了要重写反爬暴露面接近真实用户操作容易被识别为脚本流量调试体验能看到页面渲染过程出错直观出错只有状态码和空 JSON我的态度很明确小批量抓取、以练手和学习人工智能项目实战为目的selenium 是更划算的选择。它慢但是每一步都能看见翻车了好查。接口方案虽然快但需要花时间抓包分析每一个参数而且东方财富的接口带有版本更新机制哪天接口升级脚本说废就废维护成本并不低。关于反爬这里要讲清楚底线。东方财富的 F10 页面没有强登录墙但短时间内高频访问会触发验证码或者临时限制。常见做法是每抓一页停 3-5 秒单批股票控制在几十只的量级跑完一批歇几分钟再跑下一批。这份资源里的代码默认参数也是按这个逻辑设计的后面扩规模时不要贪快。2.3 页面改版怎么快速定位文本 XPath 优先于 class 定位F10 页面改版不算频繁但每次改版class 名几乎都会变。这里有一个我反复用的定位策略凡是页签、按钮这种带固定文字的控件一律用文本 XPath 定位而不是复制页面上的 class。文本是给人看的改版时大概率保留class 是给前端工程师看的随手就改了。from selenium.webdriver.common.by import By # 优先用文本定位抓不到再退到 CSSdata 属性以实际页面为准 try: tab driver.find_element(By.XPATH, //*[text()资产负债表]) except Exception: tab driver.find_element(By.CSS_SELECTOR, li[data-type1])逻辑说明try 分支先按文本找找不到说明页面结构变了再走 CSS 兜底。CSS 选择器里的>python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate pip install -r requirements.txt参数说明venv 把依赖隔离在项目目录内requirements.txt 里核心是 selenium、pandas、openpyxl 三个包如果资源里没带清单手工装也是一样的命令pip install selenium pandas openpyxl。pandas 负责表格解析和落盘openpyxl 是 pandas 写 Excel 文件的后端引擎只存 CSV 可以不装但项目里一般会同时输出 xlsx建议装全。然后是 ChromeDriver。selenium 4.6 之后的版本自带 Selenium Manager只要 Chrome 版本不太旧driver 会自动下载匹配不用手动维护 chromedriver 路径这是很多人第一次跑通的关键改进。如果仍然报 session not created 之类的错先看浏览器版本chrome://version 里看主版本号再下载对应大版本的 driver 放进 PATH。注意driver 和 Chrome 大版本号不一致是报错频率最高的入口表现为浏览器闪退或者卡在创建 session 阶段。3.2 最小代码打开 F10、切入资产负债表页签环境就绪后先跑最小链路目标是拿到一家公司的资产负债表。以下代码是这份资源核心逻辑的简化版去掉日志和重试只留主干import time import pandas as pd from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def create_driver(): options Options() options.add_argument(--window-size1920,1080) options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) return webdriver.Chrome(optionsoptions) driver create_driver() driver.get(https://emweb.securities.eastmoney.com/PC_HSF10/NewFinanceAnalysis/Index?typewebcodeSH600519) # 等待主框架的 iframe 出现再切进去 wait WebDriverWait(driver, 20) wait.until(EC.presence_of_element_located((By.TAG_NAME, iframe))) driver.switch_to.frame(driver.find_element(By.TAG_NAME, iframe))代码逻辑先启动一个真实 Chrome窗口固定成 1920x1080避免小窗口下页面布局变化导致元素被折叠get 打开指定股票的财务分析页等 iframe 出现后切进去因为财报表格在这个 iframe 内部不切 frame后面怎么找都会扑空。接着切到资产负债表页签并抓表# 页签用文本定位比 class 抗改动 wait.until(EC.element_to_be_clickable((By.XPATH, //*[text()资产负债表]))) driver.find_element(By.XPATH, //*[text()资产负债表]).click() time.sleep(2) # 等表格重新渲染 # 表格渲染完成后抓 outerHTML交给 pandas 解析 table driver.find_element(By.XPATH, //table) html table.get_attribute(outerHTML) df pd.read_html(html, header0)[0] print(df.head())参数说明time.sleep(2) 是简单粗暴的等待方式工程里更推荐 WebDriverWait 配合元素可见条件但财报表格重新渲染的时机不固定sleep 反而更容易一次打通XPATH 按文本定位是因为页签文字「资产负债表」基本不变而 class 每次改版都可能变read_html 是 pandas 自带的 HTML 表格解析器直接把 table 转成 DataFrame比逐行找 tr/td 省事一个数量级。如果不想每次调试都弹出浏览器窗口可以在 create_driver 里加一行 options.add_argument(--headlessnew)。但这里有一个经验headless 模式下某些元素的渲染时机和定位行为与有头模式有细微差别第一次跑通之前建议保持有头模式能看见页面就不慌跑通了再切无头去做批处理。3.3 字段清洗与第一张报表落盘抓下来的 DataFrame 通常是多级表头第一列是科目名其余列是各个报告期的数值。要变成干净的分析表需要做一次标准化清洗df df.dropna(howall) df df.drop_duplicates() df.columns [str(c).replace(报告期, ).strip() for c in df.columns] df df.rename(columns{df.columns[0]: 科目}) # utf-8-sig 编码让 Excel 直接打开不乱码 df.to_csv(SH600519_资产负债表.csv, indexFalse, encodingutf-8-sig)逻辑说明先把整行全空的行丢掉这类行是页面里的分隔线或者空行再清掉列名里的「报告期」字样让列名只保留日期方便后面按时间维度合并第一列统一命名成「科目」。这里有个细节容易踩to_csv 要用 utf-8-sig 而不是 utf-8否则 Excel 打开 CSV 时中文科目会乱码。如果同时要 xlsx 格式一行代码就够了df.to_excel(SH600519_资产负债表.xlsx, indexFalse)跑完这一步当前目录下就有一张贵州茅台的资产负债表。先别急着继续抓打开东方财富网页同一家公司的报表用资产总计和负债合计算一下资产负债率跟 CSV 里的数字对一下一致再扩大规模。这是所有爬虫项目都应该养成的验证习惯——第一个数对了后面才有意义。4. 扩大抓取规模多股票循环、报告期口径与落盘结构设计4.1 用股票列表驱动循环抓全市场的节奏控制单只股票打通后下一步是让脚本批量跑。资源里一般会带一个股票列表文件至少包含 code 和 name 两列自己维护也可以。核心改动是把单只逻辑包成函数循环调用并加上失败记录和随机延时import random import time from pathlib import Path def fetch_report(driver, code, report_type资产负债表): 抓单只股票的指定报表返回 DataFrame url fhttps://emweb.securities.eastmoney.com/PC_HSF10/NewFinanceAnalysis/Index?typewebcode{code} driver.get(url) wait.until(EC.presence_of_element_located((By.TAG_NAME, iframe))) driver.switch_to.frame(driver.find_element(By.TAG_NAME, iframe)) wait.until(EC.element_to_be_clickable((By.XPATH, f//*[text(){report_type}]))) driver.find_element(By.XPATH, f//*[text(){report_type}]).click() time.sleep(2) table driver.find_element(By.XPATH, //table) return pd.read_html(table.get_attribute(outerHTML), header0)[0] stocks [SH600519, SZ000001, SH601318, SZ300750] for code in stocks: try: df fetch_report(driver, code) df df.dropna(howall) df.to_csv(fdata/{code}_资产负债表.csv, indexFalse, encodingutf-8-sig) print(f[OK] {code}) except Exception as e: print(f[FAIL] {code}: {e}) time.sleep(random.uniform(3, 6))逻辑说明fetch_report 把第 3 章的链路封装成函数report_type 用参数传入三大报表共用一段逻辑循环里 try/except 是必须的几百家公司里总会有几家页面加载超时或者元素缺失一个异常中断整个批次是最亏的每跑完一家随机停 3-6 秒模拟人工节奏减少被限流的概率。参数怎么调延时区间建议从 3-6 秒起步如果频繁触发验证码就加到 8-10 秒。窗口大小 1920x1080 不要轻易缩小某些报表列数多窄窗口下表格可能横向滚动定位虽然仍有效但截图调试时容易误导判断。4.2 三大报表的报告期口径年度、中期与单季数据的差别东方财富 F10 的财务分析页面上报告期有「按报告期」和「按年度」两种口径。按报告期会给出每个季末节点的累计数据按年度只给年末数据。做基本面分析绝大多数场景用按报告期因为要的是时间序列上的连续快照。报表类型F10 页签名关键科目举例分析注重点资产负债表资产负债表货币资金、资产总计、负债合计偿债能力与杠杆水平利润表利润表营业总收入、净利润、扣非净利润盈利能力与盈利质量现金流量表现金流量表三类活动的现金流净额现金流的真实性与持续性三类报表的字段完全不同但抓取流程一致所以 fetch_report 里的 report_type 直接替换页签文本就行。需要特别注意「按报告期/按年度」的切换有的页面默认展示年度数据要拿季度序列就得先点「按报告期」。这个切换按钮在不同版本的 F10 页面位置有差异稳妥做法是判断元素存在再点击# 如果页面上存在按报告期按钮就点一下不存在则跳过 if driver.find_elements(By.XPATH, //*[text()按报告期]): driver.find_element(By.XPATH, //*[text()按报告期]).click()这个写法的好处是安全的元素存在才点不存在直接跳过不会因为页面版本差异直接报 NoSuchElementException批量跑的时候少了很多无谓的中断。4.3 落盘结构按公司分文件还是按报表分文件批量跑起来之后文件怎么组织会直接影响后续使用体验。两种常见方案各有适用场景按股票分目录适合做单公司的时间序列追踪按报表类型分目录适合做全市场的截面分析。我一般推荐混合式data/报表类型/股票代码.csv兼顾两种分析路径。from pathlib import Path report_type 资产负债表 out_dir Path(data) / report_type out_dir.mkdir(parentsTrue, exist_okTrue) for code in stocks: df fetch_report(driver, code, report_type) df.to_csv(out_dir / f{code}.csv, indexFalse, encodingutf-8-sig)最终目录结构类似 data/资产负债表/SH600519.csv、data/利润表/SZ000001.csv。如果之后要喂给模型可以写一个 merge 脚本把同一报表类型的所有文件纵向拼接并加一列股票代码import glob import pandas as pd frames [] for fp in glob.glob(data/资产负债表/*.csv): t pd.read_csv(fp) t.insert(0, code, fp.split(/)[-1].replace(.csv, )) frames.append(t) merged pd.concat(frames, ignore_indexTrue) merged.to_csv(all_balance_sheet.csv, indexFalse, encodingutf-8-sig)逻辑说明insert 把股票代码放到第一列concat 按行拼接最后得到一张「公司 科目 报告期」的长表这是量化分析里最常见的面板数据雏形。到这一步抓取规模的问题基本解决接下来进入排错环节。5. 避坑与常见问题排查爬东方财富财报的五个翻车现场这章是血泪经验汇总。下面五条问题我在跑类似项目时基本都碰到过每一条按「现象 → 原因 → 解决」展开你可以直接对照排查。5.1 现象浏览器闪退报 session not created原因Chrome 自动升级了ChromeDriver 还停在旧版本版本号不匹配。selenium 4.6 以下的版本这个坑特别深因为需要手动维护 driver 路径。解决先看 chrome://version 里的主版本号去下载同大版本的 driver 覆盖。如果用的 selenium 4.6 以上把 Chrome 和 selenium 都升到较新版本让 Selenium Manager 自动匹配。另外提醒一句如果资源包里带了 chromedriver.exe先确认它对应哪个 Chrome 版本再决定是否替换不要盲目用旧驱动。报错信息里出现 This version of ChromeDriver only supports Chrome version 时基本就锁定是这个问题不用怀疑别的地方。5.2 现象switch_to.frame 之后find_element 一直抛 NoSuchElementException原因iframe 还没渲染完成就切进去了或者页面有多个 iframe切错了。F10 页面外层框架渲染很快内层财报 iframe 是异步加载的两者之间有明显时间差过早切入自然什么都找不到。解决切 frame 之前先等 iframe 出现在 DOM 里并且用 frame_to_be_available_and_switch_to_it 这个专用等待条件一步到位wait WebDriverWait(driver, 20) wait.until(EC.frame_to_be_available_and_switch_to_it( driver.find_element(By.TAG_NAME, iframe) ))另外如果页面里 iframe 不止一个先打印数量确认特征frames driver.find_elements(By.TAG_NAME, iframe) print(len(frames), [f.get_attribute(src) for f in frames])用 src 特征区分哪个才是财报内容的 iframe避免切到顶栏导航或者广告位。5.3 现象read_html 解析出来只有表头没有数据行原因表格里的数据行是懒加载渲染的页面操作太快JS 还没来得及把行数据拼出来outerHTML 就被抓走了。这是动态页面最常见的竞态问题。解决点击页签后强制等一下或者更稳健地设置等待条件——等到表格里出现已知科目文本再抓。比如「货币资金」几乎每家公司的资产负债表都有就可以作为渲染完成的信号wait.until(EC.presence_of_element_located((By.XPATH, //*[text()货币资金])))调试时如果不确定是渲染慢还是定位错抓完之后立刻执行 driver.save_screenshot(debug.png)看截图里表格区域有没有数据。有数据就是时序问题没数据就是定位问题一张截图能省半小时瞎猜。5.4 现象抓了五十家之后开始弹验证码后续全部失败原因请求频率过高触发了站点的频率限制。单进程连续跑IP 维度很容易被标记。东方财富的治理策略不算激进但连续高频访问一定会触发。解决把随机延时从 3-6 秒拉到 8-10 秒批次之间停 5 分钟再继续股票列表拆成多个小批次错峰执行。还有一个技巧触发验证之后把 driver 整个关掉重启清理旧会话的 cookie再重新打开页面很多时候就恢复了。原则是「慢就是快」宁可多花半小时把时间拉长也不要前功尽弃重跑一遍。5.5 现象CSV 打开中文乱码或者科目列和数值列错位原因中文乱码是编码问题CSV 用 utf-8 写入Excel 默认按 GBK 打开两边对不上错位则多半是表头有多级结构read_html 的 header 参数没有对齐pandas 把合并单元格拆开了。解决写入统一用 utf-8-sig这是 Windows 生态下最省心的方案。解析前先打印 df.shape 和 df.head()看清楚表头结构再决定 header 传 0 还是 1。另外数值列有时会被 pandas 读成 object 类型需要统一做一次类型转换# 从第二列开始尝试转数值转不了的置为 NaN for col in df.columns[1:]: df[col] pd.to_numeric(df[col], errorscoerce)转换后留意出现的 NaN那些位置往往是页面里显示为「--」的空值属于正常情况不要当成抓取失败。6. 进阶把脚本改造成定时更新的财报数据管线批量抓完只是第一步真正的使用场景是定期更新——季报、中报、年报发布后增量补抓而不是每次全量重跑。常见做法是用系统的定时任务调用主脚本或者在自己项目里用 APScheduler 做调度。工程实践里我更推荐先把抓取脚本改成支持命令行参数python crawl.py --type 资产负债表 --batch 1 --sleep-min 3 --sleep-max 6参数解析用 argparse--batch 支持分片这样定时任务可以把全市场拆成多个批次错峰执行。增量逻辑很直接每次跑之前扫描一遍目标目录里已有的 CSV 文件名只抓缺失的股票代码报告期维度上固定抓最近几期即可不需要每次都拉全历史省时间也降低对数据源的打扰。验证环节我养成了一个习惯每次跑完随机抽三家公司打开东方财富网页把最新一期的资产总计和净利润跟 CSV 里的值做人工对比。这个动作看起来笨但能一次性暴露三类问题——页签点错导致抓成别的报表、报告期口径不对导致数值对不上、列错位导致科目张冠李戴。从那次抓出整列错位数据、还直接拿去算了指标之后我再没跳过这个抽检。定时任务本身不复杂Windows 上用任务计划程序调用 venv 里的 python.exe 跑 crawl.pyLinux 上写 cron 表达式指定在季报发布窗口后的日期执行。数据落地之后再接到下一步分析脚本里这套爬虫才真正完成了从「页面数据」到「可分析数据集」的闭环。希望帮到你。本文还有配套的精品资源点击获取