1. 项目概述:爬取东方财富网股票数据并存储到MySQL
最近在做一个金融数据分析的小项目,需要获取A股市场的实时行情数据。东方财富网作为国内领先的财经门户,其股票数据页面结构清晰、更新及时,非常适合作为数据源。本文将分享如何用Python爬虫抓取东方财富网的个股数据,并通过PyMySQL库将清洗后的数据存入MySQL数据库的全过程。
这个方案特别适合以下场景:
- 需要建立本地股票数据库进行量化分析
- 想要定期采集特定股票的历史行情
- 需要将不同来源的金融数据集中存储管理
提示:实际操作前请务必阅读东方财富网的robots.txt文件,控制爬取频率,建议设置2秒以上的请求间隔
2. 环境准备与工具选型
2.1 基础环境配置
首先需要准备以下环境:
- Python 3.7+(推荐3.9版本)
- MySQL 5.7+(社区版即可)
- 开发工具:VS Code或PyCharm
关键Python库安装:
pip install requests beautifulsoup4 pymysql pandas2.2 技术栈选择考量
选择Requests+BeautifulSoup组合而非Scrapy的原因:
- 东方财富网页面结构相对简单,不需要复杂的爬虫框架
- 项目数据量不大(单次抓取约几十只股票)
- 更轻量级,适合新手快速上手
选择MySQL存储的三大优势:
- 结构化存储便于后续的SQL查询分析
- 与Python生态集成良好(PyMySQL库)
- 支持事务,确保数据完整性
3. 网页分析与数据抓取
3.1 东方财富网页面结构解析
以贵州茅台(600519)的个股页面为例:
http://quote.eastmoney.com/sh600519.html关键数据位置:
- 当前价:class="price"的div
- 涨跌幅:class="change"的span
- 成交量:id="volume"的span
- 市盈率:class="pe"的td
3.2 Python爬虫实现代码
import requests from bs4 import BeautifulSoup import time def get_stock_data(stock_code): url = f"http://quote.eastmoney.com/{'sh' if stock_code.startswith('6') else 'sz'}{stock_code}.html" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } try: response = requests.get(url, headers=headers) soup = BeautifulSoup(response.text, 'html.parser') data = { 'code': stock_code, 'price': soup.find('div', class_='price').get_text(), 'change': soup.find('span', class_='change').get_text(), 'volume': soup.find('span', id='volume').get_text(), 'pe': soup.find('td', class_='pe').get_text(), 'timestamp': int(time.time()) } return data except Exception as e: print(f"获取{stock_code}数据失败:{str(e)}") return None重要提醒:务必设置合理的请求间隔,建议使用time.sleep(2)控制爬取频率
4. MySQL数据库设计与存储实现
4.1 数据库表结构设计
创建stock_data表:
CREATE TABLE `stock_data` ( `id` int(11) NOT NULL AUTO_INCREMENT, `stock_code` varchar(10) NOT NULL, `price` decimal(10,2) DEFAULT NULL, `change_percent` decimal(10,2) DEFAULT NULL, `volume` bigint(20) DEFAULT NULL, `pe_ratio` decimal(10,2) DEFAULT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_code` (`stock_code`), KEY `idx_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;4.2 Python连接MySQL的完整示例
import pymysql def save_to_mysql(data): connection = pymysql.connect( host='localhost', user='your_username', password='your_password', database='stock_db', charset='utf8mb4' ) try: with connection.cursor() as cursor: sql = """INSERT INTO stock_data (stock_code, price, change_percent, volume, pe_ratio) VALUES (%s, %s, %s, %s, %s)""" cursor.execute(sql, ( data['code'], float(data['price']), float(data['change'].strip('%')), int(data['volume']), float(data['pe']) )) connection.commit() finally: connection.close()5. 完整工作流与优化方案
5.1 主程序逻辑整合
def main(): stock_list = ['600519', '000858', '601318'] # 茅台、五粮液、平安 for code in stock_list: data = get_stock_data(code) if data: save_to_mysql(data) time.sleep(3) # 严格遵守爬虫礼仪 if __name__ == '__main__': main()5.2 性能优化建议
- 使用连接池管理数据库连接
- 实现异常重试机制(最多3次)
- 添加日志记录功能
- 考虑使用多线程加速(但要控制总请求频率)
6. 常见问题与解决方案
6.1 数据抓取失败排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 返回403错误 | IP被限制 | 1. 检查User-Agent 2. 增加延迟 3. 使用代理IP |
| 数据为空 | 页面改版 | 1. 更新CSS选择器 2. 检查JavaScript渲染 |
| 连接超时 | 网络问题 | 1. 增加超时时间 2. 添加重试逻辑 |
6.2 MySQL存储异常处理
数据库写入时的三个关键检查点:
- 字段类型匹配(特别是字符串转数字)
- 唯一键冲突处理
- 事务回滚机制
改进后的存储函数示例:
def safe_save_to_mysql(data): try: # 转换数据类型 data['price'] = float(data['price']) data['change'] = float(data['change'].strip('%')) data['volume'] = int(data['volume'].replace(',', '')) data['pe'] = float(data['pe']) if data['pe'] != '-' else None save_to_mysql(data) except ValueError as e: print(f"数据格式错误:{e}") except pymysql.err.IntegrityError: print("重复数据,跳过") except Exception as e: print(f"数据库错误:{e}")7. 项目扩展方向
7.1 定时任务实现
使用APScheduler实现定时采集:
from apscheduler.schedulers.blocking import BlockingScheduler scheduler = BlockingScheduler() @scheduler.scheduled_job('cron', hour='9-15', minute='*/5') def scheduled_job(): main() scheduler.start()7.2 数据可视化分析
结合Pandas和Matplotlib:
import matplotlib.pyplot as plt def analyze_data(): connection = pymysql.connect(...) df = pd.read_sql("SELECT * FROM stock_data WHERE create_time > DATE_SUB(NOW(), INTERVAL 7 DAY)", connection) plt.figure(figsize=(12,6)) for code, group in df.groupby('stock_code'): plt.plot(group['create_time'], group['price'], label=code) plt.legend() plt.show()在实际项目中,我通常会设置凌晨2-4点进行历史数据补全,避免交易日的高峰时段。对于高频数据需求,建议考虑使用东方财富的API服务(如有),这比爬虫更稳定可靠。另外,MySQL表设计时预留了20%的额外字段,方便后续添加技术指标等衍生数据。