代码结构分析
代码整体分为五个主要部分:基础配置、日志工具类、核心配置、爬虫类和主程序逻辑。每个部分都承担特定功能,共同构建一个健壮的爬虫系统。下面我将逐一分析这些部分。
1. 基础配置
基础配置部分处理环境初始化和安全设置。代码开头禁用了SSL警告,以避免在使用requests库时因证书问题产生的干扰。这在爬虫场景中很常见,因为许多金融网站使用自签名证书或混合HTTP/HTTPS内容。
python
import sys
import os
import time
import random
import requests
import json
import mysql.connector
import logging
import schedule
import gzip
from io import BytesIO
from datetime import datetime
from mysql.connector import Error
from typing import List, Dict, Any
import traceback
# ===================== 基础配置 =====================
# 禁用SSL警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
此外,代码针对打包环境(如使用PyInstaller生成的exe文件)和非打包环境(如直接运行Python脚本)做了差异化处理。在打包环境中,它禁用控制台输出,防止在无界面环境中弹出窗口;在非打包环境中,则保留控制台日志以便调试。日志文件存储在stock_crawler_logs目录下,按小时分文件记录。
python
# 无控制台窗口配置
class DisableConsoleOutput:
def write(self, msg):
pass
def flush(self):
pass
if getattr(sys, 'frozen', False):
sys.stdout = DisableConsoleOutput()
sys.stderr = DisableConsoleOutput()
LOG_DIR = os.path.join(os.path.dirname(sys.executable), "stock_crawler_logs")
else:
LOG_DIR = os.path.join(os.path.dirname(__file__), "stock_crawler_logs")
# 创建日志文件夹
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
这种设计提升了代码的可移植性,使其适用于不同部署场景。例如,在Windows服务或后台进程中运行时,避免不必要的用户交互。
2. 日志工具类
日志系统是爬虫的关键组件,用于记录运行状态、错误信息和性能指标。代码定义了一个LogManager类,提供静态方法get_logger来创建日志器。日志器按小时分文件存储日志,确保文件大小可控,并支持同时写入文件和控制台(仅在非打包环境)。
python
# ===================== 日志工具类(无global依赖) =====================
class LogManager:
@staticmethod
def get_logger():
"""创建按小时分文件的日志器(无global依赖)"""
# 日志文件名:stock_crawler_YYYYMMDD_HH.log
log_filename = f'stock_crawler_{datetime.now().strftime("%Y%m%d_%H")}.log'
log_filepath = os.path.join(LOG_DIR, log_filename)
# 配置日志器(使用唯一名称,避免冲突)
logger = logging.getLogger(f"EastMoneyCrawler_{datetime.now().strftime('%Y%m%d_%H')}")
logger.setLevel(logging.INFO)
logger.handlers.clear() # 清空旧处理器
# 文件处理器
file_handler = logging.FileHandler(log_filepath, encoding='utf-8')
file_handler.setLevel(logging.INFO)
# 控制台处理器(仅非打包版本)
if not getattr(sys, 'frozen', False):
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 日志格式
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
if not getattr(sys, 'frozen', False):
console_handler.setFormatter(formatter)
# 添加处理器
logger.addHandler(file_handler)
if not getattr(sys, 'frozen', False):
logger.addHandler(console_handler)
return logger
日志格式采用标准模板:%(asctime)s - %(levelname)s - %(message)s,便于后续分析。通过logger.handlers.clear()确保每次调用都创建新的处理器,避免日志重复写入。这种设计符合无状态原则,增强了代码的可重用性。
3. 核心配置
核心配置部分定义了爬虫所需的常量参数,包括数据库连接信息、请求头、字段映射和爬虫行为设置。这些参数集中管理,便于修改和扩展。
python
# ===================== 核心配置(请修改为你的数据库信息) =====================
DB_CONFIG = {
"host": "localhost",
"user": "root",
"passwd": "123456",
"database": "gupiao_infos",
"charset": "utf8mb4",
"connect_timeout": 10
}
# 东方财富请求头
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Referer": "https://quote.eastmoney.com/",
"Connection": "keep-alive"
}
# 字段映射
FIELD_TYPE_MAPPING = {
'symbol': 'VARCHAR(20) COMMENT "股票代码"',
'name': 'VARCHAR(50) COMMENT "股票名称"',
'price': 'DECIMAL(10,2) COMMENT "当前价格"',
'change_percent': 'DECIMAL(8,2) COMMENT "涨跌幅(%)"',
'change_price': 'DECIMAL(10,2) COMMENT "涨跌额"',
'volume': 'BIGINT COMMENT "成交量(股)"',
'amount': 'BIGINT COMMENT "成交额(元)"',
'high': 'DECIMAL(10,2) COMMENT "最高价"',
'low': 'DECIMAL(10,2) COMMENT "最低价"',
'open': 'DECIMAL(10,2) COMMENT "开盘价"',
'prev_close': 'DECIMAL(10,2) COMMENT "昨日收盘价"'
}
# 爬虫核心配置
MAX_RETRIES = 3 # 单页最大重试次数
PAGE_START = 1 # 起始页码
REQUEST_DELAY = (2, 3) # 正常请求延迟(秒)
RETRY_DELAY = (4, 6) # 失败重试延迟(秒)
BATCH_SIZE = 100 # 每页100条(接口上限)
VALID_DATA_THRESHOLD = 50 # 单页有效数据少于此值则终止
CONTINUE_FAIL_LIMIT = 3 # 连续失败页数上限
STOP_FILE_NAME = "stop_crawler.txt" # 停止程序的文件名
数据库配置DB_CONFIG使用MySQL连接参数,支持超时设置。请求头HEADERS模拟浏览器行为,降低被反爬虫机制拦截的风险。字段映射FIELD_TYPE_MAPPING定义了数据表列的类型和注释,确保数据库结构一致。爬虫配置参数如MAX_RETRIES和REQUEST_DELAY优化了抓取策略,避免频繁请求导致IP封禁。STOP_FILE_NAME提供外部控制机制,通过创建文件来终止爬虫,增强可控性。
4. 爬虫类
EastMoneyCrawler类是代码的核心,实现数据抓取、处理和存储逻辑。类初始化时创建日志实例,并管理数据库连接。
python
# ===================== 核心爬虫类 =====================
class EastMoneyCrawler:
def __init__(self):
self.db_conn = None
self.db_cursor = None
self.crawl_time = None
self.total_count = 0 # 累计抓取条数
self.continue_fail_count = 0 # 连续失败页数
self.logger = LogManager.get_logger() # 类内日志实例
数据库管理
_init_db方法建立数据库连接,_close_db方法关闭连接,确保资源释放。_get_table_name方法根据抓取时间生成动态表名,例如eastmoney_stock_20231015_14,支持按小时分表存储数据,便于后续查询和分析。
python
def _init_db(self):
"""初始化数据库连接"""
try:
self.db_conn = mysql.connector.connect(**DB_CONFIG)
self.db_cursor = self.db_conn.cursor()
self.logger.info("✅ 数据库连接成功")
except Error as e:
self.logger.error(f"❌ 数据库连接失败: {str(e)}")
raise
def _close_db(self):
"""关闭数据库连接"""
if self.db_cursor:
self.db_cursor.close()
if self.db_conn:
self.db_conn.close()
self.logger.info("✅ 数据库连接已关闭")
def _get_table_name(self):
"""生成按小时分表的表名"""
return f"eastmoney_stock_{self.crawl_time.strftime('%Y%m%d_%H')}"
_create_table方法基于样本数据创建数据库表。它使用FIELD_TYPE_MAPPING定义列结构,确保表结构与数据字段匹配。例如,如果样本数据包含symbol字段,表会创建相应列并添加注释。
python
def _create_table(self, sample_data: Dict[str, Any]):
"""创建数据表"""
table_name = self._get_table_name()
fields = []
for field in sample_data.keys():
if field in FIELD_TYPE_MAPPING:
fields.append(f"`{field}` {FIELD_TYPE_MAPPING[field]}")
create_sql = f"""
CREATE TABLE IF NOT EXISTS `{table_name}` (
id INT AUTO_INCREMENT PRIMARY KEY,
{', '.join(fields)},
crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='东方财富股票数据表';
"""
try:
self.db_cursor.execute(create_sql)
self.db_conn.commit()
self.logger.info(f"✅ 表 {table_name} 创建成功")
except Error as e:
self.logger.error(f"❌ 表创建失败: {str(e)}")
raise
数据抓取逻辑
爬虫主逻辑在run方法中实现。它设置抓取时间,初始化数据库,并启动分页抓取循环。每个分页请求使用重试机制,处理网络异常或API变化。
python
def run(self):
"""主运行方法"""
self.crawl_time = datetime.now()
self.logger.info(f"🕒 爬虫启动于: {self.crawl_time}")
self._init_db()
page = PAGE_START
stop_flag = False
while not stop_flag:
if os.path.exists(STOP_FILE_NAME):
self.logger.info("🛑 检测到停止文件,爬虫终止")
break
try:
data = self._fetch_page(page)
if not data:
self.continue_fail_count += 1
if self.continue_fail_count >= CONTINUE_FAIL_LIMIT:
self.logger.warning(f"⚠️ 连续失败页数达上限: {CONTINUE_FAIL_LIMIT}, 爬虫终止")
break
continue
self.continue_fail_count = 0
self._save_data(data)
self.total_count += len(data)
self.logger.info(f"📊 第 {page} 页抓取成功, 数据量: {len(data)}, 累计: {self.total_count}")
if len(data) < VALID_DATA_THRESHOLD:
self.logger.info(f"⏹️ 数据量低于阈值 {VALID_DATA_THRESHOLD}, 爬虫终止")
break
# 随机延迟
delay = random.uniform(REQUEST_DELAY[0], REQUEST_DELAY[1])
time.sleep(delay)
page += 1
except Exception as e:
self.logger.error(f"❌ 第 {page} 页处理异常: {str(e)}")
traceback.print_exc()
self.continue_fail_count += 1
if self.continue_fail_count >= CONTINUE_FAIL_LIMIT:
self.logger.error(f"🛑 连续异常达上限, 爬虫终止")
break
delay = random.uniform(RETRY_DELAY[0], RETRY_DELAY[1])
time.sleep(delay)
self._close_db()
self.logger.info(f"🏁 爬虫结束, 总抓取条数: {self.total_count}")
_fetch_page方法构造API请求URL,发送HTTP请求并解析响应数据。API URL使用参数化设计,支持分页和排序。响应数据经JSON解析后,提取股票信息列表。
python
def _fetch_page(self, page: int) -> List[Dict[str, Any]]:
"""抓取单页数据"""
for attempt in range(MAX_RETRIES):
try:
url = f"https://datacenter.eastmoney.com/api/data/get?type=RPTA_WEB_SZ_SS&sty=ALL&p={page}&ps={BATCH_SIZE}&sr=-1"
response = requests.get(url, headers=HEADERS, verify=False, timeout=10)
response.raise_for_status()
# 解压gzip响应
if response.headers.get('Content-Encoding') == 'gzip':
compressed_data = BytesIO(response.content)
with gzip.GzipFile(fileobj=compressed_data) as f:
raw_data = f.read()
data = json.loads(raw_data)
else:
data = response.json()
if data.get("success") and data.get("result") and data["result"].get("data"):
return data["result"]["data"]
else:
self.logger.warning(f"⚠️ 第 {page} 页无有效数据, 响应: {data}")
return []
except requests.exceptions.RequestException as e:
self.logger.warning(f"⚠️ 第 {page} 页请求失败, 重试 {attempt+1}/{MAX_RETRIES}: {str(e)}")
delay = random.uniform(RETRY_DELAY[0], RETRY_DELAY[1])
time.sleep(delay)
return []
_save_data方法将抓取的数据批量插入数据库。它先检查表是否存在,若不存在则创建;然后构造INSERT语句,使用事务批量提交,提升效率。
python
def _save_data(self, data: List[Dict[str, Any]]):
"""保存数据到数据库"""
if not data:
return
table_name = self._get_table_name()
# 检查表是否存在,若不存在则创建
sample = data[0]
self._create_table(sample)
# 构造插入语句
fields = list(sample.keys())
placeholders = ', '.join(['%s'] * len(fields))
columns = ', '.join([f'`{field}`' for field in fields])
insert_sql = f"INSERT INTO `{table_name}` ({columns}) VALUES ({placeholders})"
try:
# 批量插入
values = [tuple(item[field] for field in fields) for item in data]
self.db_cursor.executemany(insert_sql, values)
self.db_conn.commit()
self.logger.info(f"💾 数据保存成功, 条数: {len(data)}")
except Error as e:
self.logger.error(f"❌ 数据插入失败: {str(e)}")
self.db_conn.rollback()
raise
5. 主程序与定时任务
代码末尾定义了主函数main,它初始化爬虫实例并启动抓取。使用schedule库支持定时任务,例如每10分钟运行一次爬虫,实现数据持续更新。
python
def main():
crawler = EastMoneyCrawler()
crawler.run()
if __name__ == "__main__":
# 定时任务配置
schedule.every(10).minutes.do(main)
while True:
schedule.run_pending()
time.sleep(60)
主函数在__name__ == "__main__"块中执行,结合schedule实现循环运行。定时器间隔可调,满足不同数据更新频率需求。
完整代码呈现
以下为完整的代码内容,包括所有导入、配置、类定义和主程序逻辑。
python
import sys
import os
import time
import random
import requests
import json
import mysql.connector
import logging
import schedule
import gzip
from io import BytesIO
from datetime import datetime
from mysql.connector import Error
from typing import List, Dict, Any
import traceback
# ===================== 基础配置 =====================
# 禁用SSL警告
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 无控制台窗口配置
class DisableConsoleOutput:
def write(self, msg):
pass
def flush(self):
pass
if getattr(sys, 'frozen', False):
sys.stdout = DisableConsoleOutput()
sys.stderr = DisableConsoleOutput()
LOG_DIR = os.path.join(os.path.dirname(sys.executable), "stock_crawler_logs")
else:
LOG_DIR = os.path.join(os.path.dirname(__file__), "stock_crawler_logs")
# 创建日志文件夹
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
# ===================== 日志工具类(无global依赖) =====================
class LogManager:
@staticmethod
def get_logger():
"""创建按小时分文件的日志器(无global依赖)"""
# 日志文件名:stock_crawler_YYYYMMDD_HH.log
log_filename = f'stock_crawler_{datetime.now().strftime("%Y%m%d_%H")}.log'
log_filepath = os.path.join(LOG_DIR, log_filename)
# 配置日志器(使用唯一名称,避免冲突)
logger = logging.getLogger(f"EastMoneyCrawler_{datetime.now().strftime('%Y%m%d_%H')}")
logger.setLevel(logging.INFO)
logger.handlers.clear() # 清空旧处理器
# 文件处理器
file_handler = logging.FileHandler(log_filepath, encoding='utf-8')
file_handler.setLevel(logging.INFO)
# 控制台处理器(仅非打包版本)
if not getattr(sys, 'frozen', False):
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 日志格式
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
if not getattr(sys, 'frozen', False):
console_handler.setFormatter(formatter)
# 添加处理器
logger.addHandler(file_handler)
if not getattr(sys, 'frozen', False):
logger.addHandler(console_handler)
return logger
# ===================== 核心配置(请修改为你的数据库信息) =====================
DB_CONFIG = {
"host": "localhost",
"user": "root",
"passwd": "123456",
"database": "gupiao_infos",
"charset": "utf8mb4",
"connect_timeout": 10
}
# 东方财富请求头
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Referer": "https://quote.eastmoney.com/",
"Connection": "keep-alive"
}
# 字段映射
FIELD_TYPE_MAPPING = {
'symbol': 'VARCHAR(20) COMMENT "股票代码"',
'name': 'VARCHAR(50) COMMENT "股票名称"',
'price': 'DECIMAL(10,2) COMMENT "当前价格"',
'change_percent': 'DECIMAL(8,2) COMMENT "涨跌幅(%)"',
'change_price': 'DECIMAL(10,2) COMMENT "涨跌额"',
'volume': 'BIGINT COMMENT "成交量(股)"',
'amount': 'BIGINT COMMENT "成交额(元)"',
'high': 'DECIMAL(10,2) COMMENT "最高价"',
'low': 'DECIMAL(10,2) COMMENT "最低价"',
'open': 'DECIMAL(10,2) COMMENT "开盘价"',
'prev_close': 'DECIMAL(10,2) COMMENT "昨日收盘价"'
}
# 爬虫核心配置
MAX_RETRIES = 3 # 单页最大重试次数
PAGE_START = 1 # 起始页码
REQUEST_DELAY = (2, 3) # 正常请求延迟(秒)
RETRY_DELAY = (4, 6) # 失败重试延迟(秒)
BATCH_SIZE = 100 # 每页100条(接口上限)
VALID_DATA_THRESHOLD = 50 # 单页有效数据少于此值则终止
CONTINUE_FAIL_LIMIT = 3 # 连续失败页数上限
STOP_FILE_NAME = "stop_crawler.txt" # 停止程序的文件名
# ===================== 核心爬虫类 =====================
class EastMoneyCrawler:
def __init__(self):
self.db_conn = None
self.db_cursor = None
self.crawl_time = None
self.total_count = 0 # 累计抓取条数
self.continue_fail_count = 0 # 连续失败页数
self.logger = LogManager.get_logger() # 类内日志实例
def _init_db(self):
"""初始化数据库连接"""
try:
self.db_conn = mysql.connector.connect(**DB_CONFIG)
self.db_cursor = self.db_conn.cursor()
self.logger.info("✅ 数据库连接成功")
except Error as e:
self.logger.error(f"❌ 数据库连接失败: {str(e)}")
raise
def _close_db(self):
"""关闭数据库连接"""
if self.db_cursor:
self.db_cursor.close()
if self.db_conn:
self.db_conn.close()
self.logger.info("✅ 数据库连接已关闭")
def _get_table_name(self):
"""生成按小时分表的表名"""
return f"eastmoney_stock_{self.crawl_time.strftime('%Y%m%d_%H')}"
def _create_table(self, sample_data: Dict[str, Any]):
"""创建数据表"""
table_name = self._get_table_name()
fields = []
for field in sample_data.keys():
if field in FIELD_TYPE_MAPPING:
fields.append(f"`{field}` {FIELD_TYPE_MAPPING[field]}")
create_sql = f"""
CREATE TABLE IF NOT EXISTS `{table_name}` (
id INT AUTO_INCREMENT PRIMARY KEY,
{', '.join(fields)},
crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='东方财富股票数据表';
"""
try:
self.db_cursor.execute(create_sql)
self.db_conn.commit()
self.logger.info(f"✅ 表 {table_name} 创建成功")
except Error as e:
self.logger.error(f"❌ 表创建失败: {str(e)}")
raise
def _fetch_page(self, page: int) -> List[Dict[str, Any]]:
"""抓取单页数据"""
for attempt in range(MAX_RETRIES):
try:
url = f"https://datacenter.eastmoney.com/api/data/get?type=RPTA_WEB_SZ_SS&sty=ALL&p={page}&ps={BATCH_SIZE}&sr=-1"
response = requests.get(url, headers=HEADERS, verify=False, timeout=10)
response.raise_for_status()
# 解压gzip响应
if response.headers.get('Content-Encoding') == 'gzip':
compressed_data = BytesIO(response.content)
with gzip.GzipFile(fileobj=compressed_data) as f:
raw_data = f.read()
data = json.loads(raw_data)
else:
data = response.json()
if data.get("success") and data.get("result") and data["result"].get("data"):
return data["result"]["data"]
else:
self.logger.warning(f"⚠️ 第 {page} 页无有效数据, 响应: {data}")
return []
except requests.exceptions.RequestException as e:
self.logger.warning(f"⚠️ 第 {page} 页请求失败, 重试 {attempt+1}/{MAX_RETRIES}: {str(e)}")
delay = random.uniform(RETRY_DELAY[0], RETRY_DELAY[1])
time.sleep(delay)
return []
def _save_data(self, data: List[Dict[str, Any]]):
"""保存数据到数据库"""
if not data:
return
table_name = self._get_table_name()
# 检查表是否存在,若不存在则创建
sample = data[0]
self._create_table(sample)
# 构造插入语句
fields = list(sample.keys())
placeholders = ', '.join(['%s'] * len(fields))
columns = ', '.join([f'`{field}`' for field in fields])
insert_sql = f"INSERT INTO `{table_name}` ({columns}) VALUES ({placeholders})"
try:
# 批量插入
values = [tuple(item[field] for field in fields) for item in data]
self.db_cursor.executemany(insert_sql, values)
self.db_conn.commit()
self.logger.info(f"💾 数据保存成功, 条数: {len(data)}")
except Error as e:
self.logger.error(f"❌ 数据插入失败: {str(e)}")
self.db_conn.rollback()
raise
def run(self):
"""主运行方法"""
self.crawl_time = datetime.now()
self.logger.info(f"🕒 爬虫启动于: {self.crawl_time}")
self._init_db()
page = PAGE_START
stop_flag = False
while not stop_flag:
if os.path.exists(STOP_FILE_NAME):
self.logger.info("🛑 检测到停止文件,爬虫终止")
break
try:
data = self._fetch_page(page)
if not data:
self.continue_fail_count += 1
if self.continue_fail_count >= CONTINUE_FAIL_LIMIT:
self.logger.warning(f"⚠️ 连续失败页数达上限: {CONTINUE_FAIL_LIMIT}, 爬虫终止")
break
continue
self.continue_fail_count = 0
self._save_data(data)
self.total_count += len(data)
self.logger.info(f"📊 第 {page} 页抓取成功, 数据量: {len(data)}, 累计: {self.total_count}")
if len(data) < VALID_DATA_THRESHOLD:
self.logger.info(f"⏹️ 数据量低于阈值 {VALID_DATA_THRESHOLD}, 爬虫终止")
break
# 随机延迟
delay = random.uniform(REQUEST_DELAY[0], REQUEST_DELAY[1])
time.sleep(delay)
page += 1
except Exception as e:
self.logger.error(f"❌ 第 {page} 页处理异常: {str(e)}")
traceback.print_exc()
self.continue_fail_count += 1
if self.continue_fail_count >= CONTINUE_FAIL_LIMIT:
self.logger.error(f"🛑 连续异常达上限, 爬虫终止")
break
delay = random.uniform(RETRY_DELAY[0], RETRY_DELAY[1])
time.sleep(delay)
self._close_db()
self.logger.info(f"🏁 爬虫结束, 总抓取条数: {self.total_count}")
def main():
crawler = EastMoneyCrawler()
crawler.run()
if __name__ == "__main__":
# 定时任务配置
schedule.every(10).minutes.do(main)
while True:
schedule.run_pending()
time.sleep(60)
潜在问题与改进方向
潜在问题
- API稳定性依赖:爬虫高度依赖网站的API接口结构和响应格式。若API变更(如URL路径或JSON结构),爬虫可能失效。需增加接口版本检测或备用数据源。
- 数据完整性风险:分页抓取时,若网络波动导致中间页失败,数据可能缺失。建议添加断点续爬功能,记录最后成功页码。
- 性能瓶颈:批量插入虽优化效率,但大表操作可能影响数据库性能。可引入队列系统(如RabbitMQ)异步处理。
- 反爬虫机制:频繁请求可能触发IP封禁。应集成代理IP池或分布式爬取。
改进建议
- 错误处理增强:添加更细粒度的异常分类,如网络错误、解析错误等,并支持自动恢复。
- 数据验证:在保存前校验数据有效性,例如价格是否在合理范围( \\text{price} \> 0 ),避免脏数据。
- 扩展性:支持多交易所数据抓取,如上海证券交易所或纳斯达克,通过参数化配置API URL。
- 监控集成:结合Prometheus或Grafana实现实时监控,跟踪抓取成功率、延迟等指标。
结论
本文详细分析并呈现了一个完整的股票爬虫代码。代码采用模块化设计,涵盖配置管理、日志系统、数据库交互和核心爬取逻辑,确保数据抓取的可靠性和高效性。通过分页抓取、重试机制和定时任务,它能够自动化获取股票行情数据,为金融分析提供基础支持。潜在改进如增强错误处理和扩展数据源,可进一步提升其工业级应用价值。总体而言,该代码是金融数据工程的良好实践,适用于量化交易、市场研究等场景。