Переглянути джерело

Merge branch 'yifan' of ASJ_ADS/sync_amz_data into master

guojing_wu 1 рік тому
батько
коміт
f107766ad9

+ 89 - 0
sync_amz_data/DataTransform/Data_ETL.py

@@ -0,0 +1,89 @@
+from sync_amz_data.public.amz_ad_client import SPClient,Account
+import pandas as pd
+import numpy as np
+from dateutil.parser import parse
+
+class Acount_ETL(Account):
+    def columnsName_modify(self,df):
+        df.columns = [i.replace(".","_") for i in df.columns]
+        return df
+
+    def portfolio_ETL(self):
+        list_portfolio = self.get_portfolio()
+        df_portfolio = pd.json_normalize(list_portfolio)
+        print(self.columnsName_modify(df_portfolio))
+        return self.columnsName_modify(df_portfolio)
+
+class SP_ETL(SPClient):
+    def columnsName_modify(self,df):
+        df.columns = [i.replace(".","_") for i in df.columns]
+        return df
+
+    def time_stamp_convert(self,df,time_columns:list):
+        for time_column in time_columns:
+            df[time_column] = pd.to_datetime(df[time_column])
+        df[time_columns] = df[time_columns].astype("datetime64")
+        return df
+
+    def TZ_Deal(self,df, time_columns):
+        for time_column in time_columns:
+            df[time_column] = df[time_column].map(lambda x: parse(x).strftime("%Y-%m-%d %H:%M:%S"))
+        df[time_columns] = df[time_columns].astype("datetime64")
+        return df
+
+    def placement_segmentsplit(self,df,segment):
+        df[segment] = df[segment].astype("string")
+        df[segment+str("_percentage")] = df[segment].str.extract("'percentage':.+([\d\.]{1,}),").astype('float32')
+        df[segment+str("_placement")] = df[segment].str.extract("'placement':.+'(.+)'")
+        df.replace(['nan','Nan','NaN'],np.nan,inplace=True)
+        df.drop(columns=[segment],inplace=True)
+        return df
+
+    def campaigns_ETL(self):
+        list_campaign_SP = list(self.iter_campaigns(**{"includeExtendedDataFields":True}))
+        df_campaign = pd.json_normalize(list_campaign_SP)
+        df_campaign = self.placement_segmentsplit(df_campaign, "dynamicBidding.placementBidding")
+        df_campaign = self.TZ_Deal(df_campaign,["extendedData.creationDateTime","extendedData.lastUpdateDateTime"])
+        # print(df_campaign)
+        return self.columnsName_modify(df_campaign)
+
+    def adGroup_ETL(self):
+        list_adGroup_SP = list(self.iter_adGroups(**{"includeExtendedDataFields":True}))
+        df_adGroup_SP = pd.json_normalize(list_adGroup_SP)
+        df_adGroup_SP = self.TZ_Deal(df_adGroup_SP,["extendedData.creationDateTime","extendedData.lastUpdateDateTime"])
+        return self.columnsName_modify(df_adGroup_SP)
+
+    def ads_ETL(self):
+        list_adId_SP = list(self.iter_ads(**{"includeExtendedDataFields":True}))
+        df_adId_SP = pd.json_normalize(list_adId_SP)
+        df_adId_SP = self.TZ_Deal(df_adId_SP,["extendedData.creationDateTime", "extendedData.lastUpdateDateTime"])
+        return self.columnsName_modify(df_adId_SP)
+
+    def keywords_ETL(self):
+        list_keywords_SP = list(self.iter_keywords(**{"includeExtendedDataFields":True}))
+        df_keywords_SP = pd.json_normalize(list_keywords_SP)
+        df_keywords_SP = self.TZ_Deal(df_keywords_SP, ["extendedData.creationDateTime", "extendedData.lastUpdateDateTime"])
+        return self.columnsName_modify(df_keywords_SP)
+
+    def targets_ETL(self):
+        list_targets = list(self.iter_targets())
+        df_targets = pd.json_normalize(list_targets)
+        df_targets = self.TZ_Deal(df_targets, ["extendedData.creationDateTime", "extendedData.lastUpdateDateTime"])
+        return self.columnsName_modify(df_targets)
+
+    def budget_ETL(self,campaign_ids:list):
+        list_budget = self.get_budget(campaign_ids = campaign_ids)['success']
+        df_budget = pd.json_normalize(list_budget)
+        df_budget = self.TZ_Deal(df_budget,["usageUpdatedTimestamp"])
+        print(df_budget)
+
+if __name__ == '__main__':
+    AWS_CREDENTIALS = {
+        'lwa_client_id': 'amzn1.application-oa2-client.ebd701cd07854fb38c37ee49ec4ba109',
+        'refresh_token': "Atzr|IwEBIL4ur8kbcwRyxVu_srprAAoTYzujnBvA6jU-0SMxkRgOhGjYJSUNGKvw24EQwJa1jG5RM76mQD2P22AKSq8qSD94LddoXGdKDO74eQVYl0RhuqOMFqdrEZpp1p4bIR6_N8VeSJDHr7UCuo8FiabkSHrkq7tsNvRP-yI-bnpQv4EayPBh7YwHVX3hYdRbhxaBvgJENgCuiEPb35Q2-Z6w6ujjiKUAK2VSbCFpENlEfcHNsjDeY7RCvFlwlCoHj1IeiNIaFTE9yXFu3aEWlExe3LzHv6PZyunEi88QJSXKSh56Um0e0eEg05rMv-VBM83cAqc5POmZnTP1vUdZO8fQv3NFLZ-xU6e1WQVxVPi5Cyqk4jYhGf1Y9t98N654y0tVvw74qNIsTrB-8bGS0Uhfe24oBEWmzObvBY3zhtT1d42myGUJv4pMTU6yPoS83zhPKm3LbUDEpBA1hvvc_09jHk7vUEAuFB-UAZzlht2C1yklzQ",
+        'lwa_client_secret': 'cbf0514186db4df91e04a8905f0a91b605eae4201254ced879d8bb90df4b474d',
+        'profile_id': "3006125408623189"
+    }
+    ac_etl = SP_ETL(**AWS_CREDENTIALS)
+    print(ac_etl.budget_ETL(campaign_ids=["126327624499318"]))
+

