☰
用akshare构建价值投资指标选股策略:从数据到代码
2026/10/4 2:39:25 网站建设 项目流程

先把话说在前头:这篇文章不是荐股,也不是教你稳赚不赔的“圣杯”。它更像是我自己把价值投资理念落成可执行代码的一份实操笔记。核心就三件事:怎么理解价值投资里那些关键指标,怎么用 akshare 这个开源财经数据库把 A 股的财务和行情数据拉下来,再怎么把这些指标翻译成一条能跑出候选股的筛选逻辑。

akshare 是个好东西,它是目前国内免费开源财经数据接口里覆盖最全的之一:A股行情、财务报表、估值指标、宏观数据、资金流向都能拿。过去做“指标选股”这件事,很多人卡在数据这一步——要么用现成的炒股软件导出,量一大就卡死;要么买商业数据库,一年大几千。用 akshare 的话,一台普通电脑、一个 Python 环境,全市场几千只股票的估值和财务数据可以批量拉下来做量化初筛。这篇文章会把从安装到选股策略落地的完整链路走一遍,适合刚开始接触量化选股、又不想被复杂金融工程劝退的朋友参考。

1. 价值投资和指标选股,到底在选什么

1.1 价值投资的底层逻辑

价值投资的祖师爷格雷厄姆有句话概括得很精准:买股票就是买公司的一部分,而市场短期是投票机,长期是称重机。放到今天,这句话可以拆成两层含义:第一层,你买的不是一个跳动的价格数字,而是企业未来创造现金流的能力;第二层,价格围绕价值波动,当价格明显低于内在价值时,就是好买点。

但“内在价值”是个动态的、模糊的东西。实际执行的时候,我们只能通过一些可以量化的指标去逼近它。这也就是“价值投资/指标选股”最核心的思路:用公开的财务数据,构建一个价值评估框架,然后从全市场股票里筛出符合框架的“便宜的好公司”。

为什么有人愿意花时间做这件事,而不是直接听消息或者追热点?原因很简单:情绪驱动的短线交易,普通投资者在信息、速度、纪律上都拼不过专业机构;但基于财务指标的长期筛选,比拼的是耐心和基本面判断力,这条路子反而是散户能做深做专的。

1.2 指标选股的系统化优势

手工选股最大的问题是“盯不过来”。A股已经五千多家上市公司,如果你想一条条看财报,一个人一个月也看不完。而用程序做指标选股,可以把选股逻辑固化成规则,让计算机全市场扫描,几秒钟内就给你一个符合条件的小池子,然后你只需要把精力花在这个小池子上做深度分析。

再说一个容易被忽略的层面:指标选股能帮人克服“屁股决定脑袋”。人只要对某只股票有了好感,就会下意识找一堆理由说服自己。但规则是死的,它不关心你手里有没有这只股票,不符合条件就是剔除,这样反而能对抗很多非理性持仓。这也是我后来越来越推崇“先有规则、后有交易”的原因——不是靠盘感,而是靠一套可以回测、可以验证的标准化流程。

2. 环境准备与 akshare 安装避坑

2.1 安装 akshare 的两种方式

我用 akshare 的版本已经迭代到很新的版本了,官方要求 Python 3.8 以上,但我个人建议直接用 Python 3.10 或 3.11,因为新版本对 pandas、numpy 的依赖更友好,不容易出现依赖冲突。安装方式没什么花活:

pip install akshare -U

如果是在国内网络环境下,下载速度感人,建议直接用清华镜像源:

pip install akshare -U -i https://pypi.tuna.tsinghua.edu.cn/simple

装完之后建议顺手装三个配套库:pandas、numpy、matplotlib,前两个做数据处理,第三个用来画个简单的估值分布图。装完验证一下:

import akshare as ak print(ak.__version__)

能正常输出版本号,说明安装成功。如果这一步就报错,那大概率是依赖冲突,最省事的办法是换一个干净的虚拟环境重装。

2.2 安装过程中的高频报错

我在群里见过太多新人卡在安装这一步,我把几个典型问题列一下:

  • pip 升级报错:先pip install --upgrade pip,再装 akshare。
  • pandas 版本冲突:akshare 对 pandas 版本有要求,会出现PandasError之类的提示,按报错里的要求升降级 pandas 就能解决。
  • 缺少 lxml / beautifulsoup4:akshare 底层大量用到了网页解析,缺少解析库就补装:pip install lxml beautifulsoup4 html5lib jsonpath requests。
  • 提示“No module named ‘akshare’”,但是明明装过了:这种情况多半是 Python 环境搞混了。命令行里输入which python,确认 pip 和 Python 是同一个环境。

还有一个小建议:akshare 更新频率挺高的,接口字段偶尔会变。如果某段代码今天能跑、过两个月报错,先试试升级 akshare 版本,很多时候不是你的代码写错了,是接口数据源上游调整了。

3. 核心指标怎么选、为什么是它们

3.1 估值类指标:PE、PB、股息率

