【实战项目】API数据传输

一. 项目简述

  向xxxxAPI定期传输指定数据。

 

二. 项目设计

  1. 基础功能:

  【功能 1】 提取数据

  描述 :连接数据库,执行SQL语句,读取所需数据,并将其另存为 .xlsx 文件。

  【功能 2】 登陆API

  描述 :录入基础参数,生成签名,上传指定参数,完成接口鉴权。

  【功能 3】 获取上传路径

  描述 :解析接口鉴权的返回参数, 加入上传文件名, 获取临时上传地址。

  【功能 4】上传文件

  描述 : 向临时上传地址上传文件,并获取该文件上传后的临时下载地址。

  2. 优化功能:

  【功能 1】 设定配置文件

  描述:为了减少代码修改,将配置信息存放于 .ini 文件,需要时读取。

  【功能 2】 设定日志

  描述:为了跟踪程序运行情况,在异常出现时能及时人工介入,针对主要步骤抛出异常,存储入日志,并将日志实时发送至手机端。

 

三. 类与功能模块

  0. 第三方库与全局变量

  描述:创建日志初始信息

  全局变量:日志字典,数据的所属日期,日志计数器

  第三方库及函数解析:

  (1)datetime

    datetime.datetime.now() 获得当前时间,返回值为 datetime.datetime

    datetime.timedelta() 返回时间增量,返回值为 datetime.timedelta

  (2)time

    time.time() 当前时间的时间戳(1970纪元后经过的浮点秒数),返回值为 float

    time.localtime() 格式化时间戳为本地时间,返回值为 time.struct_time

    time.strftime()  接收 time.struct_time型值,可自定义格式化,返回值为 str

import time
import struct
import hashlib
import requests
import random
import json
import pymssql
import pandas as pd
from configobj import ConfigObj
import datetime


log = {}
date = (datetime.datetime.now() + datetime.timedelta(days = -1)).strftime("%Y-%m-%d")
counter = 0
log["DDATE"] = date
log["STIME"] = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time()))

  

  1. 文件类

  描述:用于从数据库中提取数据,并生成文件

  类的实例变量:数据库域名,用户名,密码,指定数据库名,指定表名,格式化时间(用于生成文件名)

  功能模块:connect()

  第三方库及函数解析:

  (1)configobj 

    ConfigObj(filename, encoding=‘UTF8‘)  解析配置文件 .ini

  (2)pymssql

    conn = pymssql.connect(host, username, password, database) 连接数据库

    cur = conn.cursor() 创建游标

    cur.close() 关闭游标

    cur.execute(sql) 执行SQL语句

    conn.commit() 提交数据,若SQL语句为增删改时,需要提交数据使其生效

    result = cur.fetchall() 获取全部数据

  (3)pandas

    writer = pd.ExcelWriter(fname) 打开指定excel文件,若不存在则新建

    df = pd.DataFrame(result, index=None, columns=[]) 将数据整理成指定格式的数据框

    df.to_excel(writer, index=False, sheet_name=‘...‘) 将数据框写入excel文件的指定表中

    writer.save() 保存excel文件

