Python爬虫实战:抓取股票行情数据并绘制专业K线图

发布时间:2026/10/13 15:08:33
Python爬虫实战:抓取股票行情数据并绘制专业K线图 不知道你有没有遇到过这种尴尬打开行情软件想看一只股票的历史走势结果界面上一堆广告和弹窗几分钟不操作就自动刷新想导出某段时间的K线数据更是被限制好不容易手动截图保存下来想做二次分析又得重新敲一遍数据。其实用Python爬虫把这套事情自动化是很多做量化研究、写复盘笔记的人早就习以为常的操作。这篇博文就从一个完整的实战角度出发手把手带你抓取股票实时行情数据、拼接日K线序列、用专业库绘制K线图再做趋势、动量、超买超卖三个维度的深度解读全程附源码跑通之后你可以直接替换股票代码用于自己的研究。项目本身不算复杂但里面涉及的技术细节很密集HTTP接口字段解析、GBK编码处理、JSON清洗、K线复权选择、绘图面板布局、指标信号计算以及频率控制和异常重试这些爬虫里躲不开的坑。我尽量把每一步的为什么也讲清楚而不是只丢一段能跑的代码。内容适合已经有Python基础、想接触金融数据场景的开发者也适合刚入门爬虫但不太清楚怎么把数据变成有价值信息的朋友。读完你应该能自己实现一个“输入股票代码输出带分析结论的K线图”的小工具。1. 为什么放着现成行情软件不用偏要自己抓数据画图1.1 现成工具的短板在哪里大多数人看股票第一反应是打开行情软件。这东西日常看一眼当然没问题但一旦你想把行情数据当成生产资料来用它几乎处处跟你作对。比如说我吧以前写复盘笔记经常要把近一个月的收盘价、成交量整理成表格软件里确实能复制但格式一塌糊涂日期对齐要手动改半天数据量一大就特别容易出错。更麻烦的是历史数据。行情软件通常只免费提供最近两三年的日K线更早的数据要付费或者申请专业版。对做技术分析的人来说K线样本不够长很多均线策略和形态规律根本没法验证。还有一个我不太能忍的点大部分软件自带的画图功能指标的参数是写死的均线只能选固定的几条MACD、RSI这些想改参数或者叠加自定义信号完全没有办法。1.2 自己动手能做成什么样把需求拆开之后你会发现所谓“自己做一套行情分析工具”本质上就是三件事。第一实时行情采集。定期请求一次公开的HTTP接口拿到当前价、涨跌额、涨跌幅、成交量这些字段拼成一张结构化的表格想存多久就存多久。第二K线序列的获取与管理。把一段连续交易日的数据拉下来按照日期、开盘、最高、最低、收盘、成交量的标准结构整理成DataFrame这就是后续所有分析和绘图的数据基础。第三可视化与分析。用绘图库把K线画出来叠加上均线、成交量、MACD、RSI等指标同时用程序计算买卖信号或综合评分把“看图”这件事变成可重复、可批量执行的任务。这套体系搭好之后你可以每天自动生成几十只自选股的图表和分析报告也可以把历史数据拉长到几百个交易日做回测。代码功底好一点的话再挂上定时任务每天早上开盘前自动出一份简报。项目带来的延展性是任何图形界面软件都给不了的。2. 数据源选型免费公开的HTTP行情接口其实比想象中好用2.1 数据来源方案对比做股票数据爬虫第一步卡在数据源上。我前前后后试过几类方案说下它们各自的使用成本和坑。一类是券商或交易软件的专用接口。这类接口数据最干净、速度也快但通常需要实盘账户认证、白名单控制而且在本地非交易时段经常连不上。自己学技术、做研究值当不上。另一类是现成的开源金融数据包。它们的优势是封装好了一行代码就能取数据历史数据也比较全。但缺点是很多老牌的接口已经收紧或者需要积分换权限新用户上来会有点挫败感。而且依赖这类包越多你离底层的数据格式就越远出了问题时排查起来挺难受。我最常用的其实是第三类门户网站公开的HTTP行情接口。它们本来是为了支撑网站页面动态展示行情而存在的返回结构简单通常不需要鉴权甚至直接用浏览器访问也能看到内容。虽然这类接口没有官方文档字段含义得自己踩出来也没有人承诺长期稳定但对个人学习研究来说性价比非常高。毕竟我们要的不是生产级的量化终端而是能跑通逻辑、能理解原理的实验环境。2.2 实时行情接口与K线接口形态这次项目我选了两个数据接口一个负责实时行情一个负责历史日K线。实时行情接口长这样https://qt.gtimg.cn/qsh600000,sz000001把多个股票代码用英文逗号拼接在URL后面一次性就能拿回多只股票的数据。注意这里的代码格式沪市是sh开头深市是sz开头后面跟上六位数字。直接访问会发现返回的不是JSON而是一段类似JS变量的文本而且编码是GBK如果不处理中文会直接乱码。日K线接口长这样https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?paramsh600000,day,,,320,qfqparam里有几个关键参数股票代码、K线周期day代表日线、空参数表示起始日期、320表示抓取最近320个交易日、qfq表示前复权。返回的是标准JSON格式解析起来非常舒服。2.3 字段含义与复权方式必须先搞清楚实时行情接口返回的文本是以~作为分隔符的格式大致长这样v_sh6000001~浦发银行~600000~当前价~昨收~今开~成交量手~外盘~内盘~买一价~...;字段位置比较固定我实际代码里取了这些索引3是当前价索引4是昨收索引5是今开索引6是成交量单位手索引30左右是时间后面还有最高价、最低价、涨跌、涨跌幅等。这个字段顺序没有官方文档接口升级后可能会微调所以写解析代码时最好多打几行日志确认。日K线接口返回的JSON结构里data节点下以股票代码为键code字段的值可能是qfqday前复权或day不复权取决于你URL里的复权参数。每个数组元素是平铺列表顺序是日期、开盘、收盘、最高、最低、成交量。复权这件事特别提醒一下。很多新手直接拉不复权数据做技术分析结果看到的价格是断的均线在分红除权当天出现莫名其妙的大坑或者跳空。做历史走势研究我用的是前复权也就是以当前价格为基准把历史价格做向后调整。这样K线形态连续技术指标不会被除权跳空干扰。当天数据用前复权和不复权算出来是一致的只是历史段会不同。3. 实时行情采集模块请求、编码、字段解析、异常兜底3.1 请求头与连接复用实时行情数据虽然访问速度快但写爬虫必须懂一个道理带着基本的请求头访问伪装成浏览器请求能减少很多莫名其妙的拦截。这里惯例带上User-Agent和Referer。import requests 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://gu.qq.com/, } session requests.Session() session.headers.update(HEADERS)用requests.Session()而不是裸的requests.get()可以在多次请求之间复用TCP连接避免每次都重新握手。抓多个股票时速度会明显提升也算是对服务器的一种礼貌因为连接建立次数少了。3.2 实时行情解析函数接口返回的内容是分号分隔的语句块每个股票对应一条。我的解析思路是先把文本按分号切段找到v_sh或v_sz开头的那部分然后对~切字段。def parse_realtime_line(line: str) - dict: 解析单只股票的实时行情文本 parts line.split(, 1)[1].strip().strip() fields parts.split(~) return { code: fields[2], name: fields[1], price: float(fields[3]), prev_close: float(fields[4]), open: float(fields[5]), volume_hand: float(fields[6]), high: float(fields[33]), low: float(fields[34]), change: float(fields[31]), change_pct: float(fields[32]), }需要说明字段索引以实际返回为准不同接口版本会有偏移。我第一次写的时候就是直接凭记忆取数结果最高价和最低价对调了图出来非常诡异。所以解析后顺手打印或者跟行情软件里的数字对一下很有必要。3.3 批量获取与频率控制批量获取时我建议不要一次性塞上百只代码去请求。接口虽快但单次请求塞太多代码响应体太长解析一个出错会影响全部。我控制在每个请求十个以内。import time def fetch_realtime(codes: list[str]) - pd.DataFrame: rows [] for i in range(0, len(codes), 10): batch codes[i:i 10] url https://qt.gtimg.cn/q ,.join(batch) resp session.get(url, timeout5) resp.encoding gbk text resp.text for line in text.strip().split(;): if not line.startswith(v_): continue try: rows.append(parse_realtime_line(line)) except (IndexError, ValueError) as exc: print(f解析失败: {line[:50]} - {exc}) time.sleep(0.3) # 控制请求频率别把接口当数据库打 return pd.DataFrame(rows) if __name__ __main__: df fetch_realtime([sh600000, sz000001]) print(df[[code, name, price, change_pct, volume_hand]])每批之间sleep(0.3)是我个人的习惯。实时数据讲究时效性但又没急到需要毫秒级刷新。对免费接口保持低频请求对大家都好。3.4 异常处理与超时重试网络请求不可能永远顺利。我原来写代码不爱管异常直到有一天脚本凌晨爬起来跑数据碰上DNS临时抽风整个流程直接崩了日志也没记清楚。现在我的原则是基本网络异常必须捕获并且做一次简单重试。超时设置5秒连续两次失败就放弃该批次把错误信息写到日志里而不是直接让进程退出。def fetch_realtime_with_retry(codes: list[str], retries: int 2) - pd.DataFrame: for attempt in range(retries): try: return fetch_realtime(codes) except (requests.Timeout, requests.ConnectionError) as exc: print(f第 {attempt 1} 次请求失败: {exc}) time.sleep(1) return pd.DataFrame()如果不想让数据采集过程被单只股票拖垮也可以改成逐只失败剔除。这在数据质量要求比较高的场景里很有用哪只失败就记录哪只下次集会重试而不是整体返工。4. K线历史数据获取从JSON到规整的DataFrame4.1 日K线接口的请求参数设计历史K线数据的口径很多参数里的qfq和day组合起来就决定了你拿到的是前复权日线。我把这个封装成函数把股票代码和K线数量暴露成参数这样想回测长周期数据时不用改代码。def fetch_kline(code: str, days: int 320, fq: str qfq) - pd.DataFrame: param f{code},day,,,{days},{fq} url https://web.ifzq.gtimg.cn/appstock/app/fqkline/get resp session.get(url, params{param: param}, timeout8) data resp.json() stocks data.get(data, {}).get(code, {}) key fq day if fq day in stocks else day raw stocks.get(key, []) ...为什么加入if fq day in stocks else day这个判断因为如果股票很长时间没有分红除权接口可能不返回前复权的专门字段只有原始日线字段。代码必须兼容这两种情况否则拿到个空列表就一脸懵。4.2 数据清洗与列重命名接口返回的每个K线元素是六元组[date, open, close, high, low, volume]。我习惯把它直接转成DataFrame然后统一列名和类型。import pandas as pd def fetch_kline(code: str, days: int 320, fq: str qfq) - pd.DataFrame: param f{code},day,,,{days},{fq} url https://web.ifzq.gtimg.cn/appstock/app/fqkline/get resp session.get(url, params{param: param}, timeout8) resp.encoding utf-8 payload resp.json() stocks payload.get(data, {}).get(code, {}) key fq day if fq day in stocks else day raw stocks.get(key, []) df pd.DataFrame(raw, columns[date, open, close, high, low, volume]) df[date] pd.to_datetime(df[date]) for col in [open, close, high, low]: df[col] df[col].astype(float) df[volume] df[volume].astype(float) df df.set_index(date).sort_index() return dfset_index(date)是给后面的绘图库做铺垫因为行情绘图库通常要求时间类型作为索引。sort_index()保证日期从小到大排列别小看这步接口偶尔会返回错序数据不排序你的均线计算全是错的。4.3 为什么默认拉320个交易日我默认拉320个交易日。换算一下差不多是一年半因为一年大概250个交易日。为什么要这么久因为后面要算120日和60日均线还要给这些均线留出前期数据来稳定120日周期如果没有足够多的历史样本前面一段均线就显示不出来K线图就不完整。如果你画图时发现均线少了一半通常就是数据量拉少了。有些人会直接拉1000多天甚至更久也没问题但要考虑接口响应速度。K线数据经常是全量返回数据量一大JSON解析就会慢一点。对我这个项目来说320天是画图和测试指标都够用的平衡点。5. 专业K线图绘制mplfinance三板斧做出高级感5.1 基础蜡烛图五秒钟跑通绘图环节我用的是行情专用库它封装了K线、均线、成交量这些功能比直接用matplotlib画一大堆矩形简洁得多。第一步先把拉到的数据丢进去import mplfinance as mpf df fetch_kline(sh600000, days120) mpf.plot( df, typecandle, stylecharles, volumeTrue, mav(5, 10, 20), titleK Line Demo, figsize(14, 8), )这就是一套很标准的日K线图了包含蜡烛主体、5/10/20日均线、底部成交量。stylecharles是我常用的一套配色深色背景配红绿蜡烛视觉上不刺眼。你还可以换成binance、yahoo等自带的风格或者自定义一套和你的分析报告一致的配色。5.2 指标子图MACD和RSI叠加进同一张图专业行情软件的K线图下面往往还有一两个辅助指标窗口。mplfinance里这靠panel_ratios和addplot来实现。我先把MACD和RSI计算好然后添加两个面板。def calc_macd(close: pd.Series, fast12, slow26, signal9): ema_fast close.ewm(spanfast, adjustFalse).mean() ema_slow close.ewm(spanslow, adjustFalse).mean() dif ema_fast - ema_slow dea dif.ewm(spansignal, adjustFalse).mean() hist (dif - dea) * 2 return dif, dea, hist def calc_rsi(close: pd.Series, period: int 14): delta close.diff() gain delta.where(delta 0, 0.0) loss -delta.where(delta 0, 0.0) avg_gain gain.ewm(alpha1 / period, adjustFalse).mean() avg_loss loss.ewm(alpha1 / period, adjustFalse).mean() rs avg_gain / avg_loss return 100 - 100 / (1 rs)然后通过mpf.make_addplot把指标序列注册成曲线或柱状图再一起交给plotdif, dea, hist calc_macd(df[close]) rsi calc_rsi(df[close]) ap_hist mpf.make_addplot(hist, typebar, panel2, color#888888, alpha0.6) ap_dif mpf.make_addplot(dif, panel2, color#e74c3c) ap_dea mpf.make_addplot(dea, panel2, color#2980b9) ap_rsi mpf.make_addplot(rsi, panel3, color#8e44ad, ylim(0, 100)) mpf.plot( df, typecandle, stylecharles, volumeTrue, mav(5, 10, 20, 60), addplot[ap_hist, ap_dif, ap_dea, ap_rsi], panel_ratios(3, 1, 1.5, 1), figsize(14, 12), titleStock Analysis, )panel_ratios控制四个面板的高度比例主图占3份成交量占1份MACD占1.5份RSI占1份。别让MACD和RSI挤在一起看起来会很局促。ylim(0, 100)把RSI轴固定住这样超买超卖线80和20才有参考意义。5.3 中文字体问题用mplfinance画图标题和自定义文字一旦用到中文大概率出现豆腐块一样的小方框。这是matplotlib默认字体里没有中文字体映射导致的。解决方案很直接在脚本开头设置matplotlib的字体家族。import matplotlib matplotlib.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, PingFang SC] matplotlib.rcParams[axes.unicode_minus] False不同操作系统预装的中文字体不一样Windows可以用Microsoft YaHeimacOS可以用PingFang SCLinux建议安装一个文泉驿微米黑之类的字体。干脆把所有候选都写进列表让matplotlib按顺序去找就省事很多。5.4 批量保存图表分析工具化之后通常不会满足于只画一只股票。批量保存结果图是刚需。我会把它封装成一个save_kline_chart函数输入代码列表循环生成文件文件名带上股票代码和日期方便日后归档。def plot_and_save(df, out_path, title): mpf.plot( df, typecandle, stylecharles, volumeTrue, mav(5, 10, 20, 60), addplot[ap_hist, ap_dif, ap_dea, ap_rsi], panel_ratios(3, 1, 1.5, 1), titletitle, figsize(14, 12), savefigdict(fnameout_path, dpi120, bbox_inchestight), )savfig里的bbox_inchestight特别关键。不加的话图片四周会留一圈莫名其妙的白边放到报告里很丑。dpi120是考虑到图片放大到这个分辨率下线条还算锐利又不至于像300dpi那样动辄几MB本地归档比较友好。6. 走势深度分析把指标变成明确的观察结论6.1 趋势判断均线排列状态光看图不够程序还要具备把图“翻译”成结论的能力。我做的第一层分析是趋势结构核心是看5日、20日、60日均线之间的关系。多头排列5日均线在20日均线上方20日均线在60日均线上方说明短期均线高于中期均线中期高于长期市场处于强势状态。空头排列5日均线在20日均线下方20日均线在60日均线下方说明走势偏弱。缠绕状态均线相互交叉、方向上下波动说明处于震荡期趋势类信号要打折看。代码里就是直接比较最后一根K线的均线值def analyze_trend(df): ma5 df[close].rolling(5).mean() ma20 df[close].rolling(20).mean() ma60 df[close].rolling(60).mean() last df.index[-1] if ma5[last] ma20[last] ma60[last]: return 多头排列 if ma5[last] ma20[last] ma60[last]: return 空头排列 return 均线缠绕这个分类不是预测而是帮我们识别当前市场处于什么样的技术结构里。不同结构下后续指标的判断权重要区别对待比如震荡环境中MACD金叉的可靠性就比单边上涨环境里低很多。6.2 动量判断MACD金叉死叉与柱状体背驰MACD的本质是两条指数移动平均线的差离反应的是短期和长期动量的相对方向。金叉就是快线DIF上穿慢线DEA死叉就是下穿。但实际操作里在均线空头排列下的第一次金叉往往是反弹不是反转所以在输出结论时我把趋势条件和MACD信号做了组合判断。def analyze_macd(df): dif, dea, hist calc_macd(df[close]) prev_cross dif.iloc[-2] dea.iloc[-2] curr_cross dif.iloc[-1] dea.iloc[-1] if curr_cross and not prev_cross: return MACD金叉 if not curr_cross and prev_cross: return MACD死叉 return MACD无明显交叉背驰判断稍微高级一点价格创新高但MACD柱状体高点没有创新高往往说明上涨动能在衰竭。这种背离没法写成一个简单的布尔判断因为它需要一个局部窗口比较我通常在分析函数里取最近60根K线找出价格高点和MACD柱状体高点做对比。初学者不用急着把这个做复杂先把金叉死叉输出正确再逐步加背驰检测。6.3 超买超卖RSI区间与拐点警觉RSI我用14日作为默认周期。它衡量的是最近一段时间内涨跌幅度的相对关系数值被限制在0到100之间。传统经验是80以上超买区上涨动能可能透支。20以下超卖区下跌动能可能枯竭。50附近多空强度基本均衡。这个指标的解读必须结合趋势。单边强势行情里RSI可以长时间待在80以上不代表马上会跌只能说明短期强度高。反过来说在空头排列里RSI反弹到60附近再掉头可能反而是继续走弱的信号。代码实现非常简单def analyze_rsi(df, period14): rsi calc_rsi(df[close], period).iloc[-1] if rsi 80: return fRSI{rsi:.1f}超买 if rsi 20: return fRSI{rsi:.1f}超卖 return fRSI{rsi:.1f}中性6.4 综合观察结论一个简单的打分模型分析到最后我要的不是三个孤立指标而是一个能辅助我快速定位股票状态的综合判断。这里给一个极易扩展的评分思路把趋势、动量、超买超卖、量能四块分别打分。趋势多头排列加2分空头排列减2分缠绕0分。动量MACD金叉加1分死叉减1分。超买超卖RSI超买减1分超卖加1分偏向反弹机会。成交量最近5日成交量均值大于之前20日均值加1分代表有资金关注。满分为5分负分则偏弱。这个模型非常简单肯定不能照搬去做真实交易决策但作为观察工具非常合适因为它强迫你把每一块信息都量化下来而不是被某一根大阳线带偏整体判断。这部分分析的最终输出我会直接打印成一行总结。后续你完全可以按自己的策略调整权重把这部分扩展成一个十几行的函数输入DataFrame输出结论描述和K线图一起归档。7. 完整项目整合一个文件跑通全部流程7.1 项目文件结构与依赖我建议把脚本组织成下面这种轻量结构虽说是单文件能跑但把爬取、绘图、分析拆成函数后续维护省力得多。stock_analyzer/ main.py # 主流程 requirements.txt # 依赖清单requirements.txt里只需要四个核心库requests pandas matplotlib mplfinance7.2 主流程源码下面给一个完整主程序把前面所有模块串起来。股票代码可以换默认我放了两个常见代码做演示。import time import pandas as pd import requests import mplfinance as mpf 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://gu.qq.com/, } session requests.Session() session.headers.update(HEADERS) def fetch_kline(code: str, days: int 320, fq: str qfq) - pd.DataFrame: param f{code},day,,,{days},{fq} url https://web.ifzq.gtimg.cn/appstock/app/fqkline/get resp session.get(url, params{param: param}, timeout8) payload resp.json() stocks payload.get(data, {}).get(code, {}) key fq day if fq day in stocks else day raw stocks.get(key, []) df pd.DataFrame(raw, columns[date, open, close, high, low, volume]) df[date] pd.to_datetime(df[date]) for col in [open, close, high, low, volume]: df[col] pd.to_numeric(df[col], errorscoerce) return df.set_index(date).sort_index() def calc_macd(close: pd.Series, fast12, slow26, signal9): ema_fast close.ewm(spanfast, adjustFalse).mean() ema_slow close.ewm(spanslow, adjustFalse).mean() dif ema_fast - ema_slow dea dif.ewm(spansignal, adjustFalse).mean() hist (dif - dea) * 2 return dif, dea, hist def calc_rsi(close: pd.Series, period: int 14): delta close.diff() gain delta.where(delta 0, 0.0) loss -delta.where(delta 0, 0.0) avg_gain gain.ewm(alpha1 / period, adjustFalse).mean() avg_loss loss.ewm(alpha1 / period, adjustFalse).mean() rs avg_gain / avg_loss return 100 - 100 / (1 rs) def analyze_stock(code: str, days: int 320): df fetch_kline(code, daysdays) if df.empty: print(f{code}: 没有拿到K线数据) return ma5 df[close].rolling(5).mean() ma20 df[close].rolling(20).mean() ma60 df[close].rolling(60).mean() if ma5.iloc[-1] ma20.iloc[-1] ma60.iloc[-1]: trend 多头排列 elif ma5.iloc[-1] ma20.iloc[-1] ma60.iloc[-1]: trend 空头排列 else: trend 均线缠绕 dif, dea, hist calc_macd(df[close]) if dif.iloc[-2] dea.iloc[-2] and dif.iloc[-1] dea.iloc[-1]: macd_state 金叉 elif dif.iloc[-2] dea.iloc[-2] and dif.iloc[-1] dea.iloc[-1]: macd_state 死叉 else: macd_state 无明显交叉 rsi_now calc_rsi(df[close]).iloc[-1] if rsi_now 80: rsi_state 超买 elif rsi_now 20: rsi_state 超卖 else: rsi_state 中性 volume_ma5 df[volume].rolling(5).mean().iloc[-1] volume_ma20 df[volume].rolling(20).mean().iloc[-1] volume_state 放量 if volume_ma5 volume_ma20 * 1.2 else 缩量/常态 print(f[{code}] 趋势:{trend} | MACD:{macd_state} | RSI:{rsi_state} | 量能:{volume_state}) # 画图 dif, dea, hist calc_macd(df[close]) rsi calc_rsi(df[close]) ap_hist mpf.make_addplot(hist, typebar, panel2, color#888888, alpha0.6) ap_dif mpf.make_addplot(dif, panel2, color#e74c3c) ap_dea mpf.make_addplot(dea, panel2, color#2980b9) ap_rsi mpf.make_addplot(rsi, panel3, color#8e44ad, ylim(0, 100)) mpf.plot( df, typecandle, stylecharles, volumeTrue, mav(5, 10, 20, 60), addplot[ap_hist, ap_dif, ap_dea, ap_rsi], panel_ratios(3, 1, 1.5, 1), figsize(14, 12), titlef{code} K线走势, savefigdict(fnamef{code}.png, dpi120, bbox_inchestight), ) if __name__ __main__: for stock_code in [sh600000, sz000001]: try: analyze_stock(stock_code, days320) except Exception as exc: print(f{stock_code} 处理失败: {exc}) time.sleep(1)运行之后控制台会输出每只股票的趋势、MACD状态、RSI状态、量能状态目录下会生成对应的K线分析图。7.3 输出效果与后续扩展图片生成之后你会看到四个面板从上到下依次是蜡烛图加四条均线、成交量柱状图、MACD曲线和柱状体、RSI曲线。这个版式已经很接近主流行情软件的技术分析窗口。更进一步的扩展方向很多比如定时运行并把图片和结论保存成HTML报告或者把关键结论写入SQLite数据库积累一段时间后做统计分析。甚至可以把多个代码的结果汇总自动筛选出所有“多头排列且MACD金叉”的股票做一个初筛候选池。这些都是在这个基础上自然延伸出来的功能。8. 实战中踩过的坑与调优心得8.1 编码问题GBK乱码和半角分号实时行情接口最坑的点是编码。返回内容虽然是文本但内部存在GBK编码的中文股票名直接用requests默认的编码方式解析中文名称会变成乱码。我解决的标准做法是显式指定resp.encoding gbk另外解析时用分号;切分文本但返回内容末尾可能带一个空的语句块所以每行都判断一下startswith(v_)能过滤掉大部分空块和污染数据。8.2 日期与指数的坑字符串日期不能直接排序K线接口返回的日期是字符串如果不转成datetime直接排序你会得到字典序比如2024-01-05会在2024-01-18后面因为第二位是0跟1的比较。这种错误很隐蔽因为视觉上排序好像对但画出来的K线顺序是乱的。解决方案就是前面代码里体现的df[date] pd.to_datetime(df[date]) df df.set_index(date).sort_index()再一个和日期相关的坑是接口参数里的日期格式。有人想用paramsh000001,day,2024-01-01,2024-12-31,320,qfq去指定起止日期如果日期段跨度太长接口可能返回所有K线跟你预期完全不一致。所以我在项目里干脆不传日期直接用数量参数控制行数让接口自己按最新日期往前倒推。8.3 停牌、新股与异常值的处理新股上市初期没有足够的均线样本K线数量少于60根时60日均线肯定显示不完整。我画图前会做一个最小数据量检查if len(df) 60: print(f{code}: K线数量不足60根请减少均线周期或增加数据量) return停牌股票的实时行情返回的可能是停牌前最后一个交易日的快照价格字段不是实时跳动。这种情况对爬虫本身不算错误但如果你用当前价计算涨跌幅数字就会静止容易被误读成异常值。我在解析函数里加了一个判断如果当前价等于昨收涨跌幅为0就标记为“疑似停牌”。实际情况可能需要结合行情软件确认因为有些股价确实可以横盘一天不动。还有一类坑来自复权字段里的异常值比如成交量出现0或者负数。新股上市首日、或者某些特殊交易日数据会留下0成交量记录对均线计算影响不算大但如果你后面画成交量柱状图会出现难看的空缺。简单处理就是把这些行过滤掉或填充为NaN不强行参与计算。df df[df[volume] 0]8.4 数据增量更新与长期维护建议K线接口的方案适合拉取历史数据但如果你每天都要跑每次都全量重拉几百天显然不够优雅。增量更新的思路是本地存一份历史DataFrame每天只拉最近5个交易日的数据然后把非重复日期的新数据追加进去。这个操作很简单核心就是取交集去重old_df pd.read_parquet(history.parquet) new_df fetch_kline(sh600000, days10) merged pd.concat([old_df, new_df]) merged merged[~merged.index.duplicated(keeplast)].sort_index()保存我建议用parquet格式比CSV快很多而且自动保留时间索引和数据类型。CSV存几百只股票的话加载速度肉眼可见地慢Pandas配parquet读起来基本是瞬间完成。长期使用免费接口一定要做好心理准备它可能在某天悄悄改字段可能在盘中突然限流也可能直接关停。所以你的代码里爬取层和分析层一定要解耦。我通常是让所有数据先落本地再让画图和分析消费本地文件。这样即使某天数据源挂了我的分析流程还能用手头已有的数据继续跑不至于全链路瘫痪。最后一点心得技术指标这东西代码实现不难难的是你对自己的分析逻辑有没有信心。免费的行情数据接口对我们做学习研究已经足够但千万要记得任何基于历史数据的判断都只能是参考工具不代表未来的走势。项目可以尽情折腾真正做决定的时候还是要独立思考。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询