+ 286 - 0
sync_amz_data/public/SP_SB_SD_jsonDeal.py

@@ -0,0 +1,286 @@
+from sync_amz_data.public.adstoken_OAUTH import Ads_Api_Token
+import requests
+import datetime,time
+import pandas as pd
+import numpy as np
+
+access_tokenapi = Ads_Api_Token(shop_name='ZosiDirect', profileId='3006125408623189')
+access_token = access_tokenapi._get_access_token()
+
+class DataDeal:
+    def __int__(self):
+        self.public_url = "https://advertising-api.amazon.com"
+        self.ClientId = "amzn1.application-oa2-client.ebd701cd07854fb38c37ee49ec4ba109"
+        self.Scope = "3006125408623189"
+    def token_(self):
+        access_tokenapi = Ads_Api_Token(shop_name='ZosiDirect', profileId='3006125408623189')
+        access_token = access_tokenapi._get_access_token()
+        Authorization = 'Bearer ' + access_token
+        return Authorization
+
+    def time_stamp_convert(self,df,segment:list):
+        for time_column in segment:
+            df[time_column] = pd.to_datetime(df[time_column]*1000000)
+        return df
+
+    def placement_segmentsplit(self,df,segment):
+        df[segment] = df[segment].astype("string")
+        df[segment+str("_percentage")] = df[segment].str.extract("'percentage':.+([\d\.]{1,}),").astype('float32')
+        df[segment+str("_placement")] = df[segment].str.extract("'placement':.+'(.+)'")
+        df.replace(['nan','Nan','NaN'],np.nan,inplace=True)
+        df.drop(columns=[segment],inplace=True)
+        return df
+
+    def columnsName_modify(self,df):
+        df.columns = [i.replace(".","_") for i in df.columns]
+        return df
+
+    def get_profilio_info(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization': self.token_()}
+
+        url_ = "https://advertising-api.amazon.com/v2/portfolios/extended"  # V3版本
+        req_time = 0
+        while (req_time != 3):
+            req_time += 1
+            res = requests.get(url_, headers=header)  # V3版本为post请求
+            if res.status_code not in [200, 207]:
+                time.sleep(3)
+            else:
+                list_portfolio =  res.json()
+                df_portfolio = pd.json_normalize(list_portfolio)
+                return self.columnsName_modify(df_portfolio)
+        return res.text
+
+
+    def get_campaign_info_SP(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization': self.token_(),
+                  'Content-Type': 'application/vnd.spCampaign.v3+json',
+                  'Accept': """application/vnd.spCampaign.v3+json"""}
+
+        url_ = self.public_url+"/sp/campaigns/list"  # V3版本
+        req_time = 0
+        while (req_time != 3):
+            req_time += 1
+            res = requests.post(url_, headers=header)  # V3版本为post请求
+            print(res.status_code)
+            if res.status_code not in [200, 207]:
+                time.sleep(3)
+            else:
+                list_campaign_SP = res.json()
+                df_campaign = pd.json_normalize(list_campaign_SP['campaigns'])
+                df_campaign = self.placement_segmentsplit(df_campaign,"dynamicBidding.placementBidding")
+                return self.columnsName_modify(df_campaign)
+        return res.text
+
+
+
+    def get_campaign_info_SB(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization':  self.token_(),
+                  'Content-Type': 'application/vnd.sbcampaignresource.v4+json',
+                  'Accept': """application/vnd.sbcampaignresource.v4+json"""}
+
+        url_ = self.public_url+"/sb/v4/campaigns/list"
+
+        req_time = 0
+        while (req_time != 3):
+            req_time += 1
+            res = requests.post(url_, headers=header)
+            # print(res.status_code)
+            if res.status_code not in [200, 207]:
+                time.sleep(3)
+            else:
+                list_campaign_SB = res.json()
+                df_campaign_SB = pd.json_normalize(list_campaign_SB['campaigns'])
+                df_campaign_SB = self.placement_segmentsplit(df_campaign_SB,"bidding.bidAdjustmentsByPlacement")
+                return self.columnsName_modify(df_campaign_SB)
+        return res.text
+
+
+    def get_campaign_info_SD(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization':  self.token_(),}
+
+        url_ = self.public_url+"/sd/campaigns"
+
+        req_time = 0
+        while (req_time != 3):
+            req_time += 1
+            res = requests.get(url_, headers=header)  # V3版本为post请求
+            print(res.status_code)
+            if res.status_code not in [200, 207]:
+                time.sleep(3)
+            else:
+                list_campaign_SD = res.json()
+                df_campaign_SD = pd.json_normalize(list_campaign_SD)
+                df_campaign_SD['portfolioId'] = df_campaign_SD['portfolioId'].map(lambda x: int(x) if pd.isna(x)==False else -1)
+                return self.columnsName_modify(df_campaign_SD)
+        return res.text
+
+
+    def get_adGroup_info_SP(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization':  self.token_(),
+                  'Content-Type': "application/vnd.spAdGroup.v3+json",
+                  'Accept': "application/vnd.spAdGroup.v3+json"
+                  }
+
+        url_ = self.public_url+"/sp/adGroups/list"
+        res = requests.post(url_, headers=header)
+        if res.status_code not in [200, 207]:
+            return res.text
+
+        token_ = res.json().get('nextToken')
+        temp_list = [res.json()['adGroups']]
+        while token_ != None:
+            res = requests.post(url_, headers=header, json={'nextToken': token_,"includeExtendedDataFields":True})
+            if res.status_code in [200, 207]:
+                res = res.json()
+                token_ = res.get('nextToken')
+                temp_list[0].extend(res['adGroups'])
+
+            else:
+                print(res.text)
+                break
+        list_adGroup_SP = temp_list[0]
+        df_adGroup_SP = pd.json_normalize(list_adGroup_SP)
+        return self.columnsName_modify(df_adGroup_SP)
+
+
+    def get_adGroup_info_SB(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization':  self.token_(),
+                  'Content-Type': "application/vnd.sbadgroupresource.v4+json",
+                  'Accept': "application/vnd.sbadgroupresource.v4+json"
+                  }
+
+        url_ = self.public_url+"/sb/v4/adGroups/list"  # V3版本
+        res = requests.post(url_, headers=header,json={"includeExtendedDataFields":True})  # V3版本为post请求
+        if res.status_code not in [200, 207]:
+            return res.text
+
+        token_ = res.json().get('nextToken')
+        temp_list = [res.json()['adGroups']]
+        while token_ != None:
+            res = requests.post(url_, headers=header, json={'nextToken': token_,"includeExtendedDataFields":True})
+            if res.status_code in [200, 207]:
+                res = res.json()
+                token_ = res.get('nextToken')
+                temp_list[0].extend(res['adGroups'])
+            else:
+                print(res.text)
+                break
+        list_adGroup_SB = temp_list[0]
+        df_adGroup_SB = pd.json_normalize(list_adGroup_SB)
+        df_adGroup_SB = self.time_stamp_convert(df_adGroup_SB,['extendedData.creationDate','extendedData.lastUpdateDate'])
+        return self.columnsName_modify(df_adGroup_SB)
+
+
+
+
+    def get_adGroup_info_SD(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization':  self.token_(),
+                  }
+        url_ = self.public_url+"/sd/adGroups/extended"  # V3版本
+
+        res = requests.get(url_, headers=header)  # ,params={"startIndex":0,"count":1}
+        #
+        list_adGroup_SD = res.json()
+        df_adGroup_SD = pd.json_normalize(list_adGroup_SD)
+        df_adGroup_SD = self.time_stamp_convert(df_adGroup_SD,['creationDate','lastUpdatedDate'])
+        return self.columnsName_modify(df_adGroup_SD)
+
+
+    def get_adId_info_SP(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization':  self.token_(),
+                  'Content-Type': "application/vnd.spProductAd.v3+json",
+                  'Accept': "application/vnd.spProductAd.v3+json"
+                  }
+        url_ = self.public_url+"/sp/productAds/list"
+
+        res = requests.post(url_, headers=header, json={"includeExtendedDataFields": True})  # V3版本为post请求
+        if res.status_code not in [200, 207]:
+            return res.text
+        print(res.json())
+
+        token_ = res.json().get('nextToken')
+        temp_list = [res.json()['productAds']]
+        while token_ != None:
+            res = requests.post(url_, headers=header, json={'nextToken': token_, "includeExtendedDataFields": True})
+            if res.status_code in [200, 207]:
+                res = res.json()
+                print(res)
+                token_ = res.get('nextToken')
+                temp_list[0].extend(res['productAds'])
+
+            else:
+                print(res.text)
+                break
+
+        list_adId_SP = temp_list[0]
+        df_adId_SP = pd.json_normalize(list_adId_SP)
+        return self.columnsName_modify(df_adId_SP)
+
+
+    def get_adId_info_SB(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization':  self.token_(),
+                  'Content-Type': "application/vnd.sbadresource.v4+json",
+                  'Accept': "application/vnd.sbadresource.v4+json"
+                  }
+        url_ = self.public_url+"/sb/v4/ads/list"
+
+        res = requests.post(url_, headers=header)  # V3版本为post请求
+        if res.status_code not in [200, 207]:
+            return res.text
+        token_ = res.json().get('nextToken')
+        temp_list = [res.json()['ads']]
+        while token_ != None:
+            res = requests.post(url_, headers=header, json={'nextToken': token_})
+            if res.status_code in [200, 207]:
+                res = res.json()
+                print(res)
+                token_ = res.get('nextToken')
+                temp_list[0].extend(res['ads'])
+
+            else:
+                print(res.text)
+                break
+        list_adId_SB = temp_list[0]
+        return self.SB_segmentDeal(list_adId_SB)
+
+    def SB_segmentDeal(self,rel):
+        df = pd.json_normalize(rel)
+        df = self.time_stamp_convert(df,['extendedData.creationDate','extendedData.lastUpdateDate'])
+        df[["creative.asin1","creative.asin2","creative.asin3"]] = df['creative.asins'].map(str).str.slice(1,-1).str.replace("'","").str.split(",",expand=True).applymap(lambda x:None if x==None else x.strip())
+        df.drop(columns="creative.asins",inplace=True)
+        df = self.columnsName_modify(df)
+        return df
+
+
+
+    def get_adId_info_SD(self,token):
+        header = {'Amazon-Advertising-API-ClientId': self.ClientId,
+                  'Amazon-Advertising-API-Scope': self.Scope,
+                  'Authorization':  self.token_(),
+                  }
+        url_ = self.public_url+"/sd/productAds/extended"
+
+        res = requests.get(url_, headers=header)
+        return pd.json_normalize(res.json())
+
+
+

+ 197 - 0
sync_amz_data/public/adstoken_OAUTH.py

@@ -0,0 +1,197 @@
+import json
+import requests
+import sys
+import time
+import os
+# from myapp.temp_get_budget_black_five.info import *
+
+#"C:\Users\ansjer\Documents\WXWork\1688858253398958\Cache\File\2023-08\autobid\autobid\info\token_info.json"
+class Ads_Api_Token(object):
+
+    def __init__(self, authorization_code='-1', profileId='-1', shop_name='-1') -> None:
+        sub_dir = os.path.abspath(os.path.dirname(__file__))
+        self.return_url = 'https://www.amazon.com/'
+        self.save_json_file_name = r"./token_info.json"# r"{sub_dir}\info\token_info.json".format(sub_dir=sub_dir)
+        self.lwa_info_file_name = r"./lwa_info.json"# r"{sub_dir}\info\lwa_info.json".format(sub_dir=sub_dir)
+        self.headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+        self.authorization_url = 'https://api.amazon.com/auth/o2/token'
+        self.authorization_code = authorization_code
+
+        # self.region = 'NA'
+        lwa_info = self._read_lwa_info()
+        self.lwa_client_id = lwa_info['lwa_client_id']
+        self.lwa_client_secret = lwa_info['lwa_client_secret']
+        self.access_token = self._get_access_token()
+
+        # 检查错误
+        if shop_name == '-1' or profileId == '-1':
+            print('错误:【shop_name或profileId没有初始化】')
+            print('作为广告商,你的客户列表:')
+            self._get_profile()
+            print('\n确认客户区域、名字等,选择name和profileId重新初始化shop_name和profileId')
+            sys.exit()
+        # print(self._get_profileId(name=shop_name),type(self._get_profileId(name=shop_name)))
+        if self._get_profileId(name=shop_name) != profileId:
+            print('错误:【检查profileId与shop_name不匹配】')
+            print('作为广告商,你的客户列表:')
+            self._get_profile()
+
+            print('\n确认客户区域、名字等,选择name和profileId重新初始化shop_name和profileId')
+            sys.exit()
+
+        self.shop_name = shop_name
+        self.profileId = profileId
+
+    def _read_lwa_info(self) -> str:
+        with open(self.lwa_info_file_name, 'r') as file_obj:
+            lwa_info = json.load(file_obj)
+        file_obj.close()
+        return lwa_info
+
+    def _read_token(self) -> str:
+        with open(self.save_json_file_name, 'r') as file_obj:
+            token = json.load(file_obj)
+        file_obj.close()
+        return token
+
+    def _write_token(self, token: dict) -> None:
+        """转换过期时间到秒级时间戳写入token
+
+        Args:
+            token (dict): token
+        """
+        token['expires_in'] += int(time.time())
+        with open(self.save_json_file_name, 'w') as file_obj:
+            json.dump(token, file_obj)
+
+    def _check_token_json(self, token: dict) -> bool:
+        """检查token合法性
+
+        Args:
+            token (dict): token
+
+        Returns:
+            bool: 是否合法
+        """
+        if 'access_token' in token and 'refresh_token' in token and 'expires_in' in token:
+            return True
+        else:
+            self._print_authorization_grant_info(error_type=1)
+
+    def _print_authorization_grant_info(self, error_type='-1'):
+        """打印授权的授权信息,获取授权code
+
+        Args:
+            error_type (str, optional): 0为找不到文件,1为文件内容不通过. Defaults to '-1'.
+        """
+        url = 'https://www.amazon.com/ap/oa?client_id=%s&scope=advertising::campaign_management&response_type=code&redirect_uri=%s' % (
+        self.lwa_client_id, self.return_url)
+        if error_type == 0:
+            print('==' * 10 + '未检测' + self.save_json_file_name + '==' * 10)
+        if error_type == 1:
+            print('==' * 10 + '内容不通过' + self.save_json_file_name + '==' * 10)
+        print('\t\t1.打开登陆地址' + url)
+        print('\t\t2.点击允许并登陆zhangside账号')
+        print('\t\t3.将重定向的url中的code保存下来')
+        print('\t\t4.将code填入初始化Token的authorization_code')
+        print('==' * 30)
+        sys.exit()
+
+    def _first_get_token(self, authorization_code: str) -> str:
+        """从授权的授权code申请token
+
+        Args:
+            authorization_code (str): 授权的授权code
+
+        Returns:
+            str: access_token
+        """
+        data = 'grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s&client_secret=%s' % (
+        authorization_code, self.return_url, self.lwa_client_id, self.lwa_client_secret)
+        res = requests.post(self.authorization_url, data=data, headers=self.headers)
+        res = res.json()
+        self._check_token_json(res)
+        self._write_token(res)
+        return res['access_token']
+
+    def _refresh_token(self, limit_time_minute=3) -> str:
+        """刷新token,如果在授权时间还有多余limit_time_minute分钟不刷新直接返回access_token
+
+        Args:
+            limit_time_minute (int, optional): 剩余时间分钟. Defaults to 15.
+
+        Returns:
+            str: access_token
+        """
+        token = self._read_token()
+        self._check_token_json(token)
+        token_timestamp = token['expires_in']
+        if token_timestamp - int(time.time()) > limit_time_minute * 60:
+            return token['access_token']
+        data = 'grant_type=refresh_token&client_id=%s&refresh_token=%s&client_secret=%s' % (
+        self.lwa_client_id, token['refresh_token'], self.lwa_client_secret)
+        res = requests.post(self.authorization_url, data=data, headers=self.headers)
+        res = res.json()
+        self._check_token_json(res)
+        self._write_token(res)
+        return res['access_token']
+
+    def _get_access_token(self) -> str:
+        """根据状态获取token
+
+        Returns:
+            str: access_token
+        """
+        # 检查状态
+        if self.authorization_code == '-1':
+            if not os.path.exists(self.save_json_file_name):
+                self._print_authorization_grant_info(error_type=0)
+            token = self._read_token()
+            self._check_token_json(token)
+            access_token = self._refresh_token()
+            return access_token
+        else:
+            print('填入了code,获取授权的授权,下一次可不用填入code')
+            access_token = self._first_get_token(self.authorization_code)
+            return access_token
+
+    def _get_profileId(self, name):
+        profiles_url = 'https://advertising-api.amazon.com/v2/profiles'
+        data = {
+            'Amazon-Advertising-API-ClientId': self.lwa_client_id,
+            'Authorization': 'Bearer ' + self.access_token
+        }
+        res = requests.get(profiles_url, headers=data)
+        res = res.json()
+        profileId = '-1'
+        for customer_info in res:
+            if customer_info['accountInfo']['name'] == name:
+                profileId = customer_info['profileId']
+        return str(profileId)
+
+    def _get_profile(self):
+        profiles_url = 'https://advertising-api.amazon.com/v2/profiles'
+        data = {
+            'Amazon-Advertising-API-ClientId': self.lwa_client_id,
+            'Authorization': 'Bearer ' + self.access_token
+        }
+        res = requests.get(profiles_url, headers=data)
+        res = res.json()
+        print(res)
+
+
+if __name__ == "__main__":
+    # authorization_code = 'ANzRQnVlaRziQlNxrHGG'
+    access_token = Ads_Api_Token(shop_name = 'ZosiDirect',profileId='3006125408623189')
+    tken = access_token._get_access_token()
+    print(type(tken),tken)
+# {'profileId': 420999963953676,
+#  'countryCode': 'BR',
+#  'currencyCode': 'BRL',
+#  'dailyBudget': 999999999.0,
+#  'timezone': 'America/Sao_Paulo',
+#  'accountInfo': {'marketplaceStringId': 'A2Q3Y263D00KWC',
+#                  'id': 'A252W7I0ACJHS1',
+#                  'type': 'seller',
+#                  'name': 'ZosiDirect',
+#                  'validPaymentMethod': False}}

+ 184 - 41
sync_amz_data/public/amz_ad_client.py

@@ -6,14 +6,14 @@ from urllib.parse import urljoin
 from typing import List, Literal, Iterable, Iterator
 import gzip
 from pathlib import Path
-from nb_log import get_logger
+from logging import getLogger
 
 URL_AUTH = "https://api.amazon.com/auth/o2/token"
 URL_AD_API = "https://advertising-api.amazon.com"
 
 cache = TTLCache(maxsize=10, ttl=3200)
 
-logger = get_logger(__name__)
+logger = getLogger(__name__)
 
 
 def gz_decompress(file_path: str, chunk_size: int = 1024 * 1024):
@@ -93,6 +93,17 @@ class SPClient(BaseClient):
         }
         return self._request(url_path, method="POST", headers=headers, body=body)
 
+    def iter_campaigns(self, **body) -> Iterator[dict]:
+        if "maxResults" not in body:
+            body["maxResults"] = 100
+        while True:
+            info: dict = self.get_campaigns(**body)
+            yield from info["campaigns"]
+            if not info.get("nextToken"):
+                break
+            body["nextToken"] = info["nextToken"]
+        logger.info(f"总共数量:{info['totalResults']}")
+
     def get_ad_groups(self, **body):
         url_path = "/sp/adGroups/list"
         headers = {
@@ -101,6 +112,17 @@ class SPClient(BaseClient):
         }
         return self._request(url_path, method="POST", body=body, headers=headers)
 
+    def iter_adGroups(self, **body) -> Iterator[dict]:
+        if "maxResults" not in body:
+            body["maxResults"] = 100
+        while True:
+            info: dict = self.get_ad_groups(**body)
+            yield from info["adGroups"]
+            if not info.get("nextToken"):
+                break
+            body["nextToken"] = info["nextToken"]
+        logger.info(f"总共数量:{info['totalResults']}")
+
     def get_ads(self, **body):
         url_path = "/sp/productAds/list"
         headers = {
@@ -109,6 +131,17 @@ class SPClient(BaseClient):
         }
         return self._request(url_path, method="POST", body=body, headers=headers)
 
+    def iter_ads(self, **body) -> Iterator[dict]:
+        if "maxResults" not in body:
+            body["maxResults"] = 100
+        while True:
+            info: dict = self.get_ads(**body)
+            yield from info["productAds"]
+            if not info.get("nextToken"):
+                break
+            body["nextToken"] = info["nextToken"]
+        logger.info(f"总共数量:{info['totalResults']}")
+
     def get_keywords(self, **body):
         url_path = "/sp/keywords/list"
         headers = {
@@ -116,6 +149,16 @@ class SPClient(BaseClient):
             "Content-Type": "application/vnd.spKeyword.v3+json"
         }
         return self._request(url_path, method="POST", body=body, headers=headers)
+    def iter_keywords(self, **body) -> Iterator[dict]:
+        if "maxResults" not in body:
+            body["maxResults"] = 100
+        while True:
+            info: dict = self.get_keywords(**body)
+            yield from info["keywords"]
+            if not info.get("nextToken"):
+                break
+            body["nextToken"] = info["nextToken"]
+        logger.info(f"总共数量:{info['totalResults']}")
 
     def get_targets(self, **body):
         url_path = "/sp/targets/list"
@@ -125,14 +168,43 @@ class SPClient(BaseClient):
         }
         return self._request(url_path, method="POST", body=body, headers=headers)
 
