Python爬虫实战:从东方财富网高效抓取股票历史行情数据

发布时间:2026/7/30 7:41:47
Python爬虫实战:从东方财富网高效抓取股票历史行情数据 1. 项目缘起与核心价值最近在复盘今年的投资策略想整理一下自己关注的一批股票的历史数据做个简单的回测和趋势分析。第一反应就是去东方财富网这类财经门户查数据但手动一只只股票点进去再复制粘贴日期、开盘价、收盘价这些信息到Excel里工作量实在太大而且容易出错。作为一个有点编程基础的人很自然地就想到了用爬虫来自动化这个枯燥的过程。这个项目的目的很明确写一个Python脚本能够自动从东方财富网抓取指定股票在特定日期比如2023年12月30日的行情数据并规整地保存到Excel文件中方便后续进行数据分析或可视化。这不仅仅是省下几个小时复制粘贴的时间。对于需要定期跟踪多只股票、构建个人数据库的投资者或者是对金融市场数据分析感兴趣的学习者、研究者来说掌握这套方法意味着你拥有了一个稳定、可控的数据源。市面上虽然有免费的股票数据API但往往有调用频率限制、数据字段不全或者延迟较高的问题。直接从源头网站抓取数据的实时性和字段定义都更贴近你的实际使用场景。更重要的是这个过程会让你深入理解网页数据的结构、网络请求的机制以及数据清洗的要点这些技能在当今数据驱动的时代非常实用。2. 整体思路与技术选型解析2.1 目标网站分析与请求策略东方财富网的股票行情页面是典型的动态网页数据并非直接写在HTML源码里而是通过JavaScript异步加载的。直接使用requests库去请求网页URL拿到的是不包含核心行情数据的“空壳”HTML。因此我们的核心思路是模拟浏览器行为找到真正传输数据的网络接口API。通过浏览器的开发者工具F12切换到“网络”Network标签页然后刷新一个股票页面例如http://quote.eastmoney.com/sh600519.html对应贵州茅台。在纷繁的网络请求中我们需要筛选出类型为XHR或Fetch的请求这些通常是传输数据的API。经过观察和分析东方财富网股票的历史K线数据是通过一个特定的接口获取的其URL模式通常包含股票代码、周期日K、周K等、起止时间等参数。对于本项目我们需要的是特定日期的数据这通常对应日K线接口的一个数据点。这里有一个关键点不要试图去解析和渲染整个复杂的网页而是直接调用网站后端提供数据的纯净接口。这不仅能大幅降低解析难度、提高稳定性还能减少对目标网站服务器的压力是更友好、更高效的做法。2.2 核心工具链选择基于上述思路我们选择以下工具组合requests用于发送HTTP请求获取API返回的数据。它轻量、高效是Python网络请求的“瑞士军刀”。相比于urllib它的API更加人性化。pandas用于数据处理和保存。API返回的数据通常是JSON格式pandas可以非常方便地将其转换为结构化的DataFrame并且能一键导出为Excel、CSV等多种格式。它内置的数据清洗、转换功能也能派上用场。jsonPython标准库用于解析API返回的JSON字符串。为什么不选用BeautifulSoup或Selenium因为我们的目标是直接调用数据接口返回的是结构化的JSON不需要解析复杂的HTML标签因此BeautifulSoup在这里无用武之地。Selenium是模拟浏览器操作的“重武器”适用于需要执行JavaScript渲染或复杂交互的场合但在此场景下显得笨重且低效。直接使用requests调用API是最优解。3. 核心步骤拆解与实操要点3.1 第一步定位并分析数据接口这是整个爬虫项目最核心、也最需要耐心的一步。以抓取贵州茅台600519的日K线数据为例。打开Chrome浏览器进入东方财富网贵州茅台个股页面。按下F12打开开发者工具点击Network网络标签。在页面上找到K线图区域通常附近会有“日K”、“周K”、“月K”的选项卡。确保当前显示的是“日K”线。在开发者工具的Network面板中点击Fetch/XHR过滤器。然后尝试切换K线周期或者用鼠标拖动K线图改变显示的时间范围。观察此时Network面板中新增的请求。你会看到一个名称可能类似klinedata的请求。点击它查看其Headers请求头和Preview预览或Response响应标签页。关键信息提取请求URL (Request URL)这是我们要复制的接口地址。它可能长得像这样http://push2his.eastmoney.com/api/qt/stock/kline/get?secid1.600519klt101fqt1beg0end20500101查询参数 (Query String Parameters)分析URL中的参数意义至关重要。secid股票唯一标识。1.600519表示上海证券交易所的600519。0.开头通常代表深圳证券交易所如0.000001平安银行。kltK线周期。101通常代表日线102代表周线103代表月线。fqt复权类型。1代表前复权2代表后复权。beg/end起始和结束日期格式为YYYYMMDD。0和20500101这样的值可能代表获取全部数据。响应内容 (Response)在Preview标签页你可以看到接口返回的JSON数据结构。通常包含一个data字段其下的klines是一个列表列表中的每一项是一个字符串用逗号分隔了不同字段如时间,开盘价,收盘价,最高价,最低价,成交量,成交额,振幅,涨跌幅,涨跌额,换手率。注意东方财富网的接口地址和参数可能会随时间更新。本文基于2023年末的观察如果未来发现接口失效请按上述方法重新抓包分析。这是爬虫工程师的必备技能。3.2 第二步构建Python爬虫脚本理解了接口之后我们就可以开始编写代码了。脚本主要分为四个部分构建请求、获取数据、解析数据、保存数据。import requests import pandas as pd import json from datetime import datetime def fetch_stock_data(stock_code, date_str): 获取指定股票在指定日期的前复权日K线数据。 Args: stock_code (str): 股票代码如 600519沪市或 000001深市。 date_str (str): 日期字符串格式为 YYYYMMDD如 20231230。 Returns: pandas.DataFrame: 包含指定日期行情数据的DataFrame如果未找到则返回空DataFrame。 # 1. 构建请求URL和参数 # 判断市场沪市以1开头深市以0开头 market 1 if stock_code.startswith(6) else 0 secid f{market}.{stock_code} # 将日期字符串转换为时间戳毫秒作为查询的起始和结束点以精确获取该日数据 # 注意这里是一种简化做法。实际接口可能支持按日期范围查询。 target_date datetime.strptime(date_str, %Y%m%d) # 构造beg和end为同一天理论上接口应返回该日数据 # 但更常见的做法是请求一个包含目标日期的范围然后本地过滤 beg_date date_str end_date date_str url http://push2his.eastmoney.com/api/qt/stock/kline/get params { secid: secid, klt: 101, # 日K线 fqt: 1, # 前复权 beg: beg_date, end: end_date, fields1: f1,f2,f3,f4,f5, # 这些fields参数控制返回哪些元数据字段可能需要调整 fields2: f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61, # 控制K线数据字段 # f51:日期, f52:开盘, f53:收盘, f54:最高, f55:最低, f56:成交量, f57:成交额, f58:振幅, f59:涨跌幅, f60:涨跌额, f61:换手率 } # 2. 设置请求头模拟浏览器访问 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Referer: http://quote.eastmoney.com/ } try: response requests.get(url, paramsparams, headersheaders, timeout10) response.raise_for_status() # 检查请求是否成功 data_json response.json() # 3. 解析JSON数据 if data_json[data] is None: print(f未找到股票 {stock_code} 的数据请检查代码是否正确。) return pd.DataFrame() klines data_json[data].get(klines, []) if not klines: print(f股票 {stock_code} 在 {date_str} 无交易数据可能为停牌或非交易日。) return pd.DataFrame() # 4. 将数据转换为DataFrame # 每一条kline是一个用逗号分隔的字符串 data_list [] for kline in klines: items kline.split(,) # 确保字段数量匹配我们的预期 if len(items) 11: # 至少有前11个核心字段 data_list.append({ 日期: items[0], 开盘价: float(items[1]), 收盘价: float(items[2]), 最高价: float(items[3]), 最低价: float(items[4]), 成交量: int(float(items[5])), # 成交量可能是浮点数形式 成交额: float(items[6]), 振幅: float(items[7].rstrip(%)), # 去掉百分号 涨跌幅: float(items[8].rstrip(%)), 涨跌额: float(items[9]), 换手率: float(items[10].rstrip(%)) if len(items) 10 else None, }) df pd.DataFrame(data_list) # 5. 过滤出目标日期的数据由于接口可能返回一个范围这里做精确匹配 df_target df[df[日期] date_str] return df_target except requests.exceptions.RequestException as e: print(f网络请求出错: {e}) return pd.DataFrame() except json.JSONDecodeError as e: print(fJSON解析出错: {e}) return pd.DataFrame() except KeyError as e: print(f解析数据时键错误接口结构可能已变化: {e}) return pd.DataFrame() def save_to_excel(dataframe, stock_code, date_str): 将DataFrame保存为Excel文件。 if dataframe.empty: print(数据为空不保存文件。) return filename f{stock_code}_{date_str}_行情数据.xlsx # 使用pandas的ExcelWriter可以方便地设置sheet名和格式 with pd.ExcelWriter(filename, engineopenpyxl) as writer: dataframe.to_excel(writer, indexFalse, sheet_name行情数据) # 自动调整列宽近似 worksheet writer.sheets[行情数据] for column in worksheet.columns: max_length 0 column_letter column[0].column_letter for cell in column: try: if len(str(cell.value)) max_length: max_length len(str(cell.value)) except: pass adjusted_width min(max_length 2, 50) # 设置最大宽度 worksheet.column_dimensions[column_letter].width adjusted_width print(f数据已保存至文件: {filename}) # 主程序 if __name__ __main__: # 示例获取贵州茅台2023-12-30的数据 stock_code 600519 target_date 20231230 # 注意2023-12-30是周六非交易日。这里仅为演示格式。 # 实际使用时请替换为交易日日期如 ‘20231229’ print(f正在获取 {stock_code} 在 {target_date} 的行情数据...) df_data fetch_stock_data(stock_code, target_date) if not df_data.empty: print(获取成功数据预览) print(df_data) save_to_excel(df_data, stock_code, target_date) else: print(获取数据失败。)3.3 第三步运行脚本与结果验证将上述代码保存为eastmoney_stock_spider.py。在命令行中运行python eastmoney_stock_spider.py如果目标日期是交易日且股票正常交易脚本会输出数据预览并在当前目录下生成一个名为600519_20231229_行情数据.xlsx的Excel文件。用Excel打开你会看到规整的表格包含日期、开盘、收盘、成交量等关键字段。实操心得日期格式务必注意东方财富接口和你的脚本中使用的日期格式YYYYMMDD。datetime模块的strptime和strftime方法是你进行格式转换的好帮手。错误处理代码中加入了try...except块用于捕获网络请求失败、JSON解析错误、数据结构变化等异常。在实际爬虫中健壮的错误处理至关重要否则一个意外就可能导致整个脚本崩溃。非交易日A股市场在周末和法定节假日休市。如果你请求一个非交易日的日期接口通常会返回空数据或最近一个交易日的数据取决于接口逻辑。脚本中通过判断返回的klines列表是否为空来处理这种情况。4. 功能扩展与高级技巧4.1 批量爬取多只股票、多个日期单一股票单一日期的爬取价值有限。更常见的需求是批量处理。def batch_fetch_stocks(stock_list, date_list): 批量获取多只股票在多个日期的数据。 Args: stock_list (list): 股票代码列表如 [600519, 000001, 300750] date_list (list): 日期字符串列表如 [20231227, 20231228, 20231229] Returns: dict: 一个字典键为‘股票代码_日期’值为对应的DataFrame。 all_data {} for stock_code in stock_list: for date_str in date_list: print(f正在获取 {stock_code} - {date_str} ...) df fetch_stock_data(stock_code, date_str) key f{stock_code}_{date_str} all_data[key] df # 建议每次请求后短暂休眠避免请求过快被封IP time.sleep(0.5) return all_data # 可以将所有数据合并到一个大的DataFrame并保存到一个Excel的不同Sheet中 def save_batch_to_excel(data_dict, filenamebatch_stock_data.xlsx): with pd.ExcelWriter(filename, engineopenpyxl) as writer: for key, df in data_dict.items(): if not df.empty: # 使用股票代码作为sheet名如果过长则截取 sheet_name key[:31] # Excel sheet名最长31字符 df.to_excel(writer, indexFalse, sheet_namesheet_name) print(f批量数据已保存至 {filename})4.2 应对反爬虫策略东方财富网对爬虫有一定防护但通常不会对低频、合理的请求进行封禁。为了长期稳定运行需要注意设置请求头Headers我们已经模拟了User-Agent和Referer这是最基本的。有时可能还需要添加Accept、Accept-Language、Connection等字段使其更像真实浏览器。你可以直接从浏览器开发者工具中复制完整的请求头。控制请求频率在循环请求中使用time.sleep(random.uniform(1, 3))在请求间随机休眠1-3秒。过于密集的请求可能导致IP被暂时限制。使用代理IP如果需要极高频率地抓取考虑使用代理IP池来分散请求。但对于个人投资者级别的数据抓取通常不需要。处理Cookie和Session某些接口可能需要携带特定的Cookie。你可以使用requests.Session()对象来保持会话自动处理Cookie。session requests.Session() session.headers.update({ User-Agent: 你的User-Agent字符串, # ... 其他头信息 }) response session.get(url, paramsparams)4.3 数据清洗与增强获取的原始数据可能需要进行清洗才能用于分析处理缺失值非交易日数据为空。可以使用df.dropna()删除或用前值填充df.fillna(methodffill)。计算衍生指标基于开盘、收盘、成交量等基础数据可以计算移动平均线MA、相对强弱指数RSI等技术指标。pandas的.rolling()和.ewm()方法非常强大。数据格式化将‘日期’列转换为datetime类型方便时间序列分析df[日期] pd.to_datetime(df[日期], format%Y%m%d)。5. 常见问题与排查技巧实录在实际操作中你可能会遇到以下问题问题1运行脚本后返回的DataFrame始终为空。排查步骤检查股票代码和市场标识沪市6开头用secid1.xxxxxx深市0或3开头用secid0.xxxxxx。创业板30开头和科创板688开头也属于深市和沪市但需注意secid格式有时科创板可能需要特殊处理实践中先用1.688xxx尝试。检查日期确认日期是交易日周一至周五且非节假日。可以尝试一个已知的交易日如20231229。打印请求的URL在代码中print出构造好的完整URL手动粘贴到浏览器地址栏中查看是否能返回JSON数据。如果浏览器也返回错误或空数据说明接口已变更或参数有误。检查网络请求状态码在代码中打印response.status_code。如果不是200成功则请求本身有问题如404接口不存在403被禁止访问。问题2JSONDecodeError错误。原因服务器返回的不是有效的JSON格式可能是HTML错误页面如403 Forbidden页面或空响应。解决打印response.text的前500个字符查看服务器实际返回了什么。如果是HTML说明触发了反爬机制需要检查请求头、Cookie或添加延时。问题3获取到的数据字段与预期不符如字段顺序错乱、缺少字段。原因东方财富网的接口字段fields2参数可能发生微调。解决重新用开发者工具抓包仔细查看最新接口返回的klines列表中字符串的每个部分对应什么含义。然后更新代码中解析部分的字段映射关系。问题4保存Excel时出现警告或错误关于openpyxl引擎。原因未安装openpyxl库它是pandas读写.xlsx文件的依赖。解决在命令行中安装pip install openpyxl。问题5想获取分钟级5分钟、15分钟Tick数据或更长时间范围的历史数据。思路同样通过开发者工具分析。在K线图页面切换为“5分钟”或“60分钟”线观察网络请求中klt参数的变化。通常klt5表示5分钟线klt15表示15分钟线。对于长时间范围调整beg和end参数即可。但要注意接口可能有单次返回数据量的限制如果范围太大可能需要分多次请求。个人避坑技巧建立本地缓存对于已经成功获取的数据可以将其以股票代码_日期.json的形式保存到本地文件夹。下次需要时先检查本地是否存在避免重复请求既节省时间又尊重网站资源。使用配置文件将股票代码列表、日期范围、请求头信息等配置项写入一个单独的config.py或config.ini文件使主脚本更清晰也便于修改。日志记录不要只用print使用Python的logging模块记录信息、警告和错误。这有助于你长期运行脚本时追踪问题。尊重robots.txt在爬取任何网站前检查其robots.txt文件通常在网站根目录如https://quote.eastmoney.com/robots.txt了解网站允许和禁止爬取的范围。虽然数据接口可能不在此明确限制内但遵守规则是良好的网络公民行为。