class File(object):
def __init__(self): self.domain = ‘‘ self.username = ‘‘ self.password = ‘‘ self.database = ‘‘ self.table = ‘‘ self.now = time.strftime("%Y%m%d_%H",time.localtime(time.time())) def connect(self): # 读取配置信息 config = ConfigObj(config.ini, encoding=UTF8) self.domain = config[sql_server][domain] self.username = config[sql_server][username] self.password = config[sql_server][password] self.database = config[sql_server][database] self.table = config[sql_server][table] try: conn = pymssql.connect(self.domain, self.username, self.password, self.database) log["STEP"] = "6-1 成功" except pymssql.OperationalError: log["STEP"] = "6-1 数据库连接失败" # 设定日志计数器 cur1 = conn.cursor() count_sql = ‘‘‘ SELECT COUNT(*) FROM {table} WHERE DDATE = ‘{values}‘ ‘‘‘.format(table=self.table, values=date) cur1.execute(count_sql) global counter counter = cur1.fetchall()[0][0] + 1 log["COUNTER"] = counter cur1.close() # 填写日志 cur2 = conn.cursor() keys = , .join(log.keys()) values = , .join([%s]*len(log)) sql = INSERT INTO {table}({keys}) VALUES ({values}).format(table=self.table, keys=keys, values=values) cur2.execute(sql, tuple(log.values())) conn.commit() cur2.close() # 提取第一份数据 if log["STEP"] == "6-1 成功": cur3 = conn.cursor() sql = ‘‘‘ SELECT DDATE , ORGNAME , SHPID , SHPNO , SHPNAME , DEC_ODATE , ADDR , NUM_PSN , AREA , SLAMT , GST_QTY , Uprice , AmtPA , AmtPP , SLAMT_SVS , GST_QTY_SVS , Uprice_SVS , PCT_PLU , PCT_TBC , AVG_SLAMT , AVG_GST_QTY , AVG_Uprice , AVG_AmtPA , AVG_AmtPP , WLN_DAY1 , WLN_DAY2 , WLN_DAY3 , WLN_DAY4 , WLN_DAY5 , WLN_DAY6 , WLN_DAY7 FROM OUT_Tencent_N1_SALEDAYSHP WITH(NOLOCK) WHERE DDATE = DATEADD(DAY, -1, CONVERT(VARCHAR(100), GETDATE(), 23)) ‘‘‘ cur3.execute(sql) result1 = cur3.fetchall() if result1 != []: log["STEP"] = "6-2 成功" # 数据写入文件 fname = "日销售数据_" + self.now + ".xlsx" writer = pd.ExcelWriter(fname) df1 = pd.DataFrame(result1, index=None, columns=["DDATE", "ORGNAME", "SHPID", "SHPNO", "SHPNAME", "DEC_ODATE", "ADDR", "NUM_PSN", "AREA", "SLAMT", "GST_QTY", "Uprice", "AmtPA", "AmtPP", "SLAMT_SVS", "GST_QTY_SVS", "Uprice_SVS", "PCT_PLU", "PCT_TBC", "AVG_SLAMT", "AVG_GST_QTY", "AVG_Uprice", "AVG_AmtPA", "AVG_AmtPP", "WLN_DAY1", "WLN_DAY2", "WLN_DAY3", "WLN_DAY4", "WLN_DAY5", "WLN_DAY6", "WLN_DAY7"]) df1.to_excel(writer, index=False, sheet_name=门店日销售表) cur3.close() else: log["STEP"] = "6-2 第一次提取数据为空" if log["STEP"] == "6-2 成功": # 提取第二份数据 cur4 = conn.cursor() sql = ‘‘‘ SELECT DDATE , ORGNAME , CLSID , CLSNO , CLSNAME , SLAMT , VALUE , UNIT , AVG_SLAMT , AVG_VALUE , WLN_WEEK1 , WLN_WEEK2 , WLN_WEEK3 , WLN_WEEK4 , WLN_WEEK5 , WLN_WEEK6 FROM OUT_Tencent_N2_SALEDAYCLS WITH(NOLOCK) WHERE DDATE = DATEADD(DAY, -1, CONVERT(VARCHAR(100), GETDATE(), 23)) ‘‘‘ cur4.execute(sql) result2 = cur4.fetchall() if result1 != []: log["STEP"] = "6-3 成功" df2 = pd.DataFrame(result2, index=None, columns=["DDATE", "ORGNAME", "CLSID", "CLSNO", "CLSNAME", "SLAMT", "VALUE", "UNIT", "AVG_SLAMT", "AVG_VALUE", "WLN_WEEK1", "WLN_WEEK2", "WLN_WEEK3", "WLN_WEEK4", "WLN_WEEK5", "WLN_WEEK6"]) df2.to_excel(writer, index=False, sheet_name=分类日销售表) cur4.close() conn.close() writer.save() else: log["STEP"] = "6-3 第二次提取数据为空"

 

  2. 数据类

  描述:用于将数据文件上传至腾讯微瓴API

  类的实例变量:应用ID,应用密钥,应用票据,域名,文件名,时间戳,随机数,动态密钥,物联票据,当前时间

  功能模块:generateSignature(),importFiles(),uploadFiles()

  第三方库及函数解析:

  (1)requests

    requests.get(url, params) 请求目标网站,“查询”信息

    requests.put(url, data) 请求目标网站,“增添” / “上传”信息

  (2)json

    json.loads() 将json格式数据转化为字典

  (3)struct

    struct.pack() 按照给定格式封装字符串

  (4)hashlib

    hashlib.sha1().hexdigest() 将指定字符串进行SHA1加密,并返回十六进制摘要