+    def iter_targets(self, **body) -> Iterator[dict]:
+        if "maxResults" not in body:
+            body["maxResults"] = 100
+        while True:
+            info: dict = self.get_targets(**body)
+            yield from info["targetingClauses"]
+            if not info.get("nextToken"):
+                break
+            body["nextToken"] = info["nextToken"]
+        logger.info(f"总共数量:{info['totalResults']}")
+
     def get_budget(self, campaign_ids: list):
         url_path = "/sp/campaigns/budget/usage"
         body = {
             "campaignIds": campaign_ids
         }
-        ret_data = self._request(url_path, method="POST", body=body)
-        print(json.dumps(ret_data, ensure_ascii=False))
+        return self._request(url_path, method="POST", body=body)
+
+    def get_adgroup_bidrecommendation(self,campaignId:str,adGroupId:str,targetingExpressions:list,recommendationType:str="BIDS_FOR_EXISTING_AD_GROUP"):
+        url_path = "/sp/targets/bid/recommendations"
+        headers = {
+            "Accept": "application/vnd.spthemebasedbidrecommendation.v3+json",
+            "Content-Type": "application/vnd.spthemebasedbidrecommendation.v3+json"
+        }
+        body = {"campaignId":campaignId,
+                "adGroupId":adGroupId,
+                "recommendationType":recommendationType,
+                "targetingExpressions":targetingExpressions}
+        return self._request(url_path, method="POST", body=body, headers=headers)
 