做价值投资这么多年,我自己的体感是估值类指标比技术指标靠谱得多,因为技术指标反映的是市场情绪,估值类指标反映的是“你花多少钱买到公司多少盈利和资产”。

市盈率(PE)是最常被挂在嘴边的估值指标,含义是“你为公司每 1 元利润支付多少对价”。PE 越低,理论上投资的回收期越短。但 PE 有一个很大的坑:强周期行业(比如煤炭、钢铁、有色)在盈利峰值期 PE 反而是最低的,这时候进去容易买在高点。所以用完 PE 之后,一定要叠加 PB 或者盈利稳定性指标过滤。

市净率(PB)衡量的是“你为公司的净资产支付了多少溢价”。PB 低于 1 意味着你在用低于净资产的价格买入,虽然这不代表绝对安全,但至少在资产端给了你一层缓冲。

股息率则是价值投资里一个偏防御的指标:它代表你光凭分红就能拿到多少回报。A股里常年有高股息率蓝筹股,比如银行、公用事业、高速公路这类现金流稳定的行业,股息率高往往说明公司盈利没有被大比例挪用,治理也相对规范。

3.2 盈利质量类指标:ROE 和毛利率

如果说 PE 和 PB 回答的是“贵不贵”,ROE(净资产收益率)回答的就是“赚不赚钱”。它衡量的是公司用股东投入的每一元净资产,能够创造多少净利润。长期保持 15% 以上 ROE 的公司,不需要看财报细节,基本就能判断它具备短期无法模仿的商业模式或品牌护城河。

毛利率则用来判断护城河的深度。毛利率高的企业,说明它有定价权,能够把成本压力转嫁给下游。比如很多医药、白酒企业毛利率能做到 80% 以上,这种生意本身就自带“不容易被打价格战”的属性。

我个人筛选时还有一个习惯:看“净利润现金含量”,也就是经营性现金流净额占净利润的比例。如果一家企业账面利润很高,但经营性现金流常年为负,那利润很可能只是纸面富贵。不过在批量选股阶段,先用 ROE 和毛利率粗筛,把这块放到个股复核阶段再细看,会更务实。

3.3 akshare 里的对应数据接口

在 akshare 里,这些指标分布在两个地方:实时行情快照和财务指标接口。

实时行情接口用stock_zh_a_spot_em(),它一次性返回全 A 股的最新价、涨跌幅、换手率、动态市盈率、市净率、总市值等字段。这个接口非常快,是“先粗筛”的第一步。

财务指标接口稍微复杂一点,我用得比较多的是stock_financial_analysis_indicator(symbol="600519", start_year="2022"),它返回某一只股票历年的净资产收益率、销售毛利率、销售净利率、资产负债率等关键财务数据。这个接口适合对粗筛后的候选股做二次精筛,因为要逐只调接口,速度比行情快照慢很多,不适合直接对全市场几千只股票循环。

4. 完整选股策略的实现流程

4.1 第一步:用实时行情做低估值初筛

我的策略框架是“宽进严出”:先用估值和流动性指标把几千只股票快速砍到几十只,再逐一拉财务数据做精筛。这个顺序完全是为了效率。

初筛条件我会这么设:

  • 动态 PE 大于 0 且小于 15
  • PB 大于 0 且小于 2
  • 总市值大于 100 亿元
  • 剔除 ST、退市和名称里带“退”的股票
  • 换手率大于 0.5%,保证一定的流动性,不至于出现买得进卖不出的问题

代码实现并不复杂:

import akshare as ak import pandas as pd df = ak.stock_zh_a_spot_em() df = df.rename(columns={ '市盈率-动态': 'PE', '市净率': 'PB', '总市值': 'mv', '换手率': 'turnover' }) df = df[~df['名称'].str.contains('ST|退')] cond = ( (df['PE'] > 0) & (df['PE'] <= 15) & (df['PB'] > 0) & (df['PB'] <= 2) & (df['mv'] > 100e8) & (df['turnover'] > 0.5) ) candidate = df[cond].sort_values('PE') print(candidate.shape[0]) candidate[['代码', '名称', 'PE', 'PB', 'mv', 'turnover']].head(20)

为什么要把市值卡在 100 亿以上?因为价值投资逻辑在中小盘上容易失效:小公司治理不规范、财务数据稳定性差,很容易出现“看着便宜,实际上是价值陷阱”。卡一个大市值门槛,能过滤掉相当一部分财务粉饰风险。

4.2 第二步:拉财务指标做二次精筛

用初筛拿到一个几十只股票的池子之后,接着循环拉取财务指标。这里我会重点关注 ROE 和毛利率,辅以资产负债率做排雷。

import time result = [] for code in candidate['代码'].head(30): try: fin = ak.stock_financial_analysis_indicator(symbol=code, start_year="2022") fin = fin.sort_values('日期') latest = fin.iloc[-1] result.append({ '代码': code, '最新_净资产收益率': latest['净资产收益率(%)'], '最新_销售毛利率': latest['销售毛利率(%)'], '最新_资产负债率': latest['资产负债率(%)'], }) except Exception as e: print(f"{code} 处理失败: {e}") time.sleep(0.5) fin_df = pd.DataFrame(result) final = candidate.merge(fin_df, on='代码', how='inner') final = final[ (final['最新_净资产收益率'] > 15) & (final['最新_销售毛利率'] > 30) & (final['最新_资产负债率'] < 60) ].sort_values('最新_净资产收益率', ascending=False)

