├── .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 | [![image](https://img.shields.io/pypi/v/eastmoneypy.svg)](https://pypi.org/project/eastmoneypy/) 2 | [![image](https://img.shields.io/pypi/l/eastmoneypy.svg)](https://pypi.org/project/eastmoneypy/) 3 | [![image](https://img.shields.io/pypi/pyversions/eastmoneypy.svg)](https://pypi.org/project/eastmoneypy/) 4 | [![build](https://github.com/zvtvz/eastmoneypy/actions/workflows/build.yaml/badge.svg)](https://github.com/zvtvz/eastmoneypy/actions/workflows/build.yml) 5 | [![package](https://github.com/zvtvz/eastmoneypy/actions/workflows/package.yaml/badge.svg)](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 | Wechat 78 | 79 | ------ 80 | 微信公众号: 81 | Wechat 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 | --------------------------------------------------------------------------------