+    def get_keyword_bidrecommendation(self,adGroupId:str,keyword:list,matchType:list):
+        keywords = list(map(lambda x:{"keyword":x[0],"matchType":x[1]},list(zip(keyword,matchType))))
+        print(keywords)
+        url_path = "/v2/sp/keywords/bidRecommendations"
+        body = {"adGroupId":adGroupId,
+                "keywords":keywords}
+        return self._request(url_path, method="POST", body=body)
 
 class SBClient(BaseClient):
     def get_campaigns(self, **body):
@@ -152,10 +224,70 @@ class SBClient(BaseClient):
             if not info.get("nextToken"):
                 break
             body["nextToken"] = info["nextToken"]
-        logger.info(f"总共数量:{info['totalResults']}")
+        # logger.info(f"总共数量:{info['totalResults']}")
+
+    def get_ad_groups(self,**body):
+        url_path = "/sb/v4/adGroups/list"
+        headers = {
+            'Content-Type': "application/vnd.sbadgroupresource.v4+json",
+            'Accept': "application/vnd.sbadgroupresource.v4+json"
+        }
+        return self._request(url_path, method="POST", headers=headers,body=body)
+
+    def iter_adGroups(self, **body) -> Iterator[dict]:
+        if "maxResults" not in body:
+            body["maxResults"] = 100
+        while True:
+            info: dict = self.get_ad_groups(**body)
+            print(info)
+            yield from info["adGroups"]
+            if not info.get("nextToken"):
+                break
+            body["nextToken"] = info["nextToken"]
 