这里有两个细节提醒一下。第一,股价可能有前导零,比如平安银行的代码是“000001”,直接存成数字会把前导零丢掉。所以我一般拿到代码后都补一下:df['代码'] = df['代码'].astype(str).str.zfill(6)。第二,财务接口如果某只股票多次请求失败,一定不要让整个循环崩掉,用 try-except 包住,配合 sleep 做请求间隔,实测下来稳定性会好很多。

4.3 第三步:给候选股打分排序

纯筛选的问题在于“好公司”和“便宜价格”之间的平衡没法体现。我习惯再做一个简单的打分模型,综合估值和盈利质量给候选股排个序。逻辑可以很朴素:每项指标按百分位打分,总得分 = 估值分 + 盈利分。

final['PE_score'] = 100 * (final['PE'].max() - final['PE']) / (final['PE'].max() - final['PE'].min()) final['ROE_score'] = 100 * (final['最新_净资产收益率'] - final['最新_净资产收益率'].min()) / (final['最新_净资产收益率'].max() - final['最新_净资产收益率'].min()) final['total_score'] = final['PE_score'] * 0.5 + final['ROE_score'] * 0.5 final = final.sort_values('total_score', ascending=False) print(final[['代码', '名称', 'PE', 'PB', '最新_净资产收益率', 'total_score']].head(10))

这个打分模型确实很粗糙,但它能把你从“凭感觉选股”变成“按规则排序”。之后你可以在这个基础上继续调权重,比如更看重股息率就把股息率加进打分函数;更看重质量就把 ROE 的权重调高。

5. 常见问题与排查技巧实录

5.1 akshare 数据源接口报错

用 akshare 时间长了,你一定会遇到“某某接口运行时报错”。我的经验是:先看一眼报错信息里有没有data、urlopen之类的字样。如果有,大概率是上游网页改版或网络波动,这个没法从根本上避免,只能定期升级 akshare,或者在代码里加个重试机制。

我自己写过一个简单的重试装饰器,跑数据的时候好用:

import time from functools import wraps def retry(max_retries=3, delay=2): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for i in range(max_retries): try: return func(*args, **kwargs) except Exception as e: print(f"第{i+1}次尝试失败: {e}") time.sleep(delay) raise Exception("多次重试仍失败") return wrapper return decorator

5.2 数据对齐和字段类型问题

另一个高频坑是“明明数据拉下来了,合并之后全是 NaN”。大多数情况都是数据对齐出了问题:行情表的代码是字符串“000001”,财务接口返回的代码可能是数字 1,或者带交易所后缀。合并之前,务必统一列的数据类型,用astype(str)处理一下,把代码补成 6 位。

还有一个细节是财务指标的日期列。stock_financial_analysis_indicator返回的是报告期日期,按字符串排序可能排不准,所以排序前最好转成pd.to_datetime()。

5.3 别把“低估值”等同“能买”

指标选股最大的风险是买到“价值陷阱”:PE 低、PB 低,但经营持续恶化,越跌越便宜,越便宜越看似有价值。我踩过最深的坑就是为了追求低 PE 选了某个行业里的夕阳公司,最后账面亏损远超分红。

后来我给自己定了几条硬规矩,也写进了筛选项:一是必须挑连续三年 ROE 都超过 12% 的股票,只看单一年份数据容易被业绩暴增的周期股骗进去;二是剔除有息负债率太高的公司,企业负债结构如果极端依赖银行借款,市场一抽贷现金流就完了;三是筛选结果只是候选池,不作为买入信号,真正要下手前还得打开财报逐项核一遍,尤其要看经营性现金流和应收账款变化。

6. 关于这套流程的一些实际操作心得

说点题外话吧。我从开始写这套“价值投资/指标选股(akshare)”流程到现在,最大的感触是:代码本身不难,难的是你愿意相信一套纪律,并且能坚持执行。akshare 让你过去需要花钱买、花时间找的数据变得随手可拿,但数据只是原料,加工数据的思想才是核心。

最开始跑出来的候选股,我一只都不敢买;后面跑多了,反而学会了对每周生成的列表“涨跌不惊”。我不太关注每天跑出来的结果里有什么“神票”,我关注的是这套流程是不是被严格执行了:有没有过滤掉可疑的财务指标,有没有坚持用估值和盈利质量双重标准去卡。如果你也想试,我建议你先拿近一年已经走过的行情回放一遍,看看你的筛选规则能不能有效避雷,再决定要不要让它指导下一笔投资。

最后再分享一个小技巧:akshare 的数据源是公开接口,拉数据的时候要照顾一下服务器,别在短时间内高频并发请求,不然容易被限流。批量跑的时候加个 sleep,安全和稳妥比快更重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询