# 2024/12/27 API
https://hackmd.io/kTmJqeYLSfCtruduc2eDQA
## Q & A
1. 老師可以再說一次restful Api嗎,謝謝 (X)
2. ac890831@gmail.com (X)
-  (X)
## 大綱
- 描述API基礎知識
- Yahoo finance API串接
- 基本API設計
## 需預先下載的軟體
- [Vscode](https://code.visualstudio.com/download)
- [Postman](https://www.postman.com/downloads/)
- Python3.10
- python-dotenv==1.0.1
- requests==2.31.0
- Flask-JWT-Extended==4.4.1
- Flask==2.1.2
- Flask-RESTful == 0.3.9
- PyMySQL==1.0.3
- Werkzeug==2.2.2
- MySQLDB server, 可以用自己習慣的方式下載, 也可以使用 [MAMP](https://www.mamp.info/en/downloads/) 來驅動
## 上課會用到網頁
- [Yahoo finace API](https://financeapi.net/dashboard)
- 請預先申請好一組 token 方便上課直接使用
- [ChatGPT](https://chat.openai.com/)
- [Fake API website](https://jsonplaceholder.typicode.com/)
- [小測驗](https://docs.google.com/forms/d/196mE1lY7K_99ai8cQoqbMJBilQk0ky_E4AtpfssmwI8/edit)
- [網頁提供的爬蟲規範](https://{origin}/robots.txt)
## 上半堂
串接 yahoo finance API 題目
```
import requests
url = "https://yfapi.net/v8/finance/spark"
querystring = {"symbols": "AAPL", "interval": "1d", "range": "1mo"}
headers = {"x-api-key": ""}
response = requests.request("GET", url, headers=headers, params=querystring)
results = response.json()
"""
透過"https://yfapi.net/v8/finance/spark"這隻API
取得AAPL, TSLA為期一個月且間格為一天的股價, 並整理成下方資料格式
{
"AAPL": {
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5"
},
"TSLA": {
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5"
}
}
"""
"""
Steps:
1. 將timestamp轉換成datetime(2021/12/21)
2. 將轉換過的timestamp_list 與close的price list合成上放的dict
3. 將股票代號設為key, value為整理過後的dict並return.
"""
if __name__ == "__main__":
print(response.json())
```
串接 yahoo finance API 解法
```
import requests
from datetime import datetime
def get_symbols_info(symbols: str) -> dict:
headers = {"x-api-key": "XeLhSFyv1X235xXISwu3q8mKTDYGqphI7Z8JOts9"}
url = "https://yfapi.net/v8/finance/spark"
querystring = {"symbols": symbols, "range": "5d", "interval": "1d"}
response = requests.request("GET", url, headers=headers, params=querystring)
data = response.json()
# Before
"""
data = {
'MSFT': {
'chartPreviousClose': 301.37,
'timestamp':
[1649424600, 1649683800, 1649770200, 1649856600, 1649943899],
'symbol': 'MSFT',
'previousClose': None,
'close': [296.97, 285.26, 282.06, 287.62, 286.05],
'end': None,
'start': None,
'dataGranularity': 300},
'AAPL':
{
'chartPreviousClose': 172.14,
'timestamp':
[1649424600, 1649683800, 1649770200, 1649856600, 1649943898],
'symbol': 'AAPL',
'previousClose': None,
'close':
[170.09, 165.75, 167.66, 170.4, 169.65],
'end': None,
'start': None,
'dataGranularity': 300
}
}
}
"""
# After
"""
{
"AAPL": {
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5"
},
"TSLA": {
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5"
}
}
"""
results = {}
for symbol in symbols.split(","):
# Convert timestamp to str datetime
timestamps = data[symbol]["timestamp"]
timestamps = [
datetime.fromtimestamp(timestamp).strftime("%Y/%m/%d")
for timestamp in timestamps
]
close_piece = data[symbol]["close"]
# Create dict with two list
symbol_info_mapping = dict(zip(timestamps, close_piece))
results[symbol] = symbol_info_mapping
return results
if __name__ == "__main__":
print(get_symbols_info("AAPL,MSFT"))
```
## 下半堂
在 window 中開啟一個 python 的虛擬環境
到官網下載 python 切記,把加入 path 的勾勾勾起來
```
1. pip3 install virtualenv
2. Set-ExecutionPolicy Unrestricted -Force
2.1 將 vscode 以系統管理員的身份執行(內容 -> 相容性)
3. virtualenv venv (創建 venv)
4. .\venv\Scripts\activate (進入 venv)
5. deactivate (退出 venv)
Reference
- https://stackoverflow.com/questions/18713086/virtualenv-wont-activate-on-windows
- https://takenoterecalllater.wordpress.com/2021/07/07/%E5%A6%82%E4%BD%95%E5%9C%A8-win10-%E8%AE%93-visual-studio-code-%E7%9A%84-terminal-%E4%BB%A5-%E7%B3%BB%E7%B5%B1%E7%AE%A1%E7%90%86%E5%93%A1%E8%BA%AB%E5%88%86%E5%9F%B7%E8%A1%8C/
```
api_basic/api.py
Flask 官網: https://flask.palletsprojects.com/en/stable/quickstart/
下載所有 requirements.txt 的 package`pip install -r requirements.txt`
```
from flask import Flask
import requests
from datetime import datetime
def get_symbols_info(symbols: str) -> dict:
headers = {"x-api-key": ""}
url = "https://yfapi.net/v8/finance/spark"
querystring = {"symbols": symbols, "range": "5d", "interval": "1d"}
response = requests.request("GET", url, headers=headers, params=querystring)
data = response.json()
# Before
"""
data = {
'MSFT': {
'chartPreviousClose': 301.37,
'timestamp':
[1649424600, 1649683800, 1649770200, 1649856600, 1649943899],
'symbol': 'MSFT',
'previousClose': None,
'close': [296.97, 285.26, 282.06, 287.62, 286.05],
'end': None,
'start': None,
'dataGranularity': 300},
'AAPL':
{
'chartPreviousClose': 172.14,
'timestamp':
[1649424600, 1649683800, 1649770200, 1649856600, 1649943898],
'symbol': 'AAPL',
'previousClose': None,
'close':
[170.09, 165.75, 167.66, 170.4, 169.65],
'end': None,
'start': None,
'dataGranularity': 300
}
}
}
"""
# After
"""
{
"AAPL": {
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5"
},
"TSLA": {
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5"
}
}
"""
results = {}
for symbol in symbols.split(","):
# Convert timestamp to str datetime
timestamps = data[symbol]["timestamp"]
timestamps = [
datetime.fromtimestamp(timestamp).strftime("%Y/%m/%d")
for timestamp in timestamps
]
close_piece = data[symbol]["close"]
# Create dict with two list
symbol_info_mapping = dict(zip(timestamps, close_piece))
results[symbol] = symbol_info_mapping
return results
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
@app.route("/stock")
def stock():
return get_symbols_info("AAPL,MSFT")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=10009, use_reloader=True)
```
api_rest_basic/api.py
解析帶入參數的方式: https://flask-restful.readthedocs.io/en/latest/quickstart.html#a-minimal-api

定義從哪裡取得參數: https://flask-restful.readthedocs.io/en/latest/reqparse.html

資料夾路徑參考

api_rest_basic/api.py
```
from flask import Flask
from flask_restful import Api
from resource.stock import Stock
app = Flask(__name__)
api = Api(app)
api.add_resource(Stock, "/stock")
if __name__ == "__main__":
app.run(host="0.0.0.0", port="10009", debug=True, use_reloader=True)
```
api_rest_basic/resource/stock.py
記得加上自己的 x-api-key
```
import requests
from datetime import datetime
from flask_restful import Resource
from flask_restful import reqparse
def get_symbols_info(symbols: str) -> dict:
headers = {"x-api-key": ""}
url = "https://yfapi.net/v8/finance/spark"
querystring = {"symbols": symbols, "range": "5d", "interval": "1d"}
response = requests.request("GET", url, headers=headers, params=querystring)
data = response.json()
# Before
"""
data = {
'MSFT': {
'chartPreviousClose': 301.37,
'timestamp':
[1649424600, 1649683800, 1649770200, 1649856600, 1649943899],
'symbol': 'MSFT',
'previousClose': None,
'close': [296.97, 285.26, 282.06, 287.62, 286.05],
'end': None,
'start': None,
'dataGranularity': 300},
'AAPL':
{
'chartPreviousClose': 172.14,
'timestamp':
[1649424600, 1649683800, 1649770200, 1649856600, 1649943898],
'symbol': 'AAPL',
'previousClose': None,
'close':
[170.09, 165.75, 167.66, 170.4, 169.65],
'end': None,
'start': None,
'dataGranularity': 300
}
}
}
"""
# After
"""
{
"AAPL": {
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5"
},
"TSLA": {
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5",
"2021/12/21": "216.5"
}
}
"""
results = {}
for symbol in symbols.split(","):
# Convert timestamp to str datetime
timestamps = data[symbol]["timestamp"]
timestamps = [
datetime.fromtimestamp(timestamp).strftime("%Y/%m/%d")
for timestamp in timestamps
]
close_piece = data[symbol]["close"]
# Create dict with two list
symbol_info_mapping = dict(zip(timestamps, close_piece))
results[symbol] = symbol_info_mapping
return results
class Stock(Resource):
def get(self):
parser = reqparse.RequestParser()
parser.add_argument(
"symbols",
type=str,
location="args",
)
args = parser.parse_args()
symbols = args.get("symbols")
return get_symbols_info(symbols)
```
結果呈現

---
接下來的練習
創建一個新的 API /user
可以透過 POST 的方式去新增一筆資料進去
### Step1.
建立 DB

長度隨意,不要太短就好
table(user)
- id: (AUTO_INCREMENT)
- name: str
- gender: str
- birth: str
- note: str
### DB 連線程式碼參考
與 MySQL DB 建立連線
```
import pymysql
def db_init():
db = pymysql.connect(
host="127.0.0.1", user="root", password="root", port=8889, db="api_class"
)
cursor = db.cursor(pymysql.cursors.DictCursor)
return db, cursor
```
新增資料 SQL 與語法參考
```
INSERT INTO `api_class`.`user` (`name`,`gender`,`birth`,`note`)
VALUES ('{}','{}','{}','{}');
```
POST parser.add_argument 的 location

POST /user API 程式碼參考
```
db, cursor = db_init()
sql = """
INSERT INTO `api_class`.`user` (`name`,`gender`,`birth`,`note`)
VALUES ('{}','{}','{}','{}');
""".format(
user["name"], user["gender"], user["birth"], user["note"]
)
result = cursor.execute(sql)
message = "success" if result == 1 else "failure"
db.commit()
db.close()
```
GET /user API
```
from flask import jsonify
class Users(Resource):
def get(self):
db, cursor = db_init()
sql = "SELECT * FROM api_class.user;"
cursor.execute(sql)
users = cursor.fetchall()
db.close()
return jsonify(users)
```
api_rest_basic/resource/user.py
程式碼參考,請自行修正
```
import json
from flask import jsonify
from flask_restful import Resource, reqparse
import pymysql
def db_init():
db = pymysql.connect(
host="127.0.0.1", user="root", password="root", port=8889, db="api_class"
)
cursor = db.cursor(pymysql.cursors.DictCursor)
return db, cursor
class Users(Resource):
def get(self):
db, cursor = db_init()
sql = "SELECT * FROM api_class.user;"
cursor.execute(sql)
users = cursor.fetchall()
db.close()
return jsonify(users)
# Create User
def post(self):
db, cursor = db_init()
sql = """
INSERT INTO `api_class`.`user` (`name`,`gender`,`birth`,`note`)
VALUES ('{}','{}','{}','{}');
""".format(
user["name"], user["gender"], user["birth"], user["note"]
)
result = cursor.execute(sql)
message = "success" if result == 1 else "failure"
db.commit()
db.close()
return {"message": message}
```
Postman 驗證
新增

取得
