stock-tracker

This commit is contained in:
C菌
2026-07-04 00:17:11 +08:00
commit 6087341a48
6463 changed files with 1929869 additions and 0 deletions
@@ -0,0 +1,6 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
Date: 2020/2/13 21:21
Desc:
"""
@@ -0,0 +1,10 @@
# !/usr/bin/env python
"""
Date: 2024/4/7 15:30
Desc: 通用变量
"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/114.0.0.0 Safari/537.36"
}
@@ -0,0 +1,43 @@
class AkshareConfig:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.proxies = None
return cls._instance
@classmethod
def set_proxies(cls, proxies):
cls().proxies = proxies
@classmethod
def get_proxies(cls):
return cls().proxies
config = AkshareConfig()
# 导出 set_proxies 函数
def set_proxies(proxies):
config.set_proxies(proxies)
def get_proxies():
return config.get_proxies()
class ProxyContext:
def __init__(self, proxies):
self.proxies = proxies
self.old_proxies = None
def __enter__(self):
self.old_proxies = config.get_proxies()
config.set_proxies(self.proxies)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
config.set_proxies(self.old_proxies)
return False # 不处理异常
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,77 @@
# !/usr/bin/env python
"""
Date: 2025/3/10 18:00
Desc: 通用帮助函数
"""
import math
import random
import time
from typing import List, Dict
import pandas as pd
from akshare.utils.request import request_with_retry
from akshare.utils.tqdm import get_tqdm
def fetch_paginated_data(url: str, base_params: Dict, timeout: int = 15):
"""
东方财富-分页获取数据并合并结果
https://quote.eastmoney.com/f1.html?newcode=0.000001
:param url: 股票代码
:type url: str
:param base_params: 基础请求参数
:type base_params: dict
:param timeout: 请求超时时间
:type timeout: str
:return: 合并后的数据
:rtype: pandas.DataFrame
"""
# 复制参数以避免修改原始参数
params = base_params.copy()
# 获取第一页数据,用于确定分页信息
r = request_with_retry(url, params=params, timeout=timeout)
data_json = r.json()
# 计算分页信息
per_page_num = len(data_json["data"]["diff"])
total_page = math.ceil(data_json["data"]["total"] / per_page_num)
# 存储所有页面数据
temp_list = []
# 添加第一页数据
temp_list.append(pd.DataFrame(data_json["data"]["diff"]))
# 获取进度条
tqdm = get_tqdm()
# 获取剩余页面数据
for page in tqdm(range(2, total_page + 1), leave=False):
params.update({"pn": page})
# 添加随机延迟,避免请求过于频繁
time.sleep(random.uniform(0.5, 1.5))
r = request_with_retry(url, params=params, timeout=timeout)
data_json = r.json()
inner_temp_df = pd.DataFrame(data_json["data"]["diff"])
temp_list.append(inner_temp_df)
# 合并所有数据
temp_df = pd.concat(temp_list, ignore_index=True)
temp_df["f3"] = pd.to_numeric(temp_df["f3"], errors="coerce")
temp_df.sort_values(by=["f3"], ascending=False, inplace=True, ignore_index=True)
temp_df.reset_index(inplace=True)
temp_df["index"] = temp_df["index"].astype(int) + 1
return temp_df
def set_df_columns(df: pd.DataFrame, cols: List[str]) -> pd.DataFrame:
"""
设置 pandas.DataFrame 为空的情况
:param df: 需要设置命名的数据框
:type df: pandas.DataFrame
:param cols: 字段的列表
:type cols: list
:return: 重新设置后的数据
:rtype: pandas.DataFrame
"""
if df.shape == (0, 0):
return pd.DataFrame(data=[], columns=cols)
else:
df.columns = cols
return df
@@ -0,0 +1,53 @@
"""
利用多进行执行 js 代码方案
1. 未能解决 gevent 调用问题
2. 导致 js 代码执行缓慢
3. 该方案废弃,这里仅作参考
同时发现 gevent 里面无法调用异步的接口
"""
import concurrent.futures
# 定义在模块级别的函数
def js_executor_function(js_code_str, method, args):
"""在新进程中执行 JavaScript 代码的函数"""
from py_mini_racer import MiniRacer
js_code = MiniRacer()
js_code.eval(js_code_str)
if method == "call":
fn_name = args[0]
fn_args = args[1:]
return js_code.call(fn_name, *fn_args)
elif method == "eval":
return js_code.eval(args[0])
else:
raise ValueError(f"不支持的方法: {method}")
def execute_js_in_executor(js_code_str, method, *args, timeout=30):
"""
使用 ProcessPoolExecutor 在独立进程中执行 JavaScript
参数:
js_code_str: JavaScript 代码字符串
method: 'call''eval'
args: 如果 method 是 'call',第一个参数是函数名,后续是函数参数
如果 method 是 'eval',只需提供一个参数:要评估的代码
timeout: 超时时间(秒)
返回:
执行结果
"""
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
future = executor.submit(js_executor_function, js_code_str, method, args)
try:
return future.result(timeout=timeout)
except concurrent.futures.TimeoutError:
# 清理资源并抛出超时异常
executor.shutdown(wait=False)
raise TimeoutError("JavaScript 执行超时")
@@ -0,0 +1,64 @@
# !/usr/bin/env python
"""
Date: 2025/12/31
Desc: HTTP 请求工具函数
"""
import random
import time
from typing import Dict, Tuple
import requests
from requests.adapters import HTTPAdapter
def request_with_retry(
url: str,
params: Dict = None,
timeout: int = 15,
max_retries: int = 3,
base_delay: float = 1.0,
random_delay_range: Tuple[float, float] = (0.5, 1.5),
) -> requests.Response:
"""
带重试机制的 HTTP GET 请求
:param url: 请求 URL
:type url: str
:param params: 请求参数
:type params: dict
:param timeout: 超时时间(秒)
:type timeout: int
:param max_retries: 最大重试次数
:type max_retries: int
:param base_delay: 基础延迟时间(秒),用于指数退避
:type base_delay: float
:param random_delay_range: 随机延迟范围(秒)
:type random_delay_range: tuple
:return: Response 对象
:rtype: requests.Response
:raises: 最后一次请求的异常
"""
last_exception = None
for attempt in range(max_retries):
try:
# 每次请求创建新的 Session,避免复用连接
with requests.Session() as session:
# 禁用连接池复用
adapter = HTTPAdapter(pool_connections=1, pool_maxsize=1)
session.mount("http://", adapter)
session.mount("https://", adapter)
response = session.get(url, params=params, timeout=timeout)
response.raise_for_status()
return response
except (requests.RequestException, ValueError) as e:
last_exception = e
if attempt < max_retries - 1:
# 指数退避 + 随机抖动
delay = base_delay * (2**attempt) + random.uniform(*random_delay_range)
time.sleep(delay)
raise last_exception
@@ -0,0 +1,34 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
Date: 2020/2/13 21:22
Desc: 存储和读取 Token 文件
"""
import os
import pandas as pd
from akshare.pro import cons
def set_token(token):
df = pd.DataFrame([token], columns=["token"])
user_home = os.path.expanduser("~")
fp = os.path.join(user_home, cons.TOKEN_F_P)
df.to_csv(fp, index=False)
def get_token():
user_home = os.path.expanduser("~")
fp = os.path.join(user_home, cons.TOKEN_F_P)
if os.path.exists(fp):
df = pd.read_csv(fp)
return str(df.iloc[0]["token"])
else:
print(cons.TOKEN_ERR_MSG)
return
if __name__ == "__main__":
pass
@@ -0,0 +1,27 @@
def get_tqdm(enable: bool = True):
"""
返回适用于当前环境的 tqdm 对象。
Args:
enable (bool): 是否启用进度条。默认为 True。
Returns:
tqdm 对象。
"""
if not enable:
# 如果进度条被禁用,返回一个不显示进度条的 tqdm 对象
return lambda iterable, *args, **kwargs: iterable
try:
# 尝试检查是否在 jupyter notebook 环境中,有利于退出进度条
# noinspection PyUnresolvedReferences
shell = get_ipython().__class__.__name__
if shell == "ZMQInteractiveShell":
from tqdm.notebook import tqdm
else:
from tqdm import tqdm
except (NameError, ImportError):
# 如果不在 Jupyter 环境中,就使用标准 tqdm
from tqdm import tqdm
return tqdm