资讯动态

sec-edgar:生产级SEC财报数据获取工具与金融数据工程实践

发布时间:2026/10/16 9:29:55 来源:尧图企业网站定制
简介这是一套面向金融数据开发者与Python量化初学者的SEC EDGAR数据库自动化下载工具包解决手动逐家检索、批量获取上市公司定期报告如10-K、10-Q及原始文件的低效痛点。项目基于Python构建提供简洁API调用能力支持通过股票代码如aapl或CIK编号一键拉取指定公司全部公开披露文件适用于财务分析、合规研究与另类数据采集等场景。资源包共103个文件以33个核心Python源码含filings、crawlers、utils模块、28个reStructuredText文档含API说明与使用示例、11个HTML测试页面覆盖CIK查询、多文件结果渲染等典型交互为主干辅以配置文件、索引文件及Shell脚本整体仅1.13MB轻量易部署。已有1462人学习下载用户可直接复用其CIK映射机制、日度索引解析逻辑与多线程下载框架并参考内置测试用例快速验证本地环境与EDGAR接口连通性。1. sec-edgar不是“爬虫工具”而是金融数据工程的起点——它把SEC官网变成你本地可索引、可版本化、可批量解析的财务文件仓库你有没有试过在 SEC 官网sec.gov/edgar手动查一家公司近十年的 10-K、10-Q、8-K点开一个链接等三秒加载PDF 打不开要切到新标签表格藏在 PDF 里还得复制粘贴——查五家公司一上午就没了。而 sec-edgar 这个项目不是教你写个 requests BeautifulSoup 的“五分钟小脚本”它是用 Python 构建的一套生产级 EDGAR 数据获取流水线支持按 CIK 批量拉取、自动识别文件类型HTML/PDF/EXCEL/XML、保留原始文件结构、生成元数据索引含 filing date、form type、accession number甚至预留了后续对接 Pandas、PyArrow 或 DuckDB 做结构化分析的接口。它不解决“怎么画财报图”但彻底终结“找数据比分析还累”的状态。适合正在做上市公司基本面研究、构建内部财务数据库、或需要复现学术论文中 EDGAR 数据集的 Python 工程师与量化研究员——尤其当你已经踩过“用正则从 HTML 里抠 XBRL 链接结果漏掉 2017 年前旧格式”这类坑之后你会意识到这不是又一个 crawler而是一份可维护、可审计、可回滚的财务数据基建契约。2. 为什么选 sec-edgar 而不是自己重写四个底层设计决策讲透它的不可替代性sec-edgar 不是“能跑就行”的玩具项目。它在设计上直面 EDGAR 数据生态的真实复杂性每个关键模块都对应一个长期被低估的工程难点。理解这些选型理由才能避免你在二次开发时掉进“看似简单、实则黑匣子”的陷阱。2.1 CIK 校验与公司名模糊匹配为什么不用requests.get(https://www.sec.gov/cgi-bin/browse-edgar?CIK...)硬凑EDGAR 的 CIK 是 10 位纯数字但用户输入常是公司名如 “Apple Inc.”、股票代码“AAPL”或带空格/标点的变体。sec-edgar 内置的cik_lookup.py模块采用两级策略第一级调用 SEC 官方公开的https://www.sec.gov/files/company_tickers.json每日更新做精确 ticker → CIK 映射第二级对非 ticker 输入使用fuzzywuzzy对company_tickers.json中的title字段做基于编辑距离的模糊匹配并加权过滤要求相似度 ≥ 85 且匹配结果唯一。提示这个设计绕开了“自己维护公司名-CIK 表”的运维黑洞。SEC 官方 ticker 文件包含 5,000 上市主体且每季度新增/退市变动硬编码等于每月手动 sync。# 示例模糊匹配逻辑节选来自 cik_lookup.py from fuzzywuzzy import fuzz def find_cik_by_name(company_name: str, threshold: int 85) - Optional[str]: tickers load_company_tickers() # 自动下载并缓存 company_tickers.json candidates [] for entry in tickers.values(): score fuzz.token_sort_ratio(company_name.lower(), entry[title].lower()) if score threshold: candidates.append((score, entry[cik_str])) if len(candidates) 1: return candidates[0][1] elif len(candidates) 1: raise ValueError(fAmbiguous match for {company_name}: {candidates}) else: raise ValueError(fNo match found for {company_name})这段代码背后是3 个隐性成本节约① 自动感知 SEC 数据源变更无需改代码② 拒绝低置信度匹配避免拉错公司③ 抛出明确异常而非静默返回空防止下游流程 silently fail。2.2 文件类型智能路由PDF/HTML/XBRL/EXCEL 不是“后缀决定”而是靠 HTTP Header Content-Type URL Path 三重判定EDGAR 的文件存储规则极其混乱同一份 10-K2010 年可能是 PDF2015 年是 HTML2020 年起强制 XBRL.xml但附件仍为 PDFhttps://www.sec.gov/Archives/edgar/data/320193/000032019323000106/0000320193-23-000106-index.htm是索引页HTML而.../000032019323000106/filing.htm才是主文档HTML.../000032019323000106/0000320193-23-000106.txt是原始文本TXTXBRL 实体文件.xml常嵌在 ZIP 包里需解压后识别 schema。sec-edgar 的file_downloader.py不依赖urlparse(url).path.split(.)[-1]而是先 HEAD 请求读取Content-Type如text/html; charsetUS-ASCII若为text/html再 GET 响应头中的Content-Disposition含filename0000320193-23-000106.txt最终 fallback 到 URL path 正则匹配r/(\d{10})-(\d{2})-(\d{6})\.(txt|htm|html|xml|pdf|xls|xlsx)。这种分层判定让下载器在面对 SEC 2005–2024 年所有历史格式时文件类型识别准确率稳定在 99.2%测试集覆盖 1,200 家公司 × 5 年 filings。自己写单层逻辑大概率在处理 Berkshire Hathaway 2008 年 PDF 和 2022 年 XBRL 时同一段代码给出相反结论。2.3 下载限速与请求头伪装不是“加个 time.sleep(1)”而是实现 RFC 7231 合规的客户端行为SEC 明确要求自动化访问必须遵守 Rate Limiting Policy 单 IP 每秒 ≤ 10 次请求必须设置User-Agent包含真实联系邮箱否则 403推荐添加Accept-Encoding: gzip减少带宽。sec-edgar 的rate_limiter.py实现了一个滑动窗口计数器Sliding Window Counter而非简单 sleep维护一个deque存储最近 1 秒内所有请求时间戳每次请求前len(deque) 10 则time.sleep(0.1)并重试User-Agent动态生成为sec-edgar/1.2.0 (github.com/xxx; contactexample.com)其中邮箱可由用户传参注入。这避免了两种常见翻车①sleep(1)导致吞吐量暴跌实际 QPS ≈ 1② 固定 UA 被 SEC 临时封禁因 UA 中无联系人违反政策。2.4 元数据持久化为什么用 SQLite 而不是 JSON 文件或 CSV每次下载完成sec-edgar 自动写入edgar_index.db表结构为字段类型说明cikTEXT PRIMARY KEY公司唯一标识accession_numberTEXT NOT NULL如0000320193-23-000106form_typeTEXT NOT NULL10-K,10-Q,8-K等filing_dateDATE NOT NULL格式YYYY-MM-DDfile_pathTEXT NOT NULL相对于data/的路径如320193/0000320193-23-000106/0000320193-23-000106.txtfile_size_bytesINTEGER文件字节数用于校验完整性download_timestampDATETIME DEFAULT CURRENT_TIMESTAMP本地下载时间SQLite 的优势在此刻凸显支持 ACID多进程并发写入不丢数据INSERT OR IGNORE防重复可直接SELECT * FROM filings WHERE cik320193 AND form_type10-K ORDER BY filing_date DESC LIMIT 5体积小单库 5MB 即可存 10 万条记录无需额外服务。对比 JSON无法高效查询对比 CSV无事务追加写入时进程崩溃易损坏文件对比 PostgreSQL过度设计增加部署复杂度。3. 从零启动三步完成首次完整下载含 CIK 获取、批量配置、断点续传不要被“金融数据工程”吓住——sec-edgar 的核心流程就是三个命令。但每个命令背后都有参数深意这里拆解真实工作流。3.1 第一步获取目标公司 CIK支持公司名、ticker、CIK 三种输入假设你要下载 Apple、Microsoft、Tesla 的 10-K。先确认它们的 CIK# 方式1用公司名推荐自动 fuzzy match python -m sec_edgar.cik_lookup Apple Inc. # 输出320193 python -m sec_edgar.cik_lookup MSFT # 输出789019 python -m sec_edgar.cik_lookup Tesla, Inc. # 输出1318605注意cik_lookup模块会自动下载并缓存company_tickers.json到~/.sec-edgar/cache/后续调用直接读缓存无需联网。3.2 第二步编写下载配置文件YAML 格式支持灵活组合创建config.yaml# config.yaml companies: - cik: 320193 # Apple forms: [10-K, 10-Q] years: [2020, 2021, 2022, 2023] - cik: 789019 # Microsoft forms: [10-K] years: [2019, 2020, 2021, 2022, 2023] - cik: 1318605 # Tesla forms: [10-K] years: [2020, 2021, 2022, 2023] output_dir: ./edgar_data user_agent_email: your.nameyourdomain.com # 必填SEC 政策要求 max_workers: 4 # 并发下载线程数建议 2–6 timeout: 30 # 单文件下载超时秒关键参数说明years: 不是“财报年份”而是filing date 所在年份。例如 Apple 2023 财年 10-K 实际于 2024-02-01 提交应设years: [2024]max_workers: 过高8易触发 SEC 限速过低1效率低下。实测 4 线程在 100Mbps 带宽下 QPS ≈ 8.2符合政策user_agent_email: 必须是真实可联系邮箱SEC 会据此发送异常通知如你的 IP 被临时限制。3.3 第三步执行下载支持中断恢复、失败重试、进度可视化# 首次运行完整下载 python -m sec_edgar.downloader --config config.yaml # 中断后恢复自动跳过已存在文件只下载缺失项 python -m sec_edgar.downloader --config config.yaml --resume # 仅下载特定公司调试用 python -m sec_edgar.downloader --cik 320193 --forms 10-K --years 2023执行时你会看到实时进度条Downloading filings for CIK 320193 (Apple Inc.)... [████████████████████████████████████████] 100% 10-K filings (2023) → Saved to ./edgar_data/320193/0000320193-23-000106/0000320193-23-000106.txt (12.4 MB) → Updated index: edgar_index.db (1 new record)逻辑说明--resume模式下程序会扫描output_dir下所有已存在的*-index.htm或*.txt文件解析其文件名提取accession_number查询edgar_index.db若该 accession 已存在且file_size_bytes匹配则跳过否则重新下载并校验 MD5防止网络中断导致文件截断。4. 避坑指南五个血泪经验换来的高频问题与根治方案用 sec-edgar 下载 EDGAR 数据90% 的失败不是代码 bug而是对 SEC 数据生态的误判。以下是我在某高校金融实验室部署该工具时A同学踩过的 5 个典型坑附带可直接复用的排查命令。4.1 现象下载大量.htm文件但打开全是“Document Not Found”页面原因SEC 的索引页*-index.htm和主文档页filing.htm是两个 URL。sec-edgar 默认下载索引页但用户误以为索引页 主文档。解决确认配置中forms字段是否正确。10-K和10-Q的主文档是filing.htm而8-K的主文档是filing.pdf或filing.xml。检查config.yaml是否写了forms: [10-K]正确而非forms: [10-K-index]错误不存在此 form type。4.2 现象cik_lookup返回No match found但公司确实在 SEC 官网可查原因SEC 的company_tickers.json只包含在美国交易所上市的公司。未上市的外国公司如 Toyota Motor Corp.CIK0000092550不在该文件中但可通过https://www.sec.gov/edgar/searchedgar/cik.htm手动查到。解决对非美国上市主体直接使用已知 CIK 下载跳过cik_lookup。或手动扩展company_tickers.json# 将 Toyota 的 CIK 加入自定义映射保存为 custom_ciks.json echo {toyota: {cik_str: 0000092550, title: TOYOTA MOTOR CORP}} custom_ciks.json # 修改 cik_lookup.py优先加载 custom_ciks.json4.3 现象下载速度极慢 1 QPS日志显示大量429 Too Many Requests原因user_agent_email未设置或格式错误如contact缺少域名导致 SEC 将请求归类为“未标识客户端”施加更严限速。解决检查config.yaml中user_agent_email是否为完整邮箱格式namedomain.com并在下载前用以下命令验证 UA 是否生效curl -I -H User-Agent: sec-edgar/1.2.0 (github.com/xxx; your.nameyourdomain.com) https://www.sec.gov/Archives/edgar/data/320193/000032019323000106/0000320193-23-000106-index.htm # 正常响应应有 HTTP/2 200而非 4294.4 现象edgar_index.db中file_path字段路径错误指向不存在的目录原因output_dir配置路径含相对路径如../data或 Windows 风格反斜杠.\data而 sec-edgar 内部用pathlib.Path处理对跨平台路径解析不一致。解决统一使用 POSIX 风格绝对路径。在config.yaml中output_dir: /home/user/projects/edgar_data # Linux/macOS # 或 output_dir: C:/projects/edgar_data # Windows用正斜杠提示运行前先mkdir -p /home/user/projects/edgar_data确保目录可写。4.5 现象下载的.xml文件无法用lxml解析报错XMLSyntaxError: None原因XBRL 文件.xml常被 SEC 存储为 ZIP 压缩包内的document.xmlsec-edgar 默认只下载 ZIP未自动解压。解决启用--extract-xbrl参数需安装zipfilepython -m sec_edgar.downloader --config config.yaml --extract-xbrl该参数会① 下载 ZIP② 解压到同名目录③ 将document.xml重命名为accession.xml并更新edgar_index.db。5. 进阶实战用 30 行代码把 EDGAR 文本转成结构化财报指标以 Apple 2023 10-K 为例下载只是开始。sec-edgar 的真正价值在于它输出的标准化文件结构让你能用确定性代码解析非结构化财报。下面以 Apple 2023 10-K0000320193-23-000106.txt为例提取“Total Revenue”、“Net Income”、“Cash and Cash Equivalents” 三个关键指标。这不是通用 OCR而是利用 SEC 强制的 XBRL 标签体系。5.1 理解 EDGAR 文本文件的隐藏结构Apple 的0000320193-23-000106.txt实际是HTML XBRL 标签混合体。关键不是“找文字”而是“找标签”。例如span classxbrl394328000000/span !-- 上下文中有 -- link relxbrl hrefhttps://www.sec.gov/Archives/edgar/data/320193/000032019323000106/aapl-20230930_htm.xml !-- 而 XML 中定义了 -- us-gaap:Revenues contextRefc1 unitRefu1 decimals3394328000000/us-gaap:Revenuessec-edgar 下载的.txt文件已将这些标签保留我们只需用lxml定位us-gaap:命名空间下的元素。5.2 提取代码30 行搞定三大指标含容错与单位转换# extract_financials.py from lxml import html import re from pathlib import Path def parse_10k_txt(file_path: str) - dict: tree html.parse(file_path) root tree.getroot() # 定义指标对应的 XBRL 标签来源SEC US-GAAP Taxonomy 2023 tags { revenues: us-gaap:Revenues, net_income: us-gaap:NetIncomeLoss, cash_equivalents: us-gaap:CashAndCashEquivalentsAtCarryingValue } result {} for key, tag_name in tags.items(): # 查找所有匹配标签忽略命名空间前缀差异 elements root.xpath(f//{tag_name} | //*[local-name()Revenues] | //*[local-name()NetIncomeLoss]) if not elements: result[key] None continue # 取第一个非空值财报通常有多个上下文取最新 period for elem in elements: text elem.text.strip() if elem.text else if text and re.match(r^-?\d\.?\d*$, text.replace(,, )): # 转为 float注意单位SEC XBRL 中金额单位为 USD无小数位即 394328000000 $394.3B value float(text.replace(,, )) # 自动除以 1e6 转为百万美元便于阅读 result[key] round(value / 1e6, 2) break else: result[key] None return result # 使用示例 if __name__ __main__: txt_file Path(./edgar_data/320193/0000320193-23-000106/0000320193-23-000106.txt) data parse_10k_txt(str(txt_file)) print(fApple 2023 10-K Financials (in $M):) print(f Revenues: ${data[revenues]}) print(f Net Income: ${data[net_income]}) print(f Cash Equivalents: ${data[cash_equivalents]})参数说明xpath中local-name()是关键因为不同公司 XBRL 命名空间前缀可能为us-gaap:、aapl:或dei:但本地名Revenues恒定re.match(r^-?\d\.?\d*$, ...)过滤掉NaN、N/A、—等非数字文本/ 1e6是行业惯例SEC XBRL 中金额单位为 USD无小数位394328000000$394,328,000,000除以1e6得$394,328M即$394.3B。5.3 验证与扩展如何确保提取结果可信别信代码一次跑通就完事。我给自己定的铁律是每新增一个指标必须人工核对三份财报。方法如下步骤操作目的1. 定位原文打开0000320193-23-000106.txt搜索Revenues确认标签是否存在位置是否在 Consolidated Statements of Operations 表中2. 核对数值打开 SEC 官网该文件 HTML 版https://www.sec.gov/Archives/edgar/data/320193/000032019323000106/0000320193-23-000106.htm找到 “Consolidated Statements of Operations” 表第一行确认394,328是否与代码输出一致注意逗号分隔3. 检查单位查看财报第 2 页 “Notes to Consolidated Financial Statements” → “Note 1. Summary of Significant Accounting Policies” → “Use of Estimates” 段落确认 “amounts are stated in millions of dollars”避免单位误判曾有项目把1e9当1e6导致营收虚高 1000 倍从那以后我每次新增一个 XBRL 标签解析都强制走一遍这三步。不是怕代码错是怕自己忘了 SEC 的文档游戏规则——它永远在细节里藏玄机。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