akshare-proxy-patch 0.2.1__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of akshare-proxy-patch might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akshare-proxy-patch
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: 针对 AkShare 的底层补丁,自动为东财等接口注入代理认证头,从而避免 `stock_zh_a_spot_em` 等方法报错。
5
5
  Home-page: https://github.com/helloyie/akshare-proxy-patch
6
6
  Author: cheapproxy.net
@@ -0,0 +1,116 @@
1
+ import time
2
+ import threading
3
+ import requests
4
+ from requests.adapters import HTTPAdapter
5
+
6
+ __version__ = "0.2.3"
7
+ # 备份 Session 的原始 request 方法,这是所有 requests.get/post 的最终入口
8
+ _original_request = requests.Session.request
9
+ _auth_session = requests.Session()
10
+
11
+
12
+ class AuthCache:
13
+ def __init__(self):
14
+ self.data = None
15
+ self.expire_at = 0
16
+ self.lock = threading.Lock()
17
+ self.ttl = 20
18
+
19
+
20
+ _cache = AuthCache()
21
+
22
+
23
+ def get_auth_config_with_cache(auth_url, auth_token):
24
+ now = time.time()
25
+ # 1. 检查缓存是否有效
26
+ if _cache.data and now < _cache.expire_at:
27
+ return _cache.data
28
+
29
+ # 2. 缓存失效,加锁更新
30
+ with _cache.lock:
31
+ if _cache.data and now < _cache.expire_at:
32
+ return _cache.data
33
+
34
+ try:
35
+ resp = _auth_session.get(
36
+ auth_url,
37
+ params={"token": auth_token, "version": __version__},
38
+ timeout=5,
39
+ )
40
+ data = resp.json()
41
+ if data.get("ua"):
42
+ _cache.data = data
43
+ _cache.expire_at = now + _cache.ttl
44
+ return data
45
+ print(f"授权失败: {data.get('error_msg')}")
46
+ except Exception as e:
47
+ print(f"请求授权接口异常: {e}")
48
+
49
+ return _cache.data
50
+
51
+
52
+ def install_patch(auth_ip, auth_token):
53
+ def patched_request(self, method, url, **kwargs):
54
+ # 排除非目标域名
55
+ is_target = any(
56
+ d in (url or "")
57
+ for d in [
58
+ "fund.eastmoney.com",
59
+ "push2.eastmoney.com",
60
+ "push2his.eastmoney.com",
61
+ ]
62
+ )
63
+
64
+ if not is_target:
65
+ return _original_request(self, method, url, **kwargs)
66
+
67
+ auth_url = f"http://{auth_ip}:47001/api/akshare-auth"
68
+
69
+ # 重试逻辑
70
+ for _ in range(10):
71
+ auth_res = get_auth_config_with_cache(auth_url, auth_token)
72
+ if not auth_res:
73
+ time.sleep(0.5)
74
+ continue
75
+
76
+ # 处理 Headers:确保不破坏业务代码传入的 headers
77
+ headers = kwargs.get("headers") or {}
78
+ headers["User-Agent"] = auth_res["ua"]
79
+ headers["Cookie"] = (
80
+ f"nid18={auth_res['nid18']}; nid18_create_time={auth_res['nid18_create_time']}"
81
+ )
82
+ kwargs["headers"] = headers
83
+ kwargs["proxies"] = {
84
+ "http": auth_res["proxy"],
85
+ "https": auth_res["proxy"],
86
+ }
87
+
88
+ if "timeout" not in kwargs:
89
+ kwargs["timeout"] = 8
90
+
91
+ try:
92
+ # 调用原始 request 方法
93
+ resp = _original_request(self, method, url, **kwargs)
94
+ if resp.ok:
95
+ return resp
96
+
97
+ # 如果遇到 403 等可能是代理失效
98
+ if resp.status_code == 403:
99
+ with _cache.lock:
100
+ _cache.expire_at = 0
101
+ except Exception:
102
+ # 网络异常直接使缓存失效,强制下一个请求换 IP
103
+ with _cache.lock:
104
+ _cache.expire_at = 0
105
+ time.sleep(0.1)
106
+
107
+ # 最终尝试
108
+ return _original_request(self, method, url, **kwargs)
109
+
110
+ # 关键:全局替换 Session 的 request 入口
111
+ requests.Session.request = patched_request
112
+
113
+
114
+ # 使用示例
115
+ # install_patch("你的IP", "你的TOKEN")
116
+ # requests.get("http://fund.eastmoney.com/...")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akshare-proxy-patch
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: 针对 AkShare 的底层补丁,自动为东财等接口注入代理认证头,从而避免 `stock_zh_a_spot_em` 等方法报错。
5
5
  Home-page: https://github.com/helloyie/akshare-proxy-patch
