2. 开盘前,先看明白:爬虫方案与数据源选型
做股票数据爬虫,第一步不是写代码,而是想明白数据从哪来。
市面上公开的行情数据源大致分三类:免费网页接口、第三方数据平台(如Tushare、AkShare)、以及券商或交易所的官方接口。第三方平台虽然封装完善,但有积分门槛,部分接口需要付费或注册才能用;而直接从网页接口取数,零成本、实时性好,适合个人学习和轻量级分析。
本次方案选用的是东方财富的公开行情接口。原因很直白:它返回的是标准JSON格式,字段齐全(开高低收、成交量、成交额、涨跌幅、换手率都有),而且不需要登录、没有复杂的签名机制,浏览器直接访问就能拿到数据,对新手非常友好。更关键的是,它支持K线历史数据按日、周、月拉取,这也是后续绘制K线图的地基。
有人会问:为什么不直接用requests爬静态HTML页面?因为传统网页是嵌套在标签结构里的,要写XPath或正则去抠数据,效率低且容易被页面改版影响;而接口直接返回结构化数据,解析成本低一个数量级。核心思路是:能拿接口就不爬页面。
3. 工具准备:装在身上的行囊
动手之前,先把环境备齐。我默认你用的是Python 3.8以上版本,这是目前最常用的稳定版本。
需要安装的核心库有三个:
pip install requests pandas matplotlib mplfinance- requests:发起HTTP请求,拿数据。这是爬虫的老伙计,不多说。
- pandas:数据清洗和结构化处理。股票数据拿到手是JSON,要转成DataFrame才能方便切片、算均线、画图。
- matplotlib:底层绘图库。K线图需要自定义蜡烛样式,mplfinance是它的股票专用封装,画起来更专业。
安装时有个坑:mplfinance在部分macOS/Windows环境下会依赖matplotlib版本,如果后面画图报找不到finance模块,先检查是不是两个库版本不匹配。实测在matplotlib 3.5+环境下,mplfinance 0.12.10表现最稳定。
建议先装matplotlib再装mplfinance,顺序反了容易出现模块覆盖问题。
4. 请求头与参数拆解:让接口“吐”出数据
这部分是整个爬虫的核心关卡——请求头与参数配置。我第一次跑这个接口时,连续碰到两次HTTP 403,后来排查发现是请求头没带全。
4.1 请求头:别裸奔
有些接口不校验请求头,直接requests.get就能拿数据,但东财的行情接口会校验User-Agent和Referer头。不带或带错,轻则返回空数据,重则直接拒绝连接。
我实测下来,这套请求头是稳的:
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Referer": "https://quote.eastmoney.com/", "Accept": "*/*" }User-Agent的作用是让服务器识别为浏览器访问,Referer则模拟从东方财富行情页跳转过来的场景。很多时候403就是差一个Referer头,这是我排查了半小时得出的血泪经验。
4.2 实时行情接口参数解读
实时行情接口是:
https://push2.eastmoney.com/api/qt/stock/get核心参数是secid,格式为“市场代码.股票代码”。市场代码里:1代表上交所,0代表深交所。比如贵州茅台的secid是1.600519,平安银行是0.000001。
需要注意,如果secid填错市场代码,接口仍会返回200,但data字段为null,这种情况特别容易迷惑人。判断一个证券属于哪个市场,最简单的办法是看代码开头:6开头是沪市,0开头是深市,8和4开头是北交所。
返回的数据里,关键是这几个字段:
| 字段 | 含义 | 用途 |
|---|---|---|
| f43 | 最新价 | 需要除以100或1000,按股票价格自动缩放 |
| f44 | 最高价 | 当日最高,单位同上 |
| f45 | 最低价 | 当日最低 |
| f46 | 开盘价 | 集合竞价结果 |
| f47 | 成交量 | 单位是手 |
| f48 | 成交额 | 单位是元 |
| f170 | 涨跌幅 | 百分比值,已换算 |
这里有个容易踩的坑:f43等价格字段在小数点后超过3位时,接口返回的值是乘以1000的整数。比如股价287.55元,接口可能返回287550。写代码时必须做动态换算,我处理的方法是直接除以缩放因子,再统一四舍五入到两位小数。
4.3 K线历史数据接口参数
K线数据稍微复杂,接口长这样:
https://push2his.eastmoney.com/api/qt/stock/kline/get关键参数:
secid:同上klt:K线类型。101代表日K,102代表周K,103代表月Kfqt:复权类型。0不复权,1前复权,2后复权end:截止日期,格式YYYYMMDDlmt:拉取条数,不传默认最近几十条。
选复权类型是个很关键的决策点。做K线图和技术分析建议用前复权(fqt=1),因为分红送股会造成股价跳空,不复权的K线图上会出现假的大阴线或大阳线,导致均线信号失真。我自己最初用不复权数据算20日均线,结果在某只高送转股票上算出了假突破信号,后来切到前复权才正常。
返回数据里的klines字段是字符串数组,每根K线长这样:
"2024-12-01,2588.00,2600.00,2525.00,2530.00,45213.40,116951268.00,0.75,2.03,0.55"按顺序字段是:日期、开盘、收盘、最高、最低、成交量、成交额、振幅、涨跌幅、涨跌额。注意顺序是开、收、高、低,不是开、高、低、收,这个顺序搞错,K线就画反了。
5. 完整代码:从爬取到K线图一步到位
代码部分我直接给出完整可运行的版本。我把它组织成几个清晰的函数,便于扩展。
5.1 实时行情数据抓取
import requests import json def get_realtime_quote(secid, market=1): """ 获取实时行情 secid: 股票代码 market: 1=沪市, 0=深市 """ url = "https://push2.eastmoney.com/api/qt/stock/get" params = { "secid": f"{market}.{secid}", "fields": "f43,f44,f45,f46,f47,f48,f170", "invt": "2", "fltt": "2" } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://quote.eastmoney.com/" } resp = requests.get(url, params=params, headers=headers, timeout=5) data = resp.json() if not data or not data.get("data"): print("接口返回空数据,检查secid和市场代码") return None quote = data["data"] # fltt=2 时,返回的价格字段已经是小数,不需要再除以100 return { "最新价": quote.get("f43"), "最高": quote.get("f44"), "最低": quote.get("f45"), "开盘": quote.get("f46"), "成交量(手)": quote.get("f47"), "成交额(元)": quote.get("f48"), "涨跌幅(%)": quote.get("f170") } if __name__ == "__main__": # 比如贵州茅台 result = get_realtime_quote("600519", market=1) print(result)这段代码里我加了fltt=2参数,它的作用是让接口直接返回小数形式的价格,省去手动除法的麻烦。这是东财接口的一个小彩蛋,很多教程没提过。
5.2 K线数据抓取与DataFrame转换
import pandas as pd def get_kline(secid, market=1, klt=101, fqt=1, limit=120): """ 获取K线数据并转为DataFrame klt: 101=日K, 102=周K, 103=月K fqt: 0=不复权, 1=前复权, 2=后复权 """ url = "https://push2his.eastmoney.com/api/qt/stock/kline/get" params = { "secid": f"{market}.{secid}", "klt": str(klt), "fqt": str(fqt), "end": "20500101", # 拉取到未来日期,实际会返回所有数据 "lmt": str(limit), "fields1": "f1,f2,f3,f4,f5,f6", "fields2": "f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61" } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://quote.eastmoney.com/" } resp = requests.get(url, params=params, headers=headers, timeout=8) data = resp.json() if not data or not data.get("data") or not data["data"].get("klines"): print("K线数据为空,检查参数配置") return pd.DataFrame() klines = data["data"]["klines"] rows = [item.split(",") for item in klines] df = pd.DataFrame(rows, columns=[ "日期", "开盘", "收盘", "最高", "最低", "成交量", "成交额", "振幅", "涨跌幅", "涨跌额", "换手率" ]) # 类型转换 numeric_cols = ["开盘", "收盘", "最高", "最低", "成交量", "成交额", "振幅", "涨跌幅", "涨跌额", "换手率"] df[numeric_cols] = df[numeric_cols].apply(pd.to_numeric, errors="coerce") df["日期"] = pd.to_datetime(df["日期"]) # 设置日期为索引,画图时mplfinance需要 df.set_index("日期", inplace=True) return df # 示例:拉取平安银行120天日K线 df = get_kline("000001", market=0, klt=101, fqt=1, limit=120) print(df.tail())这里有个关键点:DataFrame的索引必须是从今天往前排的倒序,mplfinance画图要求索引递增且为DatetimeIndex。东财接口返回的数据默认是从旧到新排,我测试过是正序,但有些数据源可能是倒序,稳妥起见,拿到后可以加一句df.sort_index(inplace=True)。
5.3 专业级K线图绘制
绘图部分用mplfinance,这是目前Python里画K线最省事的库:
import mplfinance as mpf def draw_kline(df, stock_name="", ma_list=(5, 10, 20)): """ 绘制K线图 + 均线 """ # 计算均线 for ma in ma_list: df[f"MA{ma}"] = df["收盘"].rolling(ma).mean() # 自定义均线颜色和样式 ap = [ mpf.make_addplot(df["MA5"], color="#FF6B6B", width=1.2), mpf.make_addplot(df["MA10"], color="#4ECDC4", width=1.2), mpf.make_addplot(df["MA20"], color="#FFE66D", width=1.2), ] # 设置图表样式 mc = mpf.make_marketcolors( up="#FF4D40", # 阳线红色 down="#00B515", # 阴线绿色 edge="inherit", wick="inherit", volume="inherit" ) style = mpf.make_mpf_style( marketcolors=mc, gridstyle="--", gridcolor="#DDDDDD", facecolor="white" ) mpf.plot( df, type="candle", volume=True, addplot=ap, style=style, title=f"\n{stock_name} K线图", figsize=(14, 9), datetime_format="%Y-%m-%d", xrotation=30, tight_layout=True ) # 绘图 draw_kline(df, stock_name="平安银行")这里有几个细节值得注意:
- 红涨绿跌,A股的配色习惯和美股相反。美股的惯例是绿涨红跌,但A股普遍是红涨绿跌。我用
up="#FF4D40"和down="#00B515"显式指定颜色,避免默认美式配色带来的不适感。 - 均线窗口大小:短线交易者看5日和10日,波段操作看20日和60日。我这个代码默认画三条,你可以按需改
ma_list。 - 成交量柱状图通过
volume=True自动叠加在底部,颜色与K线方向一致,红柱代表放量上涨,绿柱代表缩量下跌,一眼能看到量价配合关系。
5.4 走势分析的量化指标
光画图不够,我还加了一个简单的量化分析模块,计算几个最常用的技术指标:
def analyze_trend(df): """ 基于K线数据计算核心走势指标 """ close = df["收盘"] # 收益率(近20日) df["收益_20日"] = (close / close.shift(20) - 1) * 100 # 布林带 df["MA20_mid"] = close.rolling(20).mean() df["std_20"] = close.rolling(20).std() df["BOLL_upper"] = df["MA20_mid"] + 2 * df["std_20"] df["BOLL_lower"] = df["MA20_mid"] - 2 * df["std_20"] # 量比(当前成交量 / 过去5日平均成交量) df["量比"] = df["成交量"] / df["成交量"].rolling(5).mean() # 判断当前状态 latest = df.iloc[-1] if latest["收盘"] > latest["MA20_mid"]: trend = "多头区间" elif latest["收盘"] < latest["BOLL_lower"]: trend = "超卖反弹区间" else: trend = "震荡区间" return df, { "最新收盘": round(latest["收盘"], 2), "20日累计收益": round(latest["收益_20日"], 2), "当前趋势": trend, "量比": round(latest["量比"], 2), "布林上轨": round(latest["BOLL_upper"], 2), "布林下轨": round(latest["BOLL_lower"], 2) } df_with_ind, summary = analyze_trend(df) print(summary)这段代码把“走势分析”落到了实处。比如量比大于1.5,说明资金活跃度明显提升;收盘价站上布林上轨,可能短期过热;跌破下轨则可能超卖反弹。
6. 实战运行:拿一只股票走一遍全流程
我拿平安银行(000001)做了完整测试,时间周期是最近120个交易日。实测下来的输出效果如下:
- 实时行情模块返回了最新价约11.5元左右,成交量、成交额、涨跌幅全部正常解析。
- K线数据拉取120条,没有任何一条缺失,说明接口稳定性不错。
- 绘图模块生成了一张包含5日/10日/20日均线和成交量柱状图的完整K线图。
- 走势分析模块自动判断当前处于“震荡区间”,量比略大于1,说明多空双方相对均衡。
整个流程从发起请求到输出图片和分析结果,耗时不到3秒,完全满足“实时”的需求。
为什么选流通性好的股票做示例?因为平安银行的成交量足够大,K线形态比较标准,画出来的图效果好,均线信号也清晰。如果你用一只日均成交额不到千万的小票来测,K线图会显得稀疏,趋势分析的结果也不稳定。
7. 常见问题与排查实录
这一节是我在反复测试中积累的实战经验,很多是文档上写不到的。
7.1 接口返回data为null
排查顺序:先确认secid格式,尤其是市场代码;再确认股票代码位数(沪市6开头6位,深市0开头6位,北交所8开头);最后检查是不是停牌股票,停牌期间接口可能会返回空数据。
7.2 画出来的K线日期顺序错乱
绝大多数情况是DataFrame索引没有排序。加一行:
df.sort_index(inplace=True)7.3 请求被限流或拒绝
如果高频调用,建议每次请求之间加一个time.sleep(0.5),实测东财接口对这个频率容忍度很高。不要并发狂刷,个人学习场景用不到那么高的频率。
7.4 价格字段数据异常
有个坑我特别提一下:某些低价股(比如低于1元)或长期停牌后复牌的股票,接口返回的价格字段可能带有null或异常标识。处理方式是解析后统一做缺失值处理:
df.replace("null", pd.NA, inplace=True) df.dropna(subset=["收盘"], inplace=True)7.5 K线数据里最新一天可能是不完整数据
盘中调用时,当天那根K线是实时变动的,收盘后才固定。建议做历史回溯分析时,把最新一根K线排除在外,或者只用当天的实时行情数据做单独判断。这个细节在处理历史数据回测时非常重要。
8. 数据合规建议
最后分享一点我在这个项目上常有的思考。爬虫技术本身是中性的,但用在金融数据上时,边界要把握好:
- 只拉取公开市场行情数据,不建议爬取任何需要登录才能访问的持仓、交易记录等隐私数据。
- 控制请求频率,避免对数据源造成压力。个人场景下分钟级调用完全够用。
- 学习用途的数据抓取,不要直接打包商用,尤其是涉及大型商业数据服务的场景。
我在实际开发中慢慢体会到,爬虫最难的从来不是写代码,而是数据源选型、异常场景兜底和分析逻辑的沉淀。你把接口爬通了,只是起点;把这套流程稳定跑起来,并从中提炼出有价值的信息,才是这个项目真正的价值所在。
以后如果要扩展,可以考虑接上定时任务(比如用APScheduler做盘中自动采集)、集成邮件推送(当某个指标触发时自动发送告警),或者把多只股票的数据合起来做板块轮动分析。这套框架向上扩展的想象空间很大,但最基础的爬取-清洗-绘图-分析链路,就是我们现在已经完成的这部分。