-    def get_ad_groups(self):
-        pass
+    def get_ads(self,**body):
+        url_path = "/sb/v4/ads/list"
+        headers = {'Content-Type': "application/vnd.sbadresource.v4+json",
+                  'Accept': "application/vnd.sbadresource.v4+json"
+                  }
+        return self._request(url_path, method="POST", headers=headers,body=body)
+
+    def iter_ads(self,**body):
+        if "maxResults" not in body:
+            body["maxResults"] = 100
+        while True:
+            info: dict = self.get_ads(**body)
+            print(info)
+            yield from info["ads"]
+            if not info.get("nextToken"):
+                break
+            body["nextToken"] = info["nextToken"]
+    def get_keywords(self):
+        url_path = "/sb/keywords"
+        return self._request(url_path, method="GET")
+
+    def get_targets(self,**body):
+        url_path = "/sb/targets/list"
+        return self._request(url_path, method="POST", body=body)
+
+    def iter_targets(self,**body):
+        if "maxResults" not in body:
+            body["maxResults"] = 100
+        while True:
+            info: dict = self.get_targets(**body)
+            # print(info)
+            yield from info["targets"]
+            if not info.get("nextToken"):
+                break
+            body["nextToken"] = info["nextToken"]
+
+    def get_budget(self,campaignIds:list):
+        url_path = "/sb/campaigns/budget/usage"
+        body = {"campaignIds":campaignIds}
+        return self._request(url_path, method="POST",body=body)
+    def get_keyword_bidrecommendation(self,**body):
+        url_path = "/sb/recommendations/bids"
+        return self._request(url_path, method="POST", body=body)
 
     def get_report(
             self,
@@ -226,6 +358,11 @@ class SDClient(BaseClient):
         url_path = "/sd/campaigns"
         return self._request(url_path, params=params)
 
+class Account(BaseClient):
+    def get_portfolio(self):
+        url_path = "/v2/portfolios/extended"
+        return self._request(url_path)
+
 
 if __name__ == '__main__':
     AWS_CREDENTIALS = {
@@ -234,40 +371,46 @@ if __name__ == '__main__':
         'lwa_client_secret': 'cbf0514186db4df91e04a8905f0a91b605eae4201254ced879d8bb90df4b474d',
         'profile_id': "3006125408623189"
     }
+    # sp = SPClient(**AWS_CREDENTIALS)
+    # print(sp.get_keyword_bidrecommendation(adGroupId="119753215871672",keyword=["8mp security camera system","8mp security camera system"],matchType=["broad","exact"]))
+    sb = SBClient(**AWS_CREDENTIALS)
+    # print(list(sb.iter_targets()))
+    print(sb.get_keyword_bidrecommendation(**{'campaignId': 27333596383941,'keywords':[{"matchType":'broad',"keywordText":"4k security camera system"}]}))
+    print(sb.get_budget([27333596383941]))
     # sd = SDClient(**AWS_CREDENTIALS)
     # print(sd.get_campaigns(startIndex=10, count=10))
 
-    sb = SBClient(**AWS_CREDENTIALS)
-    metrics = [
-        'applicableBudgetRuleId',
-        'applicableBudgetRuleName',
-        'attributedConversions14d',
-        'attributedConversions14dSameSKU',
-        'attributedDetailPageViewsClicks14d',
-        'attributedOrderRateNewToBrand14d',
-        'attributedOrdersNewToBrand14d',
-        'attributedOrdersNewToBrandPercentage14d',
-        'attributedSales14d',
-        'attributedSales14dSameSKU',
-        'attributedSalesNewToBrand14d',
-        'attributedSalesNewToBrandPercentage14d',
-        'attributedUnitsOrderedNewToBrand14d',
-        'attributedUnitsOrderedNewToBrandPercentage14d',
-        'campaignBudget',
-        'campaignBudgetType',
-        'campaignId',
-        'campaignName',
-        'campaignRuleBasedBudget',
-        'campaignStatus',
-        'clicks',
-        'cost',
-        'dpv14d',
-        'impressions',
-        'unitsSold14d',
-        'attributedBrandedSearches14d',
-        'topOfSearchImpressionShare']
-    sb.get_report(
-        record_type="campaigns",
-        report_date="20231008",
-        metrics=metrics
-    )
+    # sb = SBClient(**AWS_CREDENTIALS)
+    # metrics = [
+    #     'applicableBudgetRuleId',
+    #     'applicableBudgetRuleName',
+    #     'attributedConversions14d',
+    #     'attributedConversions14dSameSKU',
+    #     'attributedDetailPageViewsClicks14d',
+    #     'attributedOrderRateNewToBrand14d',
+    #     'attributedOrdersNewToBrand14d',
+    #     'attributedOrdersNewToBrandPercentage14d',
+    #     'attributedSales14d',
+    #     'attributedSales14dSameSKU',
+    #     'attributedSalesNewToBrand14d',
+    #     'attributedSalesNewToBrandPercentage14d',
+    #     'attributedUnitsOrderedNewToBrand14d',
+    #     'attributedUnitsOrderedNewToBrandPercentage14d',
+    #     'campaignBudget',
+    #     'campaignBudgetType',
+    #     'campaignId',
+    #     'campaignName',
+    #     'campaignRuleBasedBudget',
+    #     'campaignStatus',
+    #     'clicks',
+    #     'cost',
+    #     'dpv14d',
+    #     'impressions',
+    #     'unitsSold14d',
+    #     'attributedBrandedSearches14d',
+    #     'topOfSearchImpressionShare']
+    # sb.get_report(
+    #     record_type="campaigns",
+    #     report_date="20231008",
+    #     metrics=metrics
+    # )