6
6
  Author: cheapproxy.net
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "akshare-proxy-patch" # 包名,需在 PyPI 唯一
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  authors = [
9
9
  { name="cheapproxy", email="emezxyj1@gmail.com" },
10
10
  ]
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="akshare-proxy-patch", # 包名,用户 pip install 时的名称
8
- version="0.2.1", # 版本号
8
+ version="0.2.3", # 版本号
9
9
  author="cheapproxy.net",
10
10
  author_email="emezxyj1@gmail.com",
11
11
  description="AkShare 报错全局Hook",
@@ -1,93 +0,0 @@
1
- import time
2
- import threading
3
- import requests
4
- from copy import deepcopy
5
- from requests.adapters import HTTPAdapter
6
-
7
- # 备份原始 send
8
- _original_send = HTTPAdapter.send
9
-
10
- # 线程局部状态
11
- _thread_state = threading.local()
12
-
13
- # 独立 Session:专门用于授权请求(不走 hook)
14
- _auth_session = requests.Session()
15
-
16
-
17
- def get_auth_config_realtime(auth_url, auth_token):
18
- """实时获取授权信息(不走全局 hook)"""
19
- try:
20
- resp = _auth_session.get(
21
- auth_url,
22
- params={"token": auth_token},
23
- timeout=5,
24
- )
25
- data = resp.json()
26
- if data.get("ua"):
27
- return data
28
- error_msg = (
29
- data.get("error_msg")
30
- or "接口授权失败,请登录 https://cheapproxy.net 联系客服获取授权码"
31
- )
32
- print(f"========={error_msg}========")
33
- except Exception as e:
34
- print(f"=========接口授权失败,{e}========")
35
- return None
36
-
37
-
38
- def install_patch(auth_ip, auth_token):
39
- def patched_send(self, request, **kwargs):
40
- # 如果明确标记跳过 hook,直接原逻辑
41
- if getattr(_thread_state, "skip_hook", False):
42
- return _original_send(self, request, **kwargs)
43
-
44
- url = request.url or ""
45
-
46
- is_target = any(
47
- d in url
48
- for d in (
49
- "fund.eastmoney.com",
50
- "push2.eastmoney.com",
51
- "push2his.eastmoney.com",
52
- )
53
- )
54
-
55
- # 非目标域名直接放行
56
- if not is_target:
57
- return _original_send(self, request, **kwargs)
58
-
59
- auth_url = f"http://{auth_ip}:47001/api/akshare-auth"
60
-
61
- # 每次请求都用“独立副本”
62
- new_request = deepcopy(request)
63
- new_kwargs = deepcopy(kwargs)
64
-
65
- for _ in range(30):
66
- try:
67
- auth_res = get_auth_config_realtime(auth_url, auth_token)
68
- if auth_res:
69
- new_request.headers = deepcopy(request.headers)
70
- new_request.headers["User-Agent"] = auth_res["ua"]
71
- new_request.headers["Cookie"] = (
72
- f"nid18={auth_res['nid18']}; "
73
- f"nid18_create_time={auth_res['nid18_create_time']}"
74
- )
75
-
76
- proxy = auth_res["proxy"]
77
- new_kwargs["proxies"] = {
78
- "http": proxy,
79
- "https": proxy,
80
- }
81
- new_kwargs["timeout"] = 8
82
-
83
- resp = _original_send(self, new_request, **new_kwargs)
84
- if resp.ok:
85
- return resp
86
-
87
- except Exception:
88
- time.sleep(0.01)
89
-
90
- # 兜底:30 次都失败,走原请求
91
- return _original_send(self, request, **kwargs)
92
-
93
- HTTPAdapter.send = patched_send