基础入门
本讲把重点放在 AkShare 入门阶段最常见的排错场景,围绕输入参数、返回结构、类型转换和读写闭环四层检查顺序展开。目标是让初学者在遇到空数据、列名不一致或字段转型失败时,能先缩小问题范围,再做最小修改。
本讲不再新增接口,而是回到前面几讲最容易让新手卡住的地方:空数据、字段不一致、列名找不到、类型转换失败。目标是建立一套很轻但实用的排查顺序,让你在 AkShare 返回不符合预期时,先判断问题属于哪一层,而不是一上来就怀疑整套环境坏了。
学完这一讲后,先把下面几件事做扎实:
这一步对入门者非常重要,因为后面真正决定你能不能独立往下走的,不是会不会多调一个接口,而是出问题时会不会排。
前 1 到第 6 讲已经把最常见的动作都走了一遍:安装、拿列表、拿日线、拿指数、拿日历、保存 CSV。真正的现实是,这些动作不会每次都一帆风顺。你迟早会遇到:
如果没有一套最小排错顺序,你就会不断在“重装环境”和“盲猜字段”之间打转。所以第 7 讲本质上是在帮你把前面六讲的练习路径稳住。
本讲不要求重新造新问题,直接拿前面已经写过的代码片段就可以。例如:
daily_df = ak.stock_zh_a_hist(
symbol="000001",
period="daily",
start_date="20240101",
end_date="20240215",
adjust=""
)
或者:
preview_df = build_preview(daily_df, ["日期", "开盘", "收盘", "成交量"])
排错训练最重要的不是写出多复杂的代码,而是拿一段你以后真的会重复用的代码,学会给它做最小诊断。
排错最怕同时动太多地方。下面四层顺序从最便宜的输入检查开始,一层层往下收窄。
先看的是人最容易写错的地方:
print(symbol)
print(start_date, end_date)
很多问题其实和 AkShare 无关,只是:
这一步一定要先做,因为输入层最便宜、也最常错。
拿到表后,先别急着转型,先看:
print(type(daily_df))
print(daily_df.shape)
print(daily_df.columns.tolist())
如果这里已经发现不是 DataFrame、列数是 0,或者关键列根本不在,那问题多半在“接口返回结果”和“你预期结构”之间,而不是 pandas 本身。
结构没问题以后,再看类型:
date_col = next(col for col in daily_df.columns if "日期" in str(col))
close_col = next(col for col in daily_df.columns if "收盘" in str(col))
parsed_date = pd.to_datetime(daily_df[date_col], errors="coerce")
parsed_close = pd.to_numeric(daily_df[close_col], errors="coerce")
print(parsed_date.isna().sum())
print(parsed_close.isna().sum())
如果这一步大量变成空值,说明问题不是“没返回表”,而是“这张表的字段虽然在,但内容形式和你预期不一致”。
如果问题出现在保存和回读阶段,就不要和接口问题混在一起排:
csv_df.to_csv("debug_sample.csv", index=False, encoding="utf-8-sig")
loaded_df = pd.read_csv("debug_sample.csv")
print(loaded_df.columns.tolist())
print(loaded_df.head())
这样能快速看出是文件没写出来、编码没对、还是回读后的列结构和原来发生了偏移。
本讲的成功标准,不是“所有报错都消失”,而是你已经能把问题收敛到更小范围。
assert isinstance(symbol, str)
assert len(symbol) > 0
print(daily_df.columns.tolist())
assert pd.to_numeric(daily_df[close_col], errors="coerce").notna().sum() >= 0
只要你已经开始把“调用接口”和“读写文件”当成两层问题来拆,本讲就已经发挥作用了。
新手排错最容易急着大改环境或重写逻辑,真正更稳的做法,是先把问题拆小,再决定改哪里。
这通常不是最优动作。除非你连导入包都失败,否则大多数问题更可能出在参数、列名或类型,而不是安装本身。
先打印本地列名,再最小替换。不要一不一样就大改逻辑。排错的核心是缩小差异,不是把代码全部推翻重写。
某些列有空值可能是正常业务现象,不等于接口彻底失效。你更应该看的是:关键字段是不是整体都不可用。
如果你同时改 symbol、日期、接口名、列名和保存路径,就很难知道到底是哪一步修好了问题。第 7 讲强调的就是“小步定位”。
这就已经是很有价值的排错进展。
import pandas as pd
def inspect_table(df: pd.DataFrame) -> None:
print("type:", type(df))
print("shape:", df.shape)
print("columns:", df.columns.tolist())
print(df.head())
inspect_table(daily_df)
这类函数看起来简单,但能显著降低你每次排错时的心智负担。排错不是靠灵感,而是靠反复执行一套稳定的小检查。
建议你做一个很短的作业:
shape 和 columns。如果你能做到这一点,第 7 讲就已经帮你建立了独立排错的起点。
如果你现在能说出这句话,第 7 讲就过关了:
“我遇到空数据或字段问题时,已经不会先乱改一大堆代码,而是会先按输入、结构、类型、读写四层顺序排查。”
这就是本讲真正的价值。
很多初学者在遇到问题时,会忍不住立刻开始改代码,而且一次改很多处。比如既改 symbol,又改日期,又改列名,还顺手把 to_csv()、read_csv() 的路径一起重写。表面看像是在积极处理问题,实际上是在扩大问题面。第 7 讲更推荐的做法,是先把问题缩小成一句话,再决定要不要改代码。
例如,你可以先问自己:
当问题已经被压缩到这四种之一时,你后面的修改就会小很多,也更容易验证是不是改对了。
如果你总觉得临场不知道该打印什么,可以直接保留下面这个模板:
def debug_snapshot(df):
print('shape =', df.shape)
print('columns =', df.columns.tolist())
print('dtypes =')
print(df.dtypes)
print('head =')
print(df.head(3))
debug_snapshot(daily_df)
这段代码不会替你自动修复问题,但它能非常稳定地把问题暴露到“人能直接看懂”的层面。对入门者来说,这往往比一上来追完整异常栈更有帮助,因为你先看见了结果长什么样,才更容易理解自己到底在修什么。
空数据是这一讲最常见的场景之一。真正实用的做法,不是立刻把所有参数都推倒重来,而是先核对三个最便宜的小点:
symbol 是否仍是你以为的那个代码。start_date 和 end_date 有没有写反或写得过窄。很多时候,你会发现问题并不神秘,只是日期区间太窄、代码写错一位,或者沿用了上一讲别的对象的参数。越早把这些便宜错误排掉,越不会把时间浪费在错误方向上。
新手还很容易把“没有这个字段”和“这个字段不能算”混成同一种错误。其实这两类问题差别很大:
例如:
if '收盘' not in daily_df.columns:
print('先排列名问题')
else:
close_series = pd.to_numeric(daily_df['收盘'], errors='coerce')
print('有效数值个数 =', close_series.notna().sum())
这种拆法的好处是,你不会再把“列根本不在”和“列在但值不干净”当成同一个 bug 来处理。
AkShare 入门短课到第 7 讲,真正应该交给你的,不只是更多接口,而是一种碰到问题也能继续往前的能力。因为真实学习过程中,阻碍新手继续下去的,往往不是“不会再多写一行 API 调用”,而是“一报错就完全失去判断路径”。第 7 讲的作用,就是把这种失控感降低。你不需要一下子成为排错高手,只要先学会把问题拆成输入、结构、类型、读写四层,后面很多事就会从“完全不会”变成“有路径可走”。
本讲是《AkShare数据获取入门短课》的第 7/8 讲,当前主题是《排查空数据和字段报错》。
上一讲:第 6 讲《保存行情到CSV》。
下一讲:第 8 讲《联动pandas算5日均线》。
风险揭示与免责声明
本页面内容仅用于量化研究与技术交流,旨在展示研究方法与流程,不构成对任何金融产品、证券或衍生品的要约、招揽、推荐或保证。
本文所涉历史数据、回测结果与示例参数不代表未来表现,也不应作为投资决策依据。
市场存在波动、流动性与执行偏差等不确定性,任何策略均可能出现收益波动或阶段性失效。
读者应结合自身风险承受能力进行独立判断,并在必要时咨询持牌专业机构意见。