class Data(object):
    def __init__(self):
        self.appid = ‘‘
        self.appKey = ‘‘
        self.app_ticket = ‘‘
        self.domain = ‘‘
        self.file_name = ‘‘
        self.timeStamp = str(int(round(time.time() * 1000)))
        self.randomNum = ‘‘.join(str(random.choice(range(16))) for _ in range(10))     
        self.token = ‘‘
        self.iotim_ticket = ‘‘
        self.now = time.strftime("%Y%m%d_%H",time.localtime(time.time())) 


    def generateSignature(self):
        signature_str = struct.pack(=12sqq,self.appKey,int(self.timeStamp),int(self.randomNum))
        signature = hashlib.sha1(signature_str).hexdigest()
        return signature


    def importFiles(self):
        with open(self.file_name, "rb") as f:
            data = f.read()
        return data


    def uploadFiles(self):
        # 读取配置信息:应用标识、应用密码、应用票据、API接口域名、文件名
        # config  = ConfigObj(‘E:\业务建模\API接口\config.ini‘, encoding=‘UTF8‘)
        config  = ConfigObj(config.ini, encoding=UTF8)
        self.appid = config[API][appid]
        self.appKey = bytes(config[API][appKey], encoding="utf8")
        self.app_ticket = config[API][app_ticket]
        self.domain = config[API][domain]
        self.file_name = config[API][file_name] + self.now + ".xlsx"
        
        # 接口鉴权
        params = {time: self.timeStamp,
                  num: self.randomNum,
                  sig: self.generateSignature(),
                  appid: self.appid,
                  app_ticket: self.app_ticket}
        if log["STEP"] == "6-3 成功":
            try:
                res = requests.get(url=self.domain + "/common/ticket/loginByApp", params=params)
                # print("接口鉴权信息:" + res.content)
                msg = json.loads(res.content)
                self.token = msg[udata][u"token"]
                self.iotim_ticket = msg[u"data"][u"iotim_ticket"]
                log["STEP"] = "6-4 成功" 
            except:
                log["STEP"] = "6-4 接口鉴权失败" 

        # 获取临时上传地址
        params1 = {"token": self.token,
                   "iotim_ticket": self.iotim_ticket,
                   "file_name": self.file_name}
        if log["STEP"] == "6-4 成功":
            try:      
                response1 = requests.get(url=self.domain + "/common/fileservice/uploadTemporaryFile", params=params1)
                # print("临时上传:" + response1.content.decode("utf8"))
                content = json.loads(response1.content)
                upload_url = content[udata][uupload_url]
                file_id = content[udata][ufile_id]
                log["STEP"] = "6-5 成功" 
            except:
                log["STEP"] = "6-5 上传地址获取失败"

        # 上传文件并获取临时下载地址
        params2 = {"token": self.token,
                   "iotim_ticket": self.iotim_ticket,
                   "file_id": file_id}
        if log["STEP"] == "6-5 成功":
            try:
                upload_imagdata = requests.put(url=upload_url, data=self.importFiles())
                response2 = requests.get(url=self.domain + "/common/fileservice/downloadTemporaryFile", params=params2)
                # print("临时下载::" + response2.content.decode("utf8"))
                log["STEP"] = "6-6 成功"
            except:
                log["STEP"] = "6-6 文件上传失败"

 

  3. 日志类

  描述:用于记录程序运行日志

  类的实例变量:域名,用户名,密码,数据库名,表名

  功能模块:connect()

 

class Log(object):
    def __init__(self):
        self.domain = ‘‘
        self.username = ‘‘
        self.password = ‘‘
        self.database = ‘‘
        self.table = ‘‘


    def connect(self):
        # 读取配置信息:数据库服务器、用户名、密码、指定数据库
        # config  = ConfigObj(‘E:\业务建模\API接口\config.ini‘, encoding=‘UTF8‘)
        config  = ConfigObj(config.ini, encoding=UTF8)
        self.domain = config[sql_server][domain]
        self.username = config[sql_server][username]
        self.password = config[sql_server][password]
        self.database = config[sql_server][database]
        self.table = config[sql_server][table]

        conn = pymssql.connect(self.domain, self.username, self.password, self.database)
        cur = conn.cursor()
        log["ETIME"] = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time()))
        keys = , .join(log.keys())
        values = , .join([%s]*len(log))
        sql = ‘‘‘
        UPDATE {table} SET
        ‘‘‘.format(table=self.table)
        update = ,.join([" {key} = %s".format(key=key) for key in log])
        sql += update
        sql_2 = ‘‘‘
        WHERE DDATE = ‘{date}‘ AND COUNTER = ‘{counter}‘
        ‘‘‘.format(date=date, counter=counter)
        sql += sql_2
        cur.execute(sql, tuple(log.values())*2)
        conn.commit()
        cur.close()
        conn.close()

  

  4. 执行

  描述:用于运行程序

if __name__ == __main__:
    files = File()
    files.connect()

    if log["STEP"] == "6-3 成功":
        data = Data()
        data.uploadFiles()

    record = Log()
    record.connect()

 

四. 项目难点

  难点 1 :windows10开发环境下的.py文件部署至windows2012服务器,服务器为新,不便操作环境配置

  解决方法:将.py文件封装为.exe可执行程序,定时运行

    (1)安装 pyinstaller,终端写入

pip install pyinstaller

    (2)将.py文件封装,终端写入

pyinstaller -F XXX.py

    (3)解决封装过程中的Python递归深度异常,打开.spec文件写入

import sys

sys.setrecursionlimit(50000)

    (4)解决UTF8乱码问题,终端写入

chcp 65001

    (5)将.spec文件封装为.exe文件,终端写入

pyinstaller -F XXX.spec

    (6)将生成的.exe文件与.ini配置文件一并上传至服务器,存于同一文件夹中下,设定windows定时作业

 

五. 项目总结

  第一次尝试了编写Python将数据上传至API的脚本程序,也是Python的首个实战项目,学到了很多第三方模块的运用。程序的优化仍需加强,继续学习!

 

  

  

【实战项目】API数据传输

上一篇:linux中得到当前目录指定文件或者目录的绝对路径


下一篇:Linux的基础命令(1)