├── .github
└── workflows
│ ├── build.yaml
│ └── package.yaml
├── .gitignore
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── README.md
├── eastmoneypy
├── __init__.py
├── api.py
├── config.json
├── group.json
└── stocks.json
├── init_env.sh
├── key.png
├── requirements.txt
├── setup.py
└── tests
├── __init__.py
└── test_api.py
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | python-version: [3.8, 3.9]
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v2
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Cache pip
20 | uses: actions/cache@v2
21 | with:
22 | # This path is specific to Ubuntu
23 | path: ~/.cache/pip
24 | # Look to see if there is a cache hit for the corresponding requirements file
25 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
26 | restore-keys: |
27 | ${{ runner.os }}-pip-
28 | ${{ runner.os }}-
29 | - name: Install dependencies
30 | run: |
31 | python -m pip install --upgrade pip
32 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
33 | - name: Test with pytest
34 | run: |
35 | pip install pytest
36 | pip install pytest-cov
37 | pytest ./tests --cov-config=.coveragerc --cov-report term
--------------------------------------------------------------------------------
/.github/workflows/package.yaml:
--------------------------------------------------------------------------------
1 | name: package
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | deploy:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Set up Python
15 | uses: actions/setup-python@v2
16 | with:
17 | python-version: '3.x'
18 | - name: Install dependencies
19 | run: |
20 | python -m pip install --upgrade pip
21 | pip install build
22 | - name: Build package
23 | run: python -m build
24 | - name: Publish package
25 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
26 | with:
27 | user: __token__
28 | password: ${{ secrets.PYPI }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | #idea
10 | .idea
11 | # Distribution / packaging
12 | .Python
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *,cover
48 | .hypothesis/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # IPython Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # dotenv
81 | .env
82 |
83 | # virtualenv
84 | venv/
85 | ENV/
86 |
87 | # Spyder project settings
88 | .spyderproject
89 |
90 | # Rope project settings
91 | .ropeproject
92 |
93 | /tick
94 | /full
95 | /data/
96 | /ui/
97 | # datasample
98 | data.tar.gz
99 | /ve/
100 | .pytest_cache/
101 |
102 | zen/
103 | eastmoney/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | cache: pip
3 | python:
4 | - '3.6'
5 | install:
6 | - git fetch --tags --depth=500
7 | - pip install 'pytest>=3.6' --force-reinstall
8 | - pip install pytest-cov codecov requests
9 | - pip install -r ./requirements.txt
10 | script:
11 | - pytest tests/ --cov-report term --cov=./eastmoneypy
12 | after_success:
13 | - codecov
14 | deploy:
15 | provider: pypi
16 | user: foolcage
17 | password:
18 | secure: kfoWW+yWijQPls4rLWJc0mzIjg+jazJFq8wtCiUiNAsejyubTGm44T4H+1S8YGBGe1atU/GUV/o3WaNxfzEHdJCT19DcgcA+jY97fqJE4DciexjI7bfOnLLch1xzfRkgReJJD5urxXP6Mg8WAf3lqpDZEbhWmuDuozWvCnUhUyZ/zEbXxEKPLNyGTvL2YErkPXi4mkBTDik/kLiZTUTbgaDV0NWUR1vIbN8D6SeGAu3yR0zmJ792HpA1ZNmRP0ICq9/IKjtq3RKorQtdXXwX31/jIglIoVHH/u3m6u5TcH090ZD1drhG73f1sDUbawa07rGnRx8hWUbaddzvOwcsafTKL1WFmJhw7X+kWbnY+UwVZocfsunVD/I49qxrPwhKiIBl/ZVbhEofDfNHknNh1ZwGuFOsw0+5doHV1OhoQzN/0iA0xMmjVv9GrYs/ALkPRzNpbK8zkTK8dHuXouBT9s6Qz+QUvumi/ZQPw5tJtw33o74Fy2wtFyqDMTMMXKLbkHNcruDDEkQ4Ti5DYIL6uZJHs+cFINYRTRSzA98/dcTgdNkWZXiw8vc5Fj4ZRx9wzKTlfN+M2xxtv0tkq9unfuHOxTLSyv7r+oogjZukIG3Nd4HvNjOo+vXL+wD6tS06TXJDqXffKcqMrM94sSUWvc9wf5VdoZj7fI6QScsyYEI=
19 | on:
20 | tags: true
21 | notifications:
22 | email:
23 | recipients:
24 | - 5533061@qq.com
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 zvtvz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include LICENSE
3 | include eastmoneypy/config.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://pypi.org/project/eastmoneypy/)
2 | [](https://pypi.org/project/eastmoneypy/)
3 | [](https://pypi.org/project/eastmoneypy/)
4 | [](https://github.com/zvtvz/eastmoneypy/actions/workflows/build.yml)
5 | [](https://github.com/zvtvz/eastmoneypy/actions/workflows/package.yaml)
6 |
7 | eastmoneypy是使用python来对[东方财富](http://www.eastmoney.com/)进行操作的一个库。
8 | 目前在[zvt](https://github.com/zvtvz/zvt)中用于选股后自动添加到东财app,可参考该[issue](https://github.com/zvtvz/zvt/issues/48)
9 | ## 实现功能:
10 | - [x] 管理组合
11 | - [x] 添加A股标的到组合
12 | - [x] 添加板块到组合
13 | - [x] 添加港股
14 | - [x] 添加美股
15 | - [ ] 添加ETF
16 |
17 |
18 | ## 安装
19 | ```
20 | pip3 install -U eastmoneypy
21 | ```
22 |
23 | ## 设置
24 |
25 | 打开网址并登录:http://quote.eastmoney.com/zixuan/
26 |
27 |

28 |
29 | 在用户目录里面,找到eastmoney-home/config.json,设置header和appkey
30 | ```
31 | {
32 | "header": "parse your total header here",
33 | "appkey": "parse your appkey here"
34 | }
35 | ```
36 |
37 | ## 使用
38 |
39 | ### 获取自选组合
40 | ```
41 | In [1]: from eastmoneypy import *
42 | In [2]: get_groups()
43 | Out[2]:
44 | [{'id': '130357503', 'name': '自选股', 'version': '322', 'source': 'web'},
45 | {'id': '348275488', 'name': 'inging', 'version': '17', 'source': 'web'},
46 | {'id': '215892391', 'name': '持仓', 'version': '118', 'source': 'mobile'},
47 | {'id': '327237386', 'name': '港股', 'version': '6', 'source': 'mobile'},
48 | {'id': '235046679', 'name': '刘世军', 'version': '10', 'source': 'mobile'},
49 | {'id': '327744616', 'name': 'etf', 'version': '22', 'source': 'mobile'},
50 | {'id': '350053618', 'name': 'tech', 'version': '0', 'source': 'web'},
51 | {'id': '350485893', 'name': '你好', 'version': '0', 'source': 'web'},
52 | {'id': '130357521', 'name': '持仓股', 'version': '1', 'source': 'mobile'}]
53 | ```
54 |
55 | ### 创建组合
56 | ```
57 | In [3]: create_group('tmp')
58 | Out[3]: (True, {'gid': '350518464', 'msg': '添加组合成功'})
59 | ```
60 |
61 | ### 添加股票到组合
62 | ```
63 | >>> add_to_group('000999', group_name='tmp')
64 | >>> add_to_group('BK1003', group_name='概念',entity_type='block')
65 | >>> add_to_group('MSFT', group_name='tmp', entity_type='stockus')
66 | >>> add_to_group('00700', group_name='tmp' entity_type='stockhk')
67 | ```
68 |
69 | ### 删除组合
70 | ```
71 | In [5]: del_group('tmp')
72 | ```
73 |
74 | ## 联系方式
75 |
76 | 个人微信:foolcage 添加暗号:zvt
77 |
78 |
79 | ------
80 | 微信公众号:
81 |
82 |
83 | 知乎专栏:
84 | https://zhuanlan.zhihu.com/automoney
85 |
--------------------------------------------------------------------------------
/eastmoneypy/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import json
3 | import logging
4 | import os
5 | from logging.handlers import RotatingFileHandler
6 |
7 | from pathlib import Path
8 |
9 | EASTMONEY_HOME = os.environ.get('EASTMONEY_HOME')
10 | if not EASTMONEY_HOME:
11 | EASTMONEY_HOME = os.path.abspath(os.path.join(Path.home(), 'eastmoneypy-home'))
12 |
13 |
14 | def init_log(file_name='eastmoneypy.log', log_dir=None, simple_formatter=True):
15 | if not log_dir:
16 | log_dir = my_env['log_path']
17 |
18 | root_logger = logging.getLogger()
19 |
20 | # reset the handlers
21 | root_logger.handlers = []
22 |
23 | root_logger.setLevel(logging.INFO)
24 |
25 | file_name = os.path.join(log_dir, file_name)
26 |
27 | fh = RotatingFileHandler(file_name, maxBytes=524288000, backupCount=10)
28 |
29 | fh.setLevel(logging.INFO)
30 |
31 | ch = logging.StreamHandler()
32 | ch.setLevel(logging.INFO)
33 |
34 | # create formatter and add it to the handlers
35 | if simple_formatter:
36 | formatter = logging.Formatter(
37 | "%(asctime)s %(levelname)s %(threadName)s %(message)s")
38 | else:
39 | formatter = logging.Formatter(
40 | "%(asctime)s %(levelname)s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(funcName)s %(message)s")
41 | fh.setFormatter(formatter)
42 | ch.setFormatter(formatter)
43 |
44 | # add the handlers to the logger
45 | root_logger.addHandler(fh)
46 | root_logger.addHandler(ch)
47 |
48 |
49 | my_env = {}
50 |
51 |
52 | def init_env(EASTMONEY_HOME: str) -> None:
53 | """
54 |
55 | :param EASTMONEY_HOME: home path for this lib
56 | """
57 | if not os.path.exists(EASTMONEY_HOME):
58 | os.makedirs(EASTMONEY_HOME)
59 |
60 | my_env['EASTMONEY_HOME'] = EASTMONEY_HOME
61 |
62 | # path for storing logs
63 | my_env['log_path'] = os.path.join(EASTMONEY_HOME, 'logs')
64 | if not os.path.exists(my_env['log_path']):
65 | os.makedirs(my_env['log_path'])
66 |
67 | # create default config.json if not exist
68 | config_path = os.path.join(EASTMONEY_HOME, 'config.json')
69 | if not os.path.exists(config_path):
70 | from shutil import copyfile
71 | copyfile(os.path.abspath(os.path.join(os.path.dirname(__file__), 'config.json')), config_path)
72 |
73 | with open(config_path) as f:
74 | config_json = json.load(f)
75 | for k in config_json:
76 | my_env[k] = config_json[k]
77 |
78 | init_log()
79 |
80 | import pprint
81 | pprint.pprint(my_env)
82 |
83 |
84 | init_env(EASTMONEY_HOME=EASTMONEY_HOME)
85 |
86 | from .api import *
87 |
--------------------------------------------------------------------------------
/eastmoneypy/api.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import time
3 |
4 | import demjson3
5 | import requests
6 | from requests import Response, Session
7 |
8 | from eastmoneypy import my_env
9 |
10 | logger = logging.getLogger(__name__)
11 |
12 |
13 | def current_timestamp():
14 | return int(time.time() * 1000)
15 |
16 |
17 | def chrome_copy_header_to_dict(src):
18 | lines = src.split("\n")
19 | header = {}
20 | if lines:
21 | for line in lines:
22 | try:
23 | index = line.index(":")
24 | key = line[:index]
25 | value = line[index + 1:]
26 | if key and value:
27 | header.setdefault(key.strip(), value.strip())
28 | except Exception:
29 | pass
30 | return header
31 |
32 |
33 | HEADER = chrome_copy_header_to_dict(my_env["header"])
34 |
35 | APIKEY = my_env["appkey"]
36 |
37 |
38 | def parse_resp(resp: Response, key=None):
39 | if resp.status_code != 200:
40 | raise Exception(f"code:{resp.status_code},msg:{resp.content}")
41 | # {
42 | # "re": true,
43 | # "message": "",
44 | # "result": {}
45 | # }
46 | result = resp.text
47 | js_text = result[result.index("(") + 1: result.index(")")]
48 |
49 | ret = demjson3.decode(js_text)
50 | logger.info(f"ret:{ret}")
51 | data = ret.get("data")
52 | if data and key:
53 | result_value = data.get(key)
54 | else:
55 | result_value = data
56 |
57 | resp.close()
58 | return ret["state"], result_value
59 |
60 |
61 | def create_group(group_name, session: Session = None, api_key: str = APIKEY,
62 | headers=None):
63 | if headers is None:
64 | headers = HEADER
65 | ts = current_timestamp()
66 | url = f"http://myfavor.eastmoney.com/v4/webouter/ag?appkey={api_key}&cb=jQuery112404771026622113468_{ts - 10}&gn={group_name}&_={ts}"
67 |
68 | if session:
69 | resp = session.get(url, headers=headers)
70 | else:
71 | resp = requests.get(url, headers=headers)
72 |
73 | _, group = parse_resp(resp)
74 | return group
75 |
76 |
77 | def get_groups(session: Session = None, api_key: str = APIKEY,
78 | headers=None):
79 | if headers is None:
80 | headers = HEADER
81 | ts = current_timestamp()
82 | url = f"http://myfavor.eastmoney.com/v4/webouter/ggdefstkindexinfos?appkey={api_key}&cb=jQuery112407703233916827181_{ts - 10}&g=1&_={ts}"
83 |
84 | if session:
85 | resp = session.get(url, headers=headers)
86 | else:
87 | resp = requests.get(url, headers=headers)
88 |
89 | _, value = parse_resp(resp, key="ginfolist")
90 | return value
91 |
92 |
93 | def rename_group(group_id, group_name, session: Session = None, api_key: str = APIKEY,
94 | headers=None):
95 | if headers is None:
96 | headers = HEADER
97 | ts = current_timestamp()
98 | url = f"http://myfavor.eastmoney.com/v4/webouter/mg?appkey={api_key}&cb=jQuery112406922055532444666_{ts - 10}&g={group_id}&gn={group_name}&_={ts}"
99 |
100 | if session:
101 | resp = session.get(url, headers=headers)
102 | else:
103 | resp = requests.get(url, headers=headers)
104 |
105 | ret, _ = parse_resp(resp)
106 | return ret
107 |
108 |
109 | def del_group(group_name=None, group_id=None, session: Session = None, api_key: str = APIKEY,
110 | headers=None):
111 | if headers is None:
112 | headers = HEADER
113 | if not group_id:
114 | assert group_name is not None
115 | group_id = get_group_id(group_name, session=session, api_key=api_key, headers=headers)
116 | if not group_id:
117 | raise Exception(f"could not find group:{group_name}")
118 |
119 | ts = current_timestamp()
120 | url = f"http://myfavor.eastmoney.com/v4/webouter/dg?appkey={api_key}&cb=jQuery1124005355240135242356_{ts - 10}&g={group_id}&_={ts}"
121 |
122 | if session:
123 | resp = session.get(url, headers=headers)
124 | else:
125 | resp = requests.get(url, headers=headers)
126 |
127 | ret, _ = parse_resp(resp, key=None)
128 | return ret
129 |
130 |
131 | def get_group_id(group_name, session=None, api_key: str = APIKEY,
132 | headers=None):
133 | if headers is None:
134 | headers = HEADER
135 | groups = get_groups(session=session, api_key=api_key, headers=headers)
136 | groups = [group for group in groups if group["gname"] == group_name]
137 | if groups:
138 | return groups[0]["gid"]
139 | return None
140 |
141 |
142 | def list_entities(group_name=None, group_id=None, session: Session = None, api_key: str = APIKEY,
143 | headers=None):
144 | if headers is None:
145 | headers = HEADER
146 | if not group_id:
147 | assert group_name is not None
148 | group_id = get_group_id(group_name, session=session, api_key=api_key, headers=headers)
149 | if not group_id:
150 | raise Exception(f"could not find group:{group_name}")
151 |
152 | ts = current_timestamp()
153 | url = f"https://myfavor.eastmoney.com/v4/webouter/gstkinfos?appkey={api_key}&cb=jQuery112404771026622113468_{ts - 10}&g={group_id}&_={ts}"
154 |
155 | if session:
156 | resp = session.get(url, headers=headers)
157 | else:
158 | resp = requests.get(url, headers=headers)
159 |
160 | _, result = parse_resp(resp)
161 | datas = result["stkinfolist"]
162 | return [data["security"].split("$")[1] for data in datas]
163 |
164 |
165 | def add_to_group(
166 | code, entity_type="stock", group_name=None, group_id=None, session: Session = None, api_key: str = APIKEY,
167 | headers=None
168 | ):
169 | if headers is None:
170 | headers = HEADER
171 | if not group_id:
172 | assert group_name is not None
173 | group_id = get_group_id(group_name, session=session, api_key=api_key, headers=headers)
174 | if not group_id:
175 | raise Exception(f"could not find group:{group_name}")
176 |
177 | code = to_eastmoney_code(code, entity_type=entity_type)
178 | ts = current_timestamp()
179 | url = f"http://myfavor.eastmoney.com/v4/webouter/as?appkey={api_key}&cb=jQuery112404771026622113468_{ts - 10}&g={group_id}&sc={code}&_={ts}"
180 |
181 | if session:
182 | resp = session.get(url, headers=headers)
183 | else:
184 | resp = requests.get(url, headers=headers)
185 |
186 | return parse_resp(resp)
187 |
188 |
189 | def to_eastmoney_code(code, entity_type="stock"):
190 | if entity_type == "stock":
191 | code_ = int(code)
192 | # 上海
193 | if 600000 <= code_ <= 800000:
194 | return f"1%24{code}"
195 | else:
196 | return f"0%24{code}"
197 | if entity_type == "block":
198 | return f"90${code}"
199 | if entity_type == "stockhk":
200 | return f"116%24{code}"
201 | if entity_type == "stockus":
202 | return f"105%24{code}"
203 | assert False
204 |
205 |
206 | __all__ = [
207 | "create_group",
208 | "get_groups",
209 | "rename_group",
210 | "del_group",
211 | "get_group_id",
212 | "add_to_group",
213 | "list_entities",
214 | "to_eastmoney_code",
215 | ]
216 |
217 | if __name__ == "__main__":
218 | print(get_groups())
219 | create_group("大局")
220 | print(add_to_group("MSFT", group_name="大局", entity_type="stockus"))
221 | print(list_entities(group_name="大局"))
222 | print(get_group_id(group_name="大局"))
223 | del_group("大局")
224 | # find the header in chrome
225 | headers = chrome_copy_header_to_dict('''
226 | Accept: */*
227 | Accept-Encoding: gzip, deflate, br, zstd
228 | Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7
229 | Connection: keep-alive
230 | sec-ch-ua-platform: "macOS"
231 | ''')
232 | print(get_groups(headers=headers))
233 | create_group("大局", headers=headers)
234 | print(add_to_group("MSFT", group_name="大局", entity_type="stockus", headers=headers))
235 | print(list_entities(group_name="大局", headers=headers))
236 | print(get_group_id(group_name="大局", headers=headers))
237 | del_group("大局", headers=headers)
238 |
--------------------------------------------------------------------------------
/eastmoneypy/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "header": "",
3 | "appkey":""
4 | }
--------------------------------------------------------------------------------
/eastmoneypy/group.json:
--------------------------------------------------------------------------------
1 | {
2 | "state": 0,
3 | "message": "成功",
4 | "data": {
5 | "ginfolist": [
6 | {
7 | "gid": "1",
8 | "gname": "自选股",
9 | "fromclient": "web",
10 | "ver": 772
11 | },
12 | {
13 | "gid": "4",
14 | "gname": "etf",
15 | "fromclient": "app",
16 | "ver": 65
17 | },
18 | {
19 | "gid": "153",
20 | "gname": "可能",
21 | "fromclient": "app",
22 | "ver": 17
23 | },
24 | {
25 | "gid": "156",
26 | "gname": "test",
27 | "fromclient": "web",
28 | "ver": 0
29 | }
30 | ],
31 | "defstkinfolist": [
32 | {
33 | "security": "0$000778$24729626186070",
34 | "star": false,
35 | "updatetime": 20210201175419,
36 | "price": "3.6"
37 | },
38 | {
39 | "security": "0$000338$43817606988714",
40 | "star": false,
41 | "updatetime": 20210201175355,
42 | "price": "21.88"
43 | },
44 | {
45 | "security": "0$300315$27044766998602",
46 | "star": false,
47 | "updatetime": 20210128074740,
48 | "price": "5.67"
49 | },
50 | {
51 | "security": "0$002959$32868148433274",
52 | "star": false,
53 | "updatetime": 20210127192916,
54 | "price": "105.1"
55 | },
56 | {
57 | "security": "0$002572$28459115792694",
58 | "star": false,
59 | "updatetime": 20210126133232,
60 | "price": "27.89"
61 | },
62 | {
63 | "security": "116$02628$36045654226909",
64 | "star": false,
65 | "updatetime": 20210126111941,
66 | "price": "17.1"
67 | },
68 | {
69 | "security": "116$01810$41461401400001",
70 | "star": false,
71 | "updatetime": 20210126101439,
72 | "price": "31.1"
73 | },
74 | {
75 | "security": "0$002594$29473419956854",
76 | "star": false,
77 | "updatetime": 20210126083948,
78 | "price": "260"
79 | },
80 | {
81 | "security": "0$002475$34574656580054",
82 | "star": false,
83 | "updatetime": 20210126083859,
84 | "price": "55.21"
85 | },
86 | {
87 | "security": "0$300676$44479835609750",
88 | "star": false,
89 | "updatetime": 20210126081214,
90 | "price": "169.33"
91 | },
92 | {
93 | "security": "0$002027$11807076919078",
94 | "star": false,
95 | "updatetime": 20210126080425,
96 | "price": "12.18"
97 | },
98 | {
99 | "security": "116$00358$35920462227709",
100 | "star": false,
101 | "updatetime": 20210126074424,
102 | "price": "14.46"
103 | },
104 | {
105 | "security": "0$000895$34562768567722",
106 | "star": false,
107 | "updatetime": 20210126074141,
108 | "price": "50.35"
109 | },
110 | {
111 | "security": "0$000930$13899667630390",
112 | "star": false,
113 | "updatetime": 20210126072828,
114 | "price": "10.11"
115 | }
116 | ],
117 | "webindex": ""
118 | }
119 | }
--------------------------------------------------------------------------------
/eastmoneypy/stocks.json:
--------------------------------------------------------------------------------
1 | {
2 | "state": 0,
3 | "message": "成功",
4 | "data": {
5 | "stkinfolist": [
6 | {
7 | "security": "0$000778$24729626186070",
8 | "star": false,
9 | "updatetime": 20210201175419,
10 | "price": "3.6"
11 | },
12 | {
13 | "security": "0$000338$43817606988714",
14 | "star": false,
15 | "updatetime": 20210201175355,
16 | "price": "21.88"
17 | },
18 | {
19 | "security": "0$300315$27044766998602",
20 | "star": false,
21 | "updatetime": 20210128074740,
22 | "price": "5.67"
23 | },
24 | {
25 | "security": "0$002959$32868148433274",
26 | "star": false,
27 | "updatetime": 20210127192916,
28 | "price": "105.1"
29 | },
30 | {
31 | "security": "0$002572$28459115792694",
32 | "star": false,
33 | "updatetime": 20210126133232,
34 | "price": "27.89"
35 | },
36 | {
37 | "security": "116$02628$36045654226909",
38 | "star": false,
39 | "updatetime": 20210126111941,
40 | "price": "17.1"
41 | },
42 | {
43 | "security": "116$01810$41461401400001",
44 | "star": false,
45 | "updatetime": 20210126101439,
46 | "price": "31.1"
47 | },
48 | {
49 | "security": "0$002594$29473419956854",
50 | "star": false,
51 | "updatetime": 20210126083948,
52 | "price": "260"
53 | },
54 | {
55 | "security": "0$002475$34574656580054",
56 | "star": false,
57 | "updatetime": 20210126083859,
58 | "price": "55.21"
59 | },
60 | {
61 | "security": "0$300676$44479835609750",
62 | "star": false,
63 | "updatetime": 20210126081214,
64 | "price": "169.33"
65 | },
66 | {
67 | "security": "0$002027$11807076919078",
68 | "star": false,
69 | "updatetime": 20210126080425,
70 | "price": "12.18"
71 | },
72 | {
73 | "security": "116$00358$35920462227709",
74 | "star": false,
75 | "updatetime": 20210126074424,
76 | "price": "14.46"
77 | },
78 | {
79 | "security": "0$000895$34562768567722",
80 | "star": false,
81 | "updatetime": 20210126074141,
82 | "price": "50.35"
83 | },
84 | {
85 | "security": "0$000930$13899667630390",
86 | "star": false,
87 | "updatetime": 20210126072828,
88 | "price": "10.11"
89 | }
90 | ]
91 | }
92 | }
--------------------------------------------------------------------------------
/init_env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | platform='unknown'
4 | unamestr=`uname`
5 | if [[ "$unamestr" == 'Linux' ]]; then
6 | platform='linux'
7 | elif [[ "$unamestr" == 'Darwin' ]]; then
8 | platform='mac'
9 | fi
10 |
11 | install_cmd='sudo apt-get install'
12 | if [[ "$platform" == 'mac' ]]; then
13 | install_cmd='brew install'
14 | fi
15 |
16 | BASEDIR=`dirname $0`
17 |
18 | if ! which python > /dev/null; then
19 | echo -e "Python3 not found! Install? (y/n) \c"
20 | read
21 | if [ "$REPLY" = "y" ]; then
22 | $install_cmd install python3
23 | fi
24 | fi
25 |
26 | #pip_opt=''
27 | pip_opt='-i http://pypi.douban.com/simple --trusted-host pypi.douban.com'
28 |
29 | if ! which virtualenv > /dev/null; then
30 | echo -e "virtualenv not found! Install? (y/n) \c"
31 | read
32 | if [ "$REPLY" = "y" ]; then
33 | pip3 install virtualenv $pip_opt
34 | fi
35 | fi
36 |
37 | if [ ! -d "$BASEDIR/ve" ]; then
38 | virtualenv -p python3 $BASEDIR/ve
39 | echo "Virtualenv created."
40 | fi
41 |
42 | source $BASEDIR/ve/bin/activate
43 | cd $BASEDIR
44 | export PYTHONPATH=$PYTHONPATH:.
45 |
46 | if [ ! -f "$BASEDIR/ve/updated" -o $BASEDIR/requirements.txt -nt $BASEDIR/ve/updated ]; then
47 | pip install -r $BASEDIR/requirements.txt $pip_opt
48 | touch $BASEDIR/ve/updated
49 | echo "Requirements installed."
50 | fi
51 |
52 | pip install ipython jupyterlab $pip_opt
53 |
54 | echo "env ok"
--------------------------------------------------------------------------------
/key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zvtvz/eastmoneypy/2f1ea0900d2af5ba1bf57cbb07718b5387f237dc/key.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests == 2.20.1
2 | demjson3 == 3.0.5
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # To use a consistent encoding
4 | from codecs import open
5 | from os import path
6 |
7 | # Always prefer setuptools over distutils
8 | from setuptools import setup, find_packages
9 |
10 | here = path.abspath(path.dirname(__file__))
11 |
12 | # Get the long description from the README file
13 | with open(path.join(here, 'README.md'), encoding='utf-8') as f:
14 | long_description = f.read()
15 |
16 | # Arguments marked as "Required" below must be included for upload to PyPI.
17 | # Fields marked as "Optional" may be commented out.
18 |
19 | setup(
20 | # This is the name of your project. The first time you publish this
21 | # package, this name will be registered for you. It will determine how
22 | # users can install this project, e.g.:
23 | #
24 | # $ pip install sampleproject
25 | #
26 | # And where it will live on PyPI: https://pypi.org/project/sampleproject/
27 | #
28 | # There are some restrictions on what makes a valid project name
29 | # specification here:
30 | # https://packaging.python.org/specifications/core-metadata/#name
31 | name='eastmoneypy', # Required
32 |
33 | # Versions should comply with PEP 440:
34 | # https://www.python.org/dev/peps/pep-0440/
35 | #
36 | # For a discussion on single-sourcing the version across setup.py and the
37 | # project code, see
38 | # https://packaging.python.org/en/latest/single_source_version.html
39 | version='0.1.9', # Required
40 |
41 | # This is a one-line description or tagline of what your project does. This
42 | # corresponds to the "Summary" metadata field:
43 | # https://packaging.python.org/specifications/core-metadata/#summary
44 | description='python lib for operating eastmoney', # Required
45 |
46 | # This is an optional longer description of your project that represents
47 | # the body of text which users will see when they visit PyPI.
48 | #
49 | # Often, this is the same as your README, so you can just read it in from
50 | # that file directly (as we have already done above)
51 | #
52 | # This field corresponds to the "Description" metadata field:
53 | # https://packaging.python.org/specifications/core-metadata/#description-optional
54 | long_description=long_description, # Optional
55 |
56 | # This should be a valid link to your project's main homepage.
57 | #
58 | # This field corresponds to the "Home-Page" metadata field:
59 | # https://packaging.python.org/specifications/core-metadata/#home-page-optional
60 | url='https://github.com/zvtvz/eastmoneypy', # Optional
61 |
62 | # This should be your name or the name of the organization which owns the
63 | # project.
64 | author='foolcage', # Optional
65 |
66 | # This should be a valid email address corresponding to the author listed
67 | # above.
68 | author_email='5533061@qq.com', # Optional
69 |
70 | # Classifiers help users find your project by categorizing it.
71 | #
72 | # For a list of valid classifiers, see
73 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers
74 | classifiers=[ # Optional
75 | # How mature is this project? Common values are
76 | # 3 - Alpha
77 | # 4 - Beta
78 | # 5 - Production/Stable
79 | 'Development Status :: 5 - Production/Stable',
80 |
81 | # Indicate who your project is intended for
82 | 'Intended Audience :: Developers',
83 | 'Intended Audience :: Customer Service',
84 | 'Intended Audience :: Education',
85 | 'Intended Audience :: Financial and Insurance Industry',
86 | 'Topic :: Software Development :: Build Tools',
87 | 'Topic :: Office/Business :: Financial :: Investment',
88 |
89 | # Pick your license as you wish
90 | 'License :: OSI Approved :: MIT License',
91 |
92 | # Specify the Python versions you support here. In particular, ensure
93 | # that you indicate whether you support Python 2, Python 3 or both.
94 | 'Programming Language :: Python :: 3.8',
95 | 'Programming Language :: Python :: 3.9',
96 | 'Programming Language :: Python :: 3.10',
97 | 'Programming Language :: Python :: 3.11',
98 | 'Programming Language :: Python :: 3.12'
99 | ],
100 |
101 | # This field adds keywords for your project which will appear on the
102 | # project page. What does your project relate to?
103 | #
104 | # Note that this is a string of words separated by whitespace, not a list.
105 | keywords='eastmoney python',
106 | # Optional
107 |
108 | # You can just specify package directories manually here if your project is
109 | # simple. Or you can use find_packages().
110 | #
111 | # Alternatively, if you just want to distribute a single Python file, use
112 | # the `py_modules` argument instead as follows, which will expect a file
113 | # called `my_module.py` to exist:
114 | #
115 | # py_modules=["my_module"],
116 | #
117 | packages=find_packages(exclude=['contrib', 'docs', 'tests', 've']), # Required
118 |
119 | # This field lists other packages that your project depends on to run.
120 | # Any package you put here will be installed by pip when your project is
121 | # installed, so they must be valid existing projects.
122 | #
123 | # For an analysis of "install_requires" vs pip's requirements files see:
124 | # https://packaging.python.org/en/latest/requirements.html
125 |
126 | install_requires=['requests>=2.20.1', 'demjson3>=3.0.5'],
127 | # Optional
128 |
129 | # List additional groups of dependencies here (e.g. development
130 | # dependencies). Users will be able to install these using the "extras"
131 | # syntax, for example:
132 | #
133 | # $ pip install sampleproject[dev]
134 | #
135 | # Similar to `install_requires` above, these must be valid existing
136 | # projects.
137 | # extras_require={ # Optional
138 | # 'dev': ['check-manifest'],
139 | # 'test': ['coverage'],
140 | # },
141 |
142 | # If there are data files included in your packages that need to be
143 | # installed, specify them here.
144 | #
145 | # If using Python 2.6 or earlier, then these have to be included in
146 | # MANIFEST.in as well.
147 | # package_data={ # Optional
148 | # 'sample': ['package_data.dat'],
149 | # },
150 |
151 | # Although 'package_data' is the preferred approach, in some case you may
152 | # need to place data files outside of your packages. See:
153 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files
154 | #
155 | # In this case, 'data_file' will be installed into '/my_data'
156 | # data_files=[('my_data', ['data/data_file'])], # Optional
157 |
158 | # To provide executable scripts, use entry points in preference to the
159 | # "scripts" keyword. Entry points provide cross-platform support and allow
160 | # `pip` to create the appropriate form of executable for the target
161 | # platform.
162 | #
163 | # For example, the following would provide a command called `sample` which
164 | # executes the function `main` from this package when invoked:
165 |
166 | # entry_points={ # Optional
167 | # },
168 |
169 | # List additional URLs that are relevant to your project as a dict.
170 | #
171 | # This field corresponds to the "Project-URL" metadata fields:
172 | # https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use
173 | #
174 | # Examples listed include a pattern for specifying where the package tracks
175 | # issues, where the source is hosted, where to say thanks to the package
176 | # maintainers, and where to support the project financially. The key is
177 | # what's used to render the link text on PyPI.
178 | project_urls={ # Optional
179 | 'Bug Reports': 'https://github.com/zvtvz/eastmoneypy/issues',
180 | 'Funding': 'https://www.foolcage.com',
181 | 'Say Thanks!': 'https://saythanks.io/to/foolcage',
182 | 'Source': 'https://github.com/zvtvz/eastmoneypy',
183 | },
184 |
185 | include_package_data=True,
186 | long_description_content_type="text/markdown",
187 | )
188 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | import sys
4 |
5 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
6 |
--------------------------------------------------------------------------------
/tests/test_api.py:
--------------------------------------------------------------------------------
1 | def test():
2 | assert True
3 |
--------------------------------------------------------------------------------