简介:压缩包收录了沪深两市三千余只A股股票的基础信息,涵盖股票代码、名称、所属市场与板块、行业分类、公司基本信息、核心财务数据、市值、市盈率、市净率、流通股本及分红派息等字段,可作为投资者进行个股筛选、行业对比和财务健康度评估的底层数据参考。压缩包内共四十八个文件,主要包含XML数据文件、C#工程源码、DLL动态库、配置文件以及可执行程序,并附有说明文档与少量图片,整体大小仅5.2MB,便于快速下载与离线解析。目前已有1722人学习使用,适合具有一定数据处理基础的分析师或量化入门者。借助包内数据,可复用Python Pandas等工具完成数据清洗、统计分析与可视化,进而挖掘估值与成长性指标;也可作为开发股票信息查询或分析系统的数据源。需要注意的是,基础信息属于静态历史数据,实际投资仍需结合宏观经济、政策变化及市场情绪综合判断。
1. 这份rar背后,是一个被低估的数据工程问题
第一次拿到《获取所有A股股票基础信息.rar》这类打包数据时,我先入为主地以为解压就能直接用。结果打开一看,满屏都是最新价、涨跌幅这类行情字段,真正关心的行业、上市日期、流通股本全散在另外几个文件里,编码还不对。做量化或财报分析的人都懂:最耗精力的不是策略本身,而是把全市场五千多家A股的股票代码、简称、行业、上市时间、股本结构这类基础字段一次性洗干净存下来。这份基础信息包要解决的正是这个起点问题——它不是给你选股结论,而是给你一张能放心做二次加工的全市场快照。适合谁?想跳过逐个查行情页面的重复劳动、需要一份本地全量清单做筛选池或入库开发的从业者。
2. 数据从哪来:三条路线怎么选
2.1 交易所披露、数据接口与终端导出的取舍
获取A股基础信息,常见的做法是三条路线里选一条走。第一条路,直接抓取交易所公开披露的挂牌公司名单和公司概况公告。这条路最权威,但公开内容形态太散,有PDF、有网页、有不同命名的附件,解析成本很高,更新节奏也跟不上你要做快照的时点。第二条路,对接某数据服务商的批量接口。服务商把公开信息整理成结构化JSON返回,一次分页请求能拿几百条记录,字段从股票代码到实控人都有,是实际工作中最常走的路线。免费的额度和频率做每日全量快照足够,付费版本主要多出的是分钟级频率和更多衍生指标,而不是基础字段本身。第三条路,用行情终端手动导出。在软件里选好板块,右键导出CSV或Excel,体感最快,但导出列数有限,也没法做到定时自动拉取,适合临时用一次,不适合长期维护。
| 路线 | 权威性 | 字段完整度 | 自动化程度 | 初始成本 | 适用场景 |
|---|---|---|---|---|---|
| 交易所公开披露 | 最高 | 字段分散,需自行整合 | 低,需解析多种格式 | 人力成本高 | 做数据源交叉校验 |
| 数据服务商接口 | 较高 | 字段宽,结构化返回 | 高,脚本定时拉取 | 注册即可用 | 自动化构建快照 |
| 行情终端导出 | 一般 | 受终端字段限制 | 低,手动操作 | 需安装终端 | 一次性临时提取 |
这三条路线我建议按「接口为主、披露文件为辅」来搭配。主力数据源负责产出全部基础字段,交易所披露文件只在怀疑某个字段对不上时用来人工核查。这样既不用把解析PDF的苦活全揽下来,又能保证口径出问题时你有地方追根溯源。
2.2 为什么基础信息用快照而不是数据库
我一般不会为这些基础字段直接搭一套数据库。原因有三点。第一,数据变更频率是公告级的,一家公司改行业、改股本,一年里也没几次,没必要用支持毫秒写入的系统去维护一份每天变不了几十条的表。第二,rar包本质是一份全量快照,放网盘、放NAS、发同事都方便,解压即用,不依赖内网数据库连接。对于一个后续要拿去写策略或喂给建模流程的文件,降低使用门槛是第一位的。第三,数据库方案要求你搭建同步任务、处理主键冲突、管理连接池,这些工作量对基础信息这种低频数据来说严重超配。
但这不意味着永远不碰数据库。更合理的做法是:采集端只产出快照文件,谁要谁自己导走,入库是下游使用者的行为。团队里一旦有人说要把基础信息做成接口服务,直接从快照文件反向灌库就行,不需要在采集端维护两份存储。这个取舍直接决定了你交付的rar包能不能成为团队内部的通用数据资产。
2.3 动手前先定字段,别抓到什么存什么
接口返回的JSON嵌套很深,同一个字段在不同接口里含义可能完全不同,抓到什么存什么是标准化翻车的第一来源。动手前我先落一张字段清单表,把每个字段的业务含义固定下来,之后再写代码就只是按单子取数,不会边写边改。
| 字段名 | 业务含义 | 数据类型 | 说明 |
|---|---|---|---|
| code | 股票代码 | str,6位 | 唯一主键,需补齐前导零 |
| name | 中文简称 | str | 不含N、C等临时前缀 |
| name_en | 英文简称 | str | 缺失时可空 |
| industry | 所属行业 | str | 以数据源分类为准,注明版本 |
| area | 注册地省份 | str | 缺失时置空 |
| list_date | 上市日期 | date | 统一成YYYY-MM-DD |
| total_share | 总股本(万股) | float | 关注数值单位 |
| float_share | 流通股本(万股) | float | 不等于总股本减限售股 |
| total_mv_yi | 总市值(亿元) | float | 收盘口径 |
| float_mv_yi | 流通市值(亿元) | float | 收盘口径 |
| controller | 实际控制人 | str | 缺失率高,不插补 |
| exchange | 所属板块 | str | 沪市主板/深市主板/科创板等 |
这张表里真正决定基础信息可用性的其实是后三行。实际控制人字段缺失率可能超过四成,早期版本里我直接删掉,后来做舆情分析需要穿透股权关系时才发现缺了它很麻烦,所以现在保留字段但允许空值。总市值和流通市值这类派生指标,每个数据源算法都有一点差异,包里只存源端原始值,不做二次加工,否则对账时说不清口径。
3. 拉全量清单到打包rar:一套可照抄的流程
3.1 拉取全量证券清单
把全市场股票基础清单拿到手,本质是一次分页拉取。下面是这套流程里最核心的一段代码,按数据服务商常见的JSON返回格式写成,接口地址替换成你自己的数据源即可。
import requests import pandas as pd import time def fetch_stock_list(api_url, page, page_size): # 常见的批量数据接口为POST,用JSON传分页参数 payload = { "page": page, "pageSize": page_size, "querys": {"market": "all"} } resp = requests.post(api_url, json=payload, timeout=30) # 约定code为0表示业务正常,部分接口返回HTTP状态码非200 body = resp.json() if body.get("code") != 0: raise RuntimeError(f"接口异常:{body.get('msg')}") return body["data"]["list"] all_rows = [] for page in range(1, 50): items = fetch_stock_list("https://api.example.com/market/list", page, 500) if not items: break all_rows.extend(items) time.sleep(0.2) # 每页间隔200ms,避免对接口造成访问压力 df_stock = pd.DataFrame(all_rows) print(f"全量证券清单:{len(df_stock)} 条") df_stock.to_csv("stock_list_raw.csv", index=False)这段代码的核心逻辑是循环取页直到接口返回空列表,循环条件没有写死页数上限,未来全市场上市公司数量增加时不会漏数据。time.sleep放在页面级而不是请求级,200ms间隔在性能和频率限制之间取一个平衡值。pageSize取500,单次响应体控制在1MB左右的常见量级;timeout设30秒,避免弱网环境下请求挂死。接口返回的业务码不一定叫code,有些叫status或者success,写代码前先用一条curl确认返回结构,这是最省事的习惯。
注意分批拉取后先看一眼列表里混进了什么。正常的全量接口往往不止返回A股股票,还有可转债、ETF、优先股、REITs等品种,这些都是后续标准化的隐患。此时先不急着过滤,保留原始表,过滤动作放到清洗阶段统一做。
3.2 逐个补齐公司和股本资料
全量清单里通常只有代码、简称、所属市场这类基础字段,行业、上市日期、股本结构还要对每个代码单独调一次信息接口。这块的逻辑就是一个for循环套一个接口调用,没有太多花活,但有两个细节值得注意。
import requests import pandas as pd import time def fetch_stock_detail(code, api_url): # 个股资料接口通常只接受单代码查询 params = {"code": code} resp = requests.get(api_url, params=params, timeout=10) body = resp.json() if body.get("code") != 0: return None detail = body.get("data", {}).get("detail") if detail is None: return None detail["code"] = code return detail code_list = df_stock["code"].tolist() detail_rows = [] for code in code_list: detail = fetch_stock_detail("https://api.example.com/stock/detail", code) if detail is not None: detail_rows.append(detail) time.sleep(0.1) # 单代码接口限频更严格,间隔收紧到100ms df_detail = pd.DataFrame(detail_rows) df_merged = df_stock.merge(df_detail, on="code", how="left", validate="one_to_one") df_merged.to_csv("stock_basic_info_tmp.csv", index=False)merge时指定validate="one_to_one"是防止数据源侧返回重复行造成记录数膨胀,一旦接口异常出现重复数据,pandas会直接抛错而不是悄悄把行数翻倍。这个报错本身就是在提醒你:先检查数据源,不要带着重复数据继续做。time.sleep放在请求之后而不是之前,保证即使某个请求超时抛异常,循环退出后也有个缓冲间隔。接口里返回的detail,常见字段包括总股本、流通股本、上市日期、注册资本,不同数据源的字段命名差异很大,在这里统一改好名字,后续清洗阶段就不用手忙脚乱。
还有一类接口会把注册资本和总股本混在一个字段里返回,如果看到数值明显大于总股本了十倍以上,基本就是拿注册资本当股本用的脏数据,需要在合并前按业务常识预筛选。
3.3 加时间戳、分离板块和文件落盘
数据合并完成后,下一步不是急着打包,而是给数据加上生成日期标识并确定落盘目录。文件名里带日期是这份rar包能否长期使用的分水岭,不带日期的快照文件三个月后再看就是一坨无法回溯的混沌状态。
from datetime import datetime today = datetime.now().strftime("%Y%m%d") out_path = f"output/stock_basic_info_{today}.csv" df_merged.to_csv(out_path, index=False, encoding="utf-8-sig") # 按板块分组统计数量,便于解压后先核对 print(df_merged.groupby("exchange").size())CSV编码统一用utf-8-sig,这样Windows上Excel双击打开不会乱码,Python读入时也不需要额外处理BOM。按板块分组统计数量是个很实用的小动作,解压后肉眼核对第一道关卡就是看每个板块的数量是否在合理区间,比如沪市主板一千八九百家、创业板一千三四百家,偏差太大说明拉取过程出了问题,不用等入库后才发现。
打包这一步用rar命令行完成,比在脚本里调用压缩库要直接得多:
cd output rar a stock_basic_info.rar stock_basic_info_20250115.csv习惯上我会在output目录里预留一个老快照目录,保留最近三个版本的历史快照而不是用新文件覆盖旧的。这样一旦发现数据源某天返回了异常字段,至少能回退到上一份可用的包。rar命令行在服务器上一般需要单独安装,安装好之后把路径加进环境变量,打包命令就能在任意目录执行。如果你的服务器上没有rar工具,先用zip打包过渡也可以,但交付给同事时Windows的rar工具链兼容性更好,这也是这份数据包选择rar格式的实际原因。
4. 字段口径和标准化:决定这份基础信息能不能用
4.1 全市场代码与板块的对应规则
A股股票代码的板块归属有一套相对稳定的规则,但每年都有新代码段启用,只靠口诀记代码不加校验代码,早晚会翻车。
| 代码段 | 所属板块 | 常见特征 |
|---|---|---|
| 600/601/603/605开头 | 沪市主板 | 存量老股为主 |
| 688开头 | 科创板 | 2020年后集中上市 |
| 000/001/002/003开头 | 深市主板 | 含原中小板合并标的 |
| 300/301开头 | 创业板 | 注册制后扩容明显 |
| 8开头、4开头、920开头 | 北交所 | 原新三板精选层平移 |
| 900、200开头 | B股 | 用外币交易,A股包内不含 |
清洗时不能看到6位数字就放行。B股代码虽然也是6位,但它不在A股基础信息包的范围内,需要显式剔除;可转债代码11开头、ETF代码15开头,这些非股票品种也会有行情数据,同样要过滤。硬编码板块规则表也许是笨办法,但在没有官方统一代码表可下载的现实条件下,这张表就是最可靠的标准。遇到代码段规则之外的新代码,先查上市板块名单,不要一律放行,也不要一律丢弃,单独建一个unknown清单人工过一遍。
4.2 名称、市值和上市日期的清洗
名称清洗是看起来简单、实际最容易带脏数据过关的一步。新股上市首日简称前会带N,上市次日至第五日会带C,这些临时标记不属于公司真实简称,必须去掉。全角空格和中文里的特殊空白符也是高频噪声源,一个肉眼看不见的\u3000字符就能让两条本该匹配的记录对不上。
# 名称清洗:去全角空格、去新股标记、去尾部空格 def clean_name(raw): if not isinstance(raw, str): return "" s = raw.replace("\u3000", "").strip() if len(s) >= 3 and s[0] in ("N", "C", "U"): # U为科创板上市后首次实现盈利的标识 s = s[1:] return s # 上市日期统一成date类型,部分接口返回带时分秒的字符串 df_final["list_date"] = pd.to_datetime(df_final["list_date"]).dt.date # 股票代码严格取6位并补齐前导零 df_final["code"] = df_final["code"].astype(str).str.zfill(6)代码里的zfill处理的是类似1被存成"1"的情况,统一补成000001才能和其他数据源做关联。市值字段的清洗则要在数值和单位两个维度同时下手。有的接口返回的是字符串"1.2亿",有的返回的是原始元单位的浮点数,混着存进同一列就是灾难。我习惯统一换算成亿元并写入列名,写成total_mv_yi,让下游使用者一眼看到单位,不用去猜。
上市日期字段还要警惕一类特殊值:部分接口把尚未上市的公司上市日期返回成"1970-01-01"或者"1900-01-01"作为空值占位。遇到这种默认时间戳,直接置空比保留它更安全,否则按上市年份做筛选时这些幽灵纪录会混进结果。
4.3 缺失值和字段基准日的分级处理
全量数据不可能每个字段都完整,处理缺失值之前先想清楚每个字段的缺失是系统性的还是偶发的,再决定处置策略。系统性缺失是指一个区块的数据整个拿不到,比如个别数据源不提供实际控制人字段,那么这一列空值再多也不是清洗能解决的,只能换源或置空。偶发性缺失是指个别股票记录单独缺某个值,大概率是源头录入不完整,按下面这张表分级处理。
| 字段 | 典型缺失率 | 处理策略 | 原因 |
|---|---|---|---|
| 实际控制人 | 30%以上 | 置空,不插补 | 穿透披露口径复杂,插补结果不可信 |
| 上市日期 | 低 | 通过招股说明书披露日推算 | 拟上市公司数据源更新时间滞后 |
| 总股本 | 极低 | 查找披露公告核对 | 新股上市后数据源未及时同步 |
| 行业分类 | 中等 | 按最近一期报告分类更新 | 行业分类每年会做再分类调整 |
不插补实际控制人是血泪教训换来的规矩。早期版本为了追求字段完整率,用公司名称相似度去推断实控人,结果把两家名称相似但股权毫无关联的公司插补成同一实控人,直接污染了下游的关联关系分析。从那以后,凡是无法从权威源交叉验证的字段,一律置空。
字段基准日比缺失值更容易被忽略。接口返回的当前股本总额,实质上是公司最新一期披露报告的数据,遇到增发、回购、限售解禁时,它的更新时点可能比本地文件生成日期早几十天。包里需要加一个data_asof_date字段,标记每个观测值在源端对应的基准日期,否则后面对比两个时期的快照时会发现股本数据对不上,那时候再查就跟大海捞针一样。
5. 避坑:基础信息抓取中的高频翻车现场
5.1 被封限流:前两千条正常,后两千条全部403
现象:脚本前两天跑得好好的,第三天开始跑到一半接口返回403 Forbidden,重试几次还是同样结果,日志里能看到连续出现status code异常。原因:请求频率固定且没有随机抖动,代码里写死了sleep(0.2),服务商的风控系统识别出这是机器行为,把该IP或账号的访问权限临时封禁。解决:把固定间隔改成随机区间,同时加上指数退避重试,把单次请求的UA和Cookie保持一致,模拟出正常浏览器的访问特征。
import random import time def sleep_random(): # 随机控制在100ms到450ms之间,避免固定节奏被识别 time.sleep(random.uniform(0.1, 0.45)) def request_with_retry(url, params, retries=3): for attempt in range(retries): try: resp = requests.get(url, params=params, timeout=10) return resp except requests.exceptions.RequestException: if attempt == retries - 1: raise # 重试等待时间按指数增长:2s、4s、8s time.sleep(2 ** attempt)不要只在客户端加延时,服务端的风控还有一层是按账号维度统计每日调用总量。每天拉一次全量已经足够,别为了验证代码反复重跑,第二次跑就把当天的配额消耗掉一半。被封后最好的处理是停掉脚本等24小时自然解封,而不是换个代理继续打,后者容易把账号送上黑名单。
5.2 代码段判断失误:包里混进去一堆非股票品种
现象:拉完清单一看总记录数,比预期多了两千多条,解压后一查发现可转债、ETF、REITs全混在里面。原因:全量接口的market参数传了all,服务商返回的是全证券品种清单,不是A股股票清单。解决:用代码段白名单过滤,并确认每个代码段的板块属性符合预期。
def is_a_stock(code: str) -> bool: # 只保留A股股票代码段,B股、可转债、基金不在包内 valid_prefix = ( ("600", "601", "603", "605"), ("688",), ("000", "001", "002", "003", "300", "301"), ("8", "920"), ) return any(code.startswith(p) for p in valid_prefix)这里用startswith匹配时注意8开头的北交所代码实际是8开头的三位段,比如82、83、87、88,写前缀时至少要保留两位,否则会把8开头的其他品种误收进来。过滤完还有一道复核,就是按第3章里按exchange分组的数量核对,数量对不上就知道过滤逻辑写错了。
5.3 股本数据日期错位:昨天拉的记录和今天对不上
现象:同一家公司的总股本字段,昨天快照里是5.2亿股,今天快照变成5.6亿股,但当天并没有除权除息或增发公告。原因:数据源返回的股本取自最近一期财报或公告,而不是拉取当日的最新市值口径,两天的快照实际对应的是公司两个不同里程碑的数据状态。解决:在字段表里单独维护股本基准日,并在文件名之外再给每条记录打上update_date标记,分析时优先按update_date对齐而不是按文件生成日对齐。
按文件生成日对齐数据是新手最容易踩的隐性坑。你以为两份文件都是当日快照,实际上其中一份的字段基准日是三个月前的财报发布日期,但基础信息场景下这种滞后通常可以接受,问题只在于你把它当成了当日数据用。
5.4 跨平台解压乱码:Windows打包Linux乱码
现象:rar包在Windows下解压一切正常,传到Linux服务器用unar解压后,CSV文件名变成乱码,文件里的中文列名和简称全是乱码。原因:Windows下的rar工具默认用GBK编码保存文件名,而Linux默认按UTF-8解码,两边编码表对不上。解决:CSV内容统一用utf-8-sig编码写入,打包时文件名全部使用英文,不要出现"最终版"、"股票数据"这类中文文件名。
文件名用英文、内容用UTF-8-SIG,这两个习惯配合在一起之后,跨平台问题基本消失。如果你收到的rar包已经乱码,先试unar -e GBK,一般能救回来,但这不是长久之计,规范的产出流程应该在源头就定好编码方案。
5.5 退市股消失:数据源悄悄把退市股剔除了
现象:拉取全量清单时总数看起来正常,但和上个月的快照对比,少了十几家公司,一查都是最近进入退市整理期的股票。原因:部分数据源的免费接口默认只返回已上市且在正常交易状态的股票,退市整理期的股票被静默过滤。解决:拉取时显式声明需要包含退市状态,并给退市股单独标注delist_date字段。
如果你做的是历史回测或财务风险分析,退市股本身就是一类非常重要的样本,把它们直接丢掉等于丢弃了A股市场最极端的一批尾部数据。拉取时多带一个参数就能解决,真正要注意的是别在不知道的情况下被数据源默认行为带偏。
6. 进阶:用校验和增量更新把这包数据用稳
拿到一份基础信息包,先别急着写策略,花十分钟跑一遍完整性校验,能省下后面几天的对账时间。我习惯上按五个维度做检查:数量核对、代码段分布、唯一性约束、空值率监控、时间戳比对。数量核对是最直观的,打开文件看一眼总行数,当前A股总数在五千家上下,出现四千或六千都值得警惕。代码段分布就是把上节那张板块规则表跑一遍分组统计,科创板数量大于沪市主板基本可以断定过滤逻辑出了问题。唯一性约束是校验股票代码有没有重复,一行代码就能跑完:
# 唯一性校验:重复代码会直接破坏下游关联 assert df["code"].is_unique, "发现重复股票代码" # 空值率监控:超过阈值直接预警,不用等到入库后才发现 null_rate = df.isnull().mean().sort_values(ascending=False) print(null_rate[null_rate > 0.3])增量更新是这份数据包能否持续使用的关键设计。每周拉一次新快照,和上一份做一次全字段diff,新增记录大概率是新上市公司,消失的记录大概率是退市股,名称或行业变化的记录需要单独看是否正常。比对逻辑不复杂,用merge就能实现,重点是把diff结果输出成一份变更日志,随rar包一起交付,下游使用者一眼就能看出本周期内市场结构发生了什么变化。
数据源的接口文档会更新,代码段规则会调整,每年年底行业分类还会重新洗牌,单凭经验记规则总有覆盖不到的死角。我现在的习惯是每月手动抽查一次数据包,随机挑十家公司,去公开披露文件里人工核对两三个关键字段,这是数据质量保障的最后一道防线。
最后说一个让我自己印象深刻的教训。有一版数据包我只看数量没看字段口径,把总股本和流通股本口径当成一回事拿去和另一套数据源对账,整整折腾了两天,最后发现两边算法本身就不一致,并不是数据错误。从那以后,我把字段口径说明文件放进包内,和最新数据文件放在一起,每次交付都提醒自己先讲口径再讲数据。这个习惯希望能帮到你,让这份基础信息包从一开始就走在正确的路上。
本文还有配套的精品资源,点击获取