amz_ad_client.py 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336
  1. import requests
  2. from urllib3.util.retry import Retry
  3. from requests.adapters import HTTPAdapter
  4. import json
  5. import time
  6. from cachetools import TTLCache
  7. from urllib.parse import urljoin
  8. from typing import List, Literal, Iterable, Iterator
  9. import gzip
  10. from pathlib import Path
  11. import s3fs
  12. from s3fs import S3FileSystem
  13. import logging
  14. URL_AUTH = "https://api.amazon.com/auth/o2/token"
  15. URL_AD_API = "https://advertising-api.amazon.com"
  16. cache = TTLCache(maxsize=10, ttl=3200)
  17. logger = logging.getLogger(__name__)
  18. def shop_infos(profile_id):
  19. resp_rel = requests.get("http://192.168.1.225/api/ad_manage/profiles/",headers={"X-Token": "da4ab6bc5cbf1dfa"})
  20. data = resp_rel.json().get("data")
  21. profile_info = {}
  22. for info in data:
  23. if info.get("profile_id") in [int(profile_id),str(profile_id)]:
  24. profile_info["profile_id"] = profile_id
  25. profile_info["refresh_token"] = info.get("refresh_token")
  26. profile_info["account_name"] = info.get("account_name")
  27. profile_info["advertiser_id"] = info.get("advertiser_id")
  28. profile_info["country_code"] = info.get("country_code")
  29. profile_info["marketplace_str_id"] =info.get("marketplace_str_id")
  30. profile_info["time_zone"] = info.get("time_zone")
  31. return profile_info
  32. return resp_rel.text
  33. class RateLimitError(Exception):
  34. def __init__(self, retry_after: str = None):
  35. self.retry_after = retry_after
  36. def gz_decompress(file_path: str, chunk_size: int = 1024 * 1024):
  37. decompressed_file = file_path.rstrip(".gz")
  38. with open(decompressed_file, "wb") as pw:
  39. zf = gzip.open(file_path, mode='rb')
  40. while True:
  41. chunk = zf.read(size=chunk_size)
  42. if not chunk:
  43. break
  44. pw.write(chunk)
  45. return decompressed_file
  46. class BaseClient:
  47. def __init__(
  48. self, lwa_client_id: str, lwa_client_secret: str, refresh_token: str = None, profile_id: str = None,
  49. data_path: str = "./"
  50. ):
  51. self.lwa_client_id = lwa_client_id
  52. self.lwa_client_secret = lwa_client_secret
  53. self.refresh_token = refresh_token
  54. self.profile_id = profile_id
  55. self.data_path = Path(data_path)
  56. if not self.data_path.exists():
  57. self.data_path.mkdir(parents=True)
  58. retry_strategy = Retry(
  59. total=5, # 重试次数
  60. allowed_methods=["GET", "POST"],
  61. # 强制重试的状态码,在method_whitelist中的请求方法才会重试
  62. status_forcelist=[429, 500, 502, 503, 504],
  63. raise_on_status=False, # 在status_forcelist中的状态码达到重试次数后是否抛出异常
  64. # backoff_factor * (2 ** (retry_time-1)), 即间隔1s, 2s, 4s, 8s, ...
  65. backoff_factor=2,
  66. )
  67. adapter = HTTPAdapter(max_retries=retry_strategy)
  68. self.session = requests.session()
  69. self.session.mount("https://", adapter)
  70. @property
  71. def access_token(self) -> str:
  72. try:
  73. return cache[self.refresh_token]
  74. except KeyError:
  75. resp = requests.post(URL_AUTH, data={
  76. "grant_type": "refresh_token",
  77. "client_id": self.lwa_client_id,
  78. "refresh_token": self.refresh_token,
  79. "client_secret": self.lwa_client_secret,
  80. })
  81. if resp.status_code != 200:
  82. raise Exception(resp.text)
  83. js = resp.json()
  84. cache[self.refresh_token] = js["access_token"]
  85. self.refresh_token = js["refresh_token"]
  86. return js["access_token"]
  87. @property
  88. def auth_headers(self):
  89. return {
  90. "Amazon-Advertising-API-ClientId": self.lwa_client_id,
  91. "Amazon-Advertising-API-Scope": self.profile_id,
  92. "Authorization": f"Bearer {self.access_token}",
  93. }
  94. def _request(self, url_path: str, method: str = "GET", headers: dict = None, params: dict = None,
  95. body: dict = None):
  96. head = self.auth_headers
  97. if headers:
  98. head.update(headers)
  99. resp = self.session.request(
  100. method=method,
  101. url=urljoin(URL_AD_API, url_path),
  102. headers=head,
  103. params=params,
  104. json=body,
  105. )
  106. if resp.status_code == 429:
  107. raise RateLimitError(resp.headers.get("Retry-After"))
  108. if resp.status_code >= 400:
  109. raise Exception(resp.text)
  110. return resp.json()
  111. def get_profilesInfo(self):
  112. url_path = "/v2/profiles"
  113. return self._request(url_path)
  114. class SPClient(BaseClient):
  115. def get_campaigns(self, **body):
  116. url_path = "/sp/campaigns/list"
  117. headers = {
  118. "Accept": "application/vnd.spcampaign.v3+json",
  119. "Content-Type": "application/vnd.spcampaign.v3+json"
  120. }
  121. return self._request(url_path, method="POST", headers=headers, body=body)
  122. def iter_campaigns(self, **body) -> Iterator[dict]:
  123. if "maxResults" not in body:
  124. body["maxResults"] = 100
  125. while True:
  126. info: dict = self.get_campaigns(**body)
  127. yield from info["campaigns"]
  128. if not info.get("nextToken"):
  129. break
  130. body["nextToken"] = info["nextToken"]
  131. logger.info(f"总共数量:{info['totalResults']}")
  132. def get_budgetrecommendation(self, campaign_ids):
  133. url_path = "/sp/campaigns/budgetRecommendations"
  134. body = {
  135. "campaignIds": campaign_ids
  136. }
  137. headers = {
  138. "Accept": "application/vnd.budgetrecommendation.v3+json",
  139. "Content-Type": "application/vnd.budgetrecommendation.v3+json"
  140. }
  141. return self._request(url_path, method="POST", headers=headers, body=body)
  142. def iter_budgetrecommendation(self,campaign_ids):
  143. for i in range(0,len(campaign_ids),100):
  144. campaign_id = campaign_ids[i:i+100]
  145. info: list = self.get_budgetrecommendation(campaign_id)
  146. yield from info["budgetRecommendationsSuccessResults"]
  147. def get_ad_groups(self, **body):
  148. url_path = "/sp/adGroups/list"
  149. headers = {
  150. "Accept": "application/vnd.spadGroup.v3+json",
  151. "Content-Type": "application/vnd.spadGroup.v3+json"
  152. }
  153. return self._request(url_path, method="POST", body=body, headers=headers)
  154. def iter_adGroups(self, **body) -> Iterator[dict]:
  155. if "maxResults" not in body:
  156. body["maxResults"] = 100
  157. while True:
  158. info: dict = self.get_ad_groups(**body)
  159. yield from info["adGroups"]
  160. if not info.get("nextToken"):
  161. break
  162. body["nextToken"] = info["nextToken"]
  163. logger.info(f"总共数量:{info['totalResults']}")
  164. def get_ads(self, **body):
  165. url_path = "/sp/productAds/list"
  166. headers = {
  167. "Accept": "application/vnd.spproductAd.v3+json",
  168. "Content-Type": "application/vnd.spproductAd.v3+json"
  169. }
  170. return self._request(url_path, method="POST", body=body, headers=headers)
  171. def iter_ads(self, **body) -> Iterator[dict]:
  172. if "maxResults" not in body:
  173. body["maxResults"] = 100
  174. while True:
  175. info: dict = self.get_ads(**body)
  176. yield from info["productAds"]
  177. if not info.get("nextToken"):
  178. break
  179. body["nextToken"] = info["nextToken"]
  180. logger.info(f"总共数量:{info['totalResults']}")
  181. def get_keywords(self, **body):
  182. url_path = "/sp/keywords/list"
  183. headers = {
  184. "Accept": "application/vnd.spKeyword.v3+json",
  185. "Content-Type": "application/vnd.spKeyword.v3+json"
  186. }
  187. return self._request(url_path, method="POST", body=body, headers=headers)
  188. def iter_keywords(self, **body) -> Iterator[dict]:
  189. if "maxResults" not in body:
  190. body["maxResults"] = 100
  191. while True:
  192. info: dict = self.get_keywords(**body)
  193. yield from info["keywords"]
  194. if not info.get("nextToken"):
  195. break
  196. body["nextToken"] = info["nextToken"]
  197. logger.info(f"总共数量:{info['totalResults']}")
  198. def get_targets(self, **body):
  199. url_path = "/sp/targets/list"
  200. headers = {
  201. "Accept": "application/vnd.sptargetingClause.v3+json",
  202. "Content-Type": "application/vnd.sptargetingClause.v3+json"
  203. }
  204. return self._request(url_path, method="POST", body=body, headers=headers)
  205. def iter_targets(self, **body) -> Iterator[dict]:
  206. if "maxResults" not in body:
  207. body["maxResults"] = 100
  208. while True:
  209. info: dict = self.get_targets(**body)
  210. yield from info["targetingClauses"]
  211. if not info.get("nextToken"):
  212. break
  213. body["nextToken"] = info["nextToken"]
  214. logger.info(f"总共数量:{info['totalResults']}")
  215. def get_budget(self, campaign_ids: list):
  216. url_path = "/sp/campaigns/budget/usage"
  217. body = {
  218. "campaignIds": campaign_ids
  219. }
  220. return self._request(url_path, method="POST", body=body)
  221. def get_adgroup_bidrecommendation(
  222. self, campaignId: str, adGroupId: str, targetingExpressions: list,
  223. recommendationType: str = "BIDS_FOR_EXISTING_AD_GROUP"):
  224. url_path = "/sp/targets/bid/recommendations"
  225. headers = {
  226. "Accept": "application/vnd.spthemebasedbidrecommendation.v3+json",
  227. "Content-Type": "application/vnd.spthemebasedbidrecommendation.v3+json"
  228. }
  229. body = {
  230. "campaignId": campaignId,
  231. "adGroupId": adGroupId,
  232. "recommendationType": recommendationType,
  233. "targetingExpressions": targetingExpressions
  234. }
  235. return self._request(url_path, method="POST", body=body, headers=headers)
  236. def iter_adgroup_bidrecommendation(self,campaignId: str, adGroupId: str, targetingExpressions: list,
  237. recommendationType: str = "BIDS_FOR_EXISTING_AD_GROUP"):
  238. for i in range(0,len(targetingExpressions),100):
  239. try:
  240. info = self.get_adgroup_bidrecommendation(campaignId,adGroupId,targetingExpressions[i:i+100],recommendationType)
  241. yield from info['bidRecommendations']
  242. except:
  243. # print("空值")
  244. return iter([])
  245. def get_keyword_bidrecommendation(self, adGroupId: str, keyword: list, matchType: list):
  246. keywords = list(map(lambda x: {"keyword": x[0], "matchType": x[1]}, list(zip(keyword, matchType))))
  247. url_path = "/v2/sp/keywords/bidRecommendations"
  248. body = {"adGroupId": adGroupId,
  249. "keywords": keywords}
  250. return self._request(url_path, method="POST", body=body)
  251. def get_bidrecommendationList(self,adGroupId,expressions):
  252. url_path = "/v2/sp/targets/bidRecommendations"
  253. body = {
  254. "adGroupId": adGroupId,
  255. "expressions": expressions
  256. }
  257. return self._request(url_path,method="POST",body=body)
  258. def iter_bidrecommendationList(self, adGroupId, expressions):
  259. for i in range(0,len(expressions), 10):
  260. try:
  261. info = self.get_bidrecommendationList(adGroupId, expressions[i:i+10])
  262. out = info['recommendations']
  263. for i in out:
  264. i['adGroupId'] = adGroupId
  265. yield from out
  266. except:
  267. # print("空值")
  268. return iter([])
  269. def get_campaignNegativekeyword(self,**body):
  270. url_path = '/sp/campaignNegativeKeywords/list'
  271. headers = {
  272. "Accept": "application/vnd.spCampaignNegativeKeyword.v3+json",
  273. "Content-Type": "application/vnd.spCampaignNegativeKeyword.v3+json"
  274. }
  275. return self._request(url_path,method="POST",body=body,headers=headers)
  276. def iter_campaignNegativekeyword(self,**body):
  277. if "maxResults" not in body:
  278. body["maxResults"] = 100
  279. while True:
  280. info: dict = self.get_campaignNegativekeyword(**body)
  281. yield from info["campaignNegativeKeywords"]
  282. if not info.get("nextToken"):
  283. break
  284. body["nextToken"] = info["nextToken"]
  285. def get_negativekeyword(self,**body):
  286. url_path = '/sp/negativeKeywords/list'
  287. headers = {
  288. "Accept": "application/vnd.spNegativeKeyword.v3+json",
  289. "Content-Type": "application/vnd.spNegativeKeyword.v3+json"
  290. }
  291. return self._request(url_path,method="POST",body=body,headers=headers)
  292. def iter_negativekeyword(self,**body):
  293. if "maxResults" not in body:
  294. body["maxResults"] = 100
  295. while True:
  296. info: dict = self.get_negativekeyword(**body)
  297. yield from info["negativeKeywords"]
  298. if not info.get("nextToken"):
  299. break
  300. body["nextToken"] = info["nextToken"]
  301. def get_campaignNegativetargeting(self, **body):
  302. url_path = '/sp/campaignNegativeTargets/list'
  303. headers = {
  304. "Accept": "application/vnd.spCampaignNegativeTargetingClause.v3+json",
  305. "Content-Type": "application/vnd.spCampaignNegativeTargetingClause.v3+json"
  306. }
  307. return self._request(url_path, method="POST", body=body, headers=headers)
  308. def iter_campaignNegativetargeting(self, **body):
  309. if "maxResults" not in body:
  310. body["maxResults"] = 100
  311. while True:
  312. info: dict = self.get_campaignNegativetargeting(**body)
  313. yield from info["campaignNegativeTargetingClauses"]
  314. if not info.get("nextToken"):
  315. break
  316. body["nextToken"] = info["nextToken"]
  317. def get_negativetargeting(self, **body):
  318. url_path = '/sp/negativeTargets/list'
  319. headers = {
  320. "Accept": "application/vnd.spNegativeTargetingClause.v3+json",
  321. "Content-Type": "application/vnd.spNegativeTargetingClause.v3+json"
  322. }
  323. return self._request(url_path, method="POST", body=body, headers=headers)
  324. def iter_negativetargeting(self, **body):
  325. if "maxResults" not in body:
  326. body["maxResults"] = 100
  327. while True:
  328. info: dict = self.get_negativetargeting(**body)
  329. yield from info["negativeTargetingClauses"]
  330. if not info.get("nextToken"):
  331. break
  332. body["nextToken"] = info["nextToken"]
  333. def get_targets_bid_recommendations(self,campaignId:str=None,
  334. adGroupId:str=None,
  335. asins:list=None,
  336. bid:float=None,
  337. keyword:str=None,
  338. userSelectedKeyword:bool=False,
  339. matchType:Literal["BROAD","EXACT","PHRASE"]="BROAD",
  340. recommendationType:Literal['KEYWORDS_FOR_ASINS','KEYWORDS_FOR_ADGROUP']="KEYWORDS_FOR_ASINS",
  341. sortDimension:Literal["CLICKS","CONVERSIONS","DEFAULT"]="DEFAULT",
  342. locale:Literal["ar_EG" ,"de_DE", "en_AE", "en_AU", "en_CA", "en_GB", "en_IN", "en_SA", "en_SG", "en_US",
  343. "es_ES", "es_MX", "fr_FR", "it_IT", "ja_JP", "nl_NL", "pl_PL", "pt_BR", "sv_SE", "tr_TR", "zh_CN"]="en_US"):
  344. """
  345. adGroupId与asins只能选择一个参数填写。
  346. @param campaignId: pass
  347. @param adGroupId: 如果recommendationType为KEYWORDS_FOR_ADGROUP时,为必填项
  348. @param asins:如果recommendationType为KEYWORDS_FOR_ASINS时,为必填项
  349. @param userSelectedKeyword:是否参考选择的keyword
  350. @param recommendationType:类型选择,必填
  351. @param locale:pass
  352. """
  353. headers = {"Accept":"application/vnd.spkeywordsrecommendation.v3+json",
  354. "Content-Type":"application/vnd.spkeywordsrecommendation.v3+json"}
  355. url_path = "/sp/targets/keywords/recommendations"
  356. body = {
  357. "recommendationType": recommendationType,
  358. "targets": [
  359. {
  360. "matchType": matchType,
  361. "keyword": keyword,
  362. "bid": bid,
  363. "userSelectedKeyword": userSelectedKeyword
  364. }
  365. ],
  366. "maxRecommendations": "200",
  367. "sortDimension": sortDimension,
  368. "locale": locale
  369. }
  370. if adGroupId is not None:
  371. body["campaignId"]=campaignId
  372. body["adGroupId"]= adGroupId
  373. else:
  374. body['asins'] = asins
  375. return self._request(url_path, method="POST", body=body,headers=headers)
  376. def get_v3_report(self,
  377. groupby:list,
  378. columns:list,
  379. startDate:str,
  380. endDate:str,
  381. reportType: Literal['spCampaigns','spAdvertisedProduct' ,'spPurchasedProduct', 'spTargeting', 'spSearchTerm'],
  382. timeUnit="DAILY",
  383. download=True,
  384. Rewrite=False):
  385. """
  386. @param groupby: 聚合条件,[campaign,adGroup, searchTerm,purchasedAsin,campaignPlacement,targeting,searchTerm,advertiser,asin]
  387. columns: 需要获取的字段
  388. """
  389. #######################################################################################################################################################
  390. if Rewrite:
  391. pid = self.profile_id
  392. report_info = {'groupby': groupby,
  393. 'columns': columns,
  394. 'startDate': startDate,
  395. 'endDate': endDate,
  396. 'reportType': reportType,
  397. 'timeUnit': timeUnit,
  398. 'download': download}
  399. reportrel = self.download_v3_report(report_info, None,
  400. f"s3://reportforspsbsd/zosi/us/sp/{str(groupby)}_{startDate}_{endDate}_{reportType}_{str(pid)}.json.gz")
  401. return reportrel
  402. ##############################################################################################################################################
  403. url_path = "/reporting/reports"
  404. headers = {
  405. "Content-Type":"application/vnd.createasyncreportrequest.v3+json"
  406. }
  407. body = {
  408. "name":"SP campaigns report",
  409. "startDate":startDate,
  410. "endDate":endDate,
  411. "configuration":{
  412. "adProduct":"SPONSORED_PRODUCTS",
  413. "groupBy":groupby,
  414. "columns":columns,
  415. "reportTypeId":reportType,
  416. "timeUnit":timeUnit,
  417. "format":"GZIP_JSON"
  418. }
  419. }
  420. ret = self._request(url_path,method="POST",headers=headers,body=body)
  421. # print(ret)
  422. report_id = ret["reportId"]
  423. status = ret["status"]
  424. if status == "FAILURE":
  425. raise Exception(ret)
  426. logger.info(f"创建报告成功:{ret}")
  427. while status in ["PROCESSING","PENDING"]:
  428. logger.debug(f"报告{report_id}正在处理中...")
  429. time.sleep(4)
  430. try:
  431. ret = self._request(f"/reporting/reports/{report_id}")
  432. except:
  433. time.sleep(15)
  434. ret = self._request(f"/reporting/reports/{report_id}")
  435. print(ret)
  436. status = ret["status"]
  437. if status == "FAILURE":
  438. raise Exception(ret)
  439. logger.info(f"报告处理完成:{ret}")
  440. if download:
  441. pid = self.profile_id
  442. report_info = {'groupby': groupby,
  443. 'columns': columns,
  444. 'startDate': startDate,
  445. 'endDate': endDate,
  446. 'reportType': reportType,
  447. 'timeUnit': timeUnit,
  448. 'download': download}
  449. reportrel= self.download_v3_report(report_info,ret['url'],f"s3://reportforspsbsd/zosi/us/sp/{str(groupby)}_{startDate}_{endDate}_{reportType}_{str(pid)}.json.gz")
  450. return reportrel
  451. else:
  452. return ret
  453. def download_v3_report(self,report_info, url, file_path: str, decompress: bool = True,Rewrite=False) -> str:
  454. ##################################################################################
  455. if Rewrite:
  456. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  457. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  458. 'aws_secret_access_key': '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'}
  459. s3_ = S3FileSystem(client_kwargs=kwargs)
  460. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  461. data = gzip.GzipFile(fileobj=f, mode='rb')
  462. de_file = json.load(data)
  463. # logger.info(f"解压完成:{de_file}")
  464. # print(de_file)
  465. return de_file
  466. ##################################################################################
  467. resp = requests.get(url, stream=True, allow_redirects=True)
  468. # print(resp)
  469. if resp.status_code in [200, 207]:
  470. try:
  471. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  472. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  473. 'aws_secret_access_key': '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'}
  474. s3_ = S3FileSystem(client_kwargs=kwargs)
  475. # print()
  476. with s3_.open(file_path, 'wb') as f:
  477. for data in resp.iter_content(chunk_size=10 * 1024):
  478. f.write(data)
  479. if not decompress:
  480. return file_path
  481. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  482. data = gzip.GzipFile(fileobj=f, mode='rb')
  483. de_file = json.load(data)
  484. # logger.info(f"解压完成:{de_file}")
  485. # print(de_file)
  486. return de_file
  487. except:
  488. try:
  489. with open(f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz", 'wb') as f:
  490. for data in resp.iter_content(chunk_size=10 * 1024):
  491. f.write(data)
  492. if not decompress:
  493. return file_path
  494. with open(f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz", 'rb') as f: # 读取s3数据
  495. data = gzip.GzipFile(fileobj=f, mode='rb')
  496. de_file = json.load(data)
  497. # logger.info(f"解压完成:{de_file}")
  498. # print(de_file)
  499. return de_file
  500. except:
  501. logger.info(f"过期开始重试")
  502. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  503. report_info['endDate'],
  504. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  505. else:
  506. logger.info(f"状态码{resp.status_code},开始重试")
  507. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  508. report_info['endDate'],
  509. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  510. class SBClient(BaseClient):
  511. def get_campaigns(self, **body):
  512. url_path = "/sb/v4/campaigns/list"
  513. headers = {
  514. "Accept": "application/vnd.sbcampaignresouce.v4+json",
  515. "Content-Type": "application/vnd.sbcampaignresouce.v4+json"
  516. }
  517. return self._request(url_path, method="POST", body=body, headers=headers)
  518. def get_campaign_v3(self, campaignId):
  519. if campaignId is None:
  520. url_path = f'/sb/campaigns'
  521. else:
  522. url_path = f'/sb/campaigns/{campaignId}'
  523. return self._request(url_path, method="GET")
  524. def iter_campaigns(self, **body) -> Iterator[dict]:
  525. if "maxResults" not in body:
  526. body["maxResults"] = 100
  527. while True:
  528. info: dict = self.get_campaigns(**body)
  529. yield from info["campaigns"]
  530. if not info.get("nextToken"):
  531. break
  532. body["nextToken"] = info["nextToken"]
  533. # logger.info(f"总共数量:{info['totalResults']}")
  534. def get_ad_groups(self, **body):
  535. url_path = "/sb/v4/adGroups/list"
  536. headers = {
  537. 'Content-Type': "application/vnd.sbadgroupresource.v4+json",
  538. 'Accept': "application/vnd.sbadgroupresource.v4+json"
  539. }
  540. return self._request(url_path, method="POST", headers=headers, body=body)
  541. def iter_adGroups(self, **body) -> Iterator[dict]:
  542. if "maxResults" not in body:
  543. body["maxResults"] = 100
  544. while True:
  545. info: dict = self.get_ad_groups(**body)
  546. # print(info)
  547. yield from info["adGroups"]
  548. if not info.get("nextToken"):
  549. break
  550. body["nextToken"] = info["nextToken"]
  551. def get_ads(self, **body):
  552. url_path = "/sb/v4/ads/list"
  553. headers = {
  554. 'Content-Type': "application/vnd.sbadresource.v4+json",
  555. 'Accept': "application/vnd.sbadresource.v4+json"
  556. }
  557. return self._request(url_path, method="POST", headers=headers, body=body)
  558. def iter_ads(self, **body):
  559. if "maxResults" not in body:
  560. body["maxResults"] = 100
  561. while True:
  562. info: dict = self.get_ads(**body)
  563. # print(info)
  564. yield from info["ads"]
  565. if not info.get("nextToken"):
  566. break
  567. body["nextToken"] = info["nextToken"]
  568. def get_keywords(self,**param):
  569. url_path = "/sb/keywords"
  570. return self._request(url_path, method="GET",params=param)
  571. def get_keyword(self,keywordid):
  572. url_path = f'/sb/keywords/{keywordid}'
  573. return self._request(url_path,method="GET")
  574. def iter_keywords(self,**param):
  575. if "startIndex" not in param:
  576. param["startIndex"] = 0
  577. param["count"] = 5000
  578. while True:
  579. info:list = self.get_keywords(**param)
  580. # print(info)
  581. if len(info) == 0:
  582. break
  583. param["startIndex"] += 5000
  584. yield info
  585. def get_targets(self, **body):
  586. url_path = "/sb/targets/list"
  587. return self._request(url_path, method="POST", body=body)
  588. def iter_targets(self, **body):
  589. if "maxResults" not in body:
  590. body["maxResults"] = 100
  591. while True:
  592. info: dict = self.get_targets(**body)
  593. # print(info)
  594. yield from info["targets"]
  595. if not info.get("nextToken"):
  596. break
  597. body["nextToken"] = info["nextToken"]
  598. def get_budget(self, campaignIds: list):
  599. url_path = "/sb/campaigns/budget/usage"
  600. body = {"campaignIds": campaignIds}
  601. return self._request(url_path, method="POST", body=body)
  602. def get_keyword_bidrecommendation(self, campaignId: str, keywords: list,):
  603. url_path = "/sb/recommendations/bids"
  604. headers = {
  605. "Content-Type": "application/json"
  606. }
  607. body = {
  608. "campaignId": campaignId,
  609. "keywords": keywords
  610. }
  611. return self._request(url_path, method="POST", body=body, headers=headers)
  612. def iter_keyword_bidrecommendation(self,campaignId: str, keywords: list):
  613. for i in range(0, len(keywords), 100):
  614. try:
  615. info = self.get_keyword_bidrecommendation(campaignId, keywords[i:i+100])
  616. yield from info['keywordsBidsRecommendationSuccessResults']
  617. except:
  618. # print("空值")
  619. return iter([])
  620. def get_v3_report(self,
  621. groupby:list,
  622. columns:list,
  623. startDate:str,
  624. endDate:str,
  625. reportType: Literal['sbCampaigns', 'sbPurchasedProduct', 'sbTargeting', 'sbSearchTerm'],
  626. timeUnit="DAILY",
  627. download=True,
  628. Rewrite=False):
  629. """
  630. Now about reportType is only sbPurchasedProduct available.
  631. @param groupby: 聚合条件
  632. @param columns: 需要获取的字段[campaign,purchasedAsin,targeting,searchTerm]
  633. @param startDate: 请求开始的日期
  634. @param endDate: 请求结束的日期
  635. @param reportType: 广告类型
  636. @param timeUnit: 时间指标-[DAILY, SUMMARY]
  637. @param download: 下载报告
  638. """
  639. ##############################################################################################
  640. if Rewrite:
  641. pid = self.profile_id
  642. report_info = {'groupby': groupby,
  643. 'columns': columns,
  644. 'startDate': startDate,
  645. 'endDate': endDate,
  646. 'reportType': reportType,
  647. 'timeUnit': timeUnit,
  648. 'download': download}
  649. reportrel = self.download_v3_report(report_info, None,
  650. f"s3://reportforspsbsd/zosi/us/sb/{startDate}_{endDate}_{reportType}_{str(groupby)}_{str(pid)}.json.gz")
  651. return reportrel
  652. ###############################################################################################
  653. url_path = "/reporting/reports"
  654. headers = {
  655. "Content-Type":"application/vnd.createasyncreportrequest.v3+json"
  656. }
  657. body = {
  658. "name":"SB campaigns report",
  659. "startDate":startDate,
  660. "endDate":endDate,
  661. "configuration":{
  662. "adProduct":"SPONSORED_BRANDS",
  663. "groupBy":groupby,
  664. "columns":columns,
  665. "reportTypeId":reportType,
  666. "timeUnit":timeUnit,
  667. "format":"GZIP_JSON"
  668. }
  669. }
  670. ret = self._request(url_path,method="POST",headers=headers,body=body)
  671. # print(ret)
  672. report_id = ret["reportId"]
  673. status = ret["status"]
  674. if status == "FAILURE":
  675. raise Exception(ret)
  676. logger.info(f"创建报告成功:{ret}")
  677. while status in ["PROCESSING","PENDING"]:
  678. logger.debug(f"报告{report_id}正在处理中...")
  679. time.sleep(4)
  680. try:
  681. ret = self._request(f"/reporting/reports/{report_id}")
  682. except:
  683. time.sleep(15)
  684. ret = self._request(f"/reporting/reports/{report_id}")
  685. print(ret)
  686. status = ret["status"]
  687. if status == "FAILURE":
  688. raise Exception(ret)
  689. logger.info(f"报告处理完成:{ret}")
  690. if download:
  691. pid = self.profile_id
  692. report_info = {'groupby': groupby,
  693. 'columns': columns,
  694. 'startDate': startDate,
  695. 'endDate': endDate,
  696. 'reportType': reportType,
  697. 'timeUnit': timeUnit,
  698. 'download': download}
  699. reportrel= self.download_v3_report(report_info,ret['url'],f"s3://reportforspsbsd/zosi/us/sb/{startDate}_{endDate}_{reportType}_{str(groupby)}_{str(pid)}.json.gz")
  700. return reportrel
  701. else:
  702. return ret
  703. def download_v3_report(self, report_info, url, file_path: str, decompress: bool = True,Rewrite=False) -> str:
  704. ###########################################################################################
  705. if Rewrite:
  706. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  707. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  708. 'aws_secret_access_key': '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'}
  709. s3_ = S3FileSystem(client_kwargs=kwargs)
  710. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  711. data = gzip.GzipFile(fileobj=f, mode='rb')
  712. de_file = json.load(data)
  713. # logger.info(f"解压完成:{de_file}")
  714. # print(de_file)
  715. return de_file
  716. #############################################################################################
  717. resp = requests.get(url, stream=True, allow_redirects=True)
  718. # print(resp)
  719. if resp.status_code in [200, 207]:
  720. try:
  721. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  722. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  723. 'aws_secret_access_key': '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'}
  724. s3_ = S3FileSystem(client_kwargs=kwargs)
  725. # print()
  726. with s3_.open(file_path, 'wb') as f:
  727. for data in resp.iter_content(chunk_size=10 * 1024):
  728. f.write(data)
  729. if not decompress:
  730. return file_path
  731. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  732. data = gzip.GzipFile(fileobj=f, mode='rb')
  733. de_file = json.load(data)
  734. # logger.info(f"解压完成:{de_file}")
  735. # print(de_file)
  736. return de_file
  737. except:
  738. try:
  739. with open(
  740. f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz",
  741. 'wb') as f:
  742. for data in resp.iter_content(chunk_size=10 * 1024):
  743. f.write(data)
  744. if not decompress:
  745. return file_path
  746. with open(
  747. f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz",
  748. 'rb') as f: # 读取s3数据
  749. data = gzip.GzipFile(fileobj=f, mode='rb')
  750. de_file = json.load(data)
  751. # logger.info(f"解压完成:{de_file}")
  752. # print(de_file)
  753. return de_file
  754. except:
  755. logger.info(f"过期开始重试")
  756. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  757. report_info['endDate'],
  758. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  759. else:
  760. logger.info(f"状态码{resp.status_code},开始重试")
  761. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  762. report_info['endDate'],
  763. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  764. def get_v2_report(
  765. self,
  766. record_type: Literal['campaigns', 'adGroups', 'ads', 'targets', 'keywords'],
  767. report_date: str,
  768. metrics: List[str],
  769. segment: Literal['placement', 'query'] = None,
  770. creative_type: Literal['video', 'all'] = "all",
  771. download: bool = True,
  772. Rewrite=False
  773. ):
  774. """
  775. @param download: 是否下载文件
  776. @param record_type:
  777. @param report_date: 格式为YYYYMMDD,以请求的卖家市场所对应的时区为准,超过60天的报告不可用
  778. @param metrics:
  779. @param segment:
  780. @param creative_type:
  781. None:仅包含非视频广告
  782. 'video':仅包含视频广告
  783. 'all':包含视频和非视频广告
  784. @return:
  785. """
  786. ############################################################################################
  787. if Rewrite:
  788. pid = self.profile_id
  789. report_info = {"record_type": record_type, "report_date": report_date, "metrics": metrics,
  790. "segment": segment, "creative_type": creative_type, "download": download}
  791. reportrel = self.download_v2_report(report_info, None,
  792. f"s3://reportforspsbsd/zosi/us/sb/{str(report_date)}_{record_type}_{creative_type}_{segment}_{str(pid)}.gz"
  793. )
  794. return reportrel
  795. #############################################################################################
  796. url = f"/v2/hsa/{record_type}/report"
  797. body = {
  798. "reportDate": report_date,
  799. "metrics": ",".join(metrics),
  800. "creativeType": creative_type,
  801. "segment": segment
  802. }
  803. if record_type == "ads":
  804. body["creativeType"] = "all"
  805. ret = self._request(url, method="POST", body=body)
  806. report_id = ret["reportId"]
  807. status = ret["status"]
  808. if status == "FAILURE":
  809. raise Exception(ret)
  810. logger.info(f"创建报告成功:{ret}")
  811. while status == "IN_PROGRESS":
  812. logger.debug(f"报告{report_id}正在处理中...")
  813. time.sleep(4)
  814. try:
  815. ret = self._request(f"/v2/reports/{report_id}")
  816. except:
  817. time.sleep(15)
  818. ret = self._request(f"/v2/reports/{report_id}")
  819. print(ret)
  820. status = ret["status"]
  821. if status == "FAILURE":
  822. raise Exception(ret)
  823. logger.info(f"报告处理完成:{ret}")
  824. if download:
  825. pid = self.profile_id
  826. report_info = {"record_type": record_type, "report_date": report_date, "metrics": metrics,
  827. "segment": segment, "creative_type": creative_type, "download": download}
  828. reportrel= self.download_v2_report(report_info,report_id, f"s3://reportforspsbsd/zosi/us/sb/{str(report_date)}_{record_type}_{creative_type}_{segment}_{str(pid)}.gz")
  829. return reportrel
  830. else:
  831. return ret
  832. def download_v2_report(self, report_info, report_id: str, file_path: str, decompress: bool = True,Rewrite=False) -> str:
  833. ################################################################################
  834. if Rewrite:
  835. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  836. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  837. 'aws_secret_access_key': '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'}
  838. s3_ = S3FileSystem(client_kwargs=kwargs)
  839. # print()
  840. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  841. data = gzip.GzipFile(fileobj=f, mode='rb')
  842. de_file = json.load(data)
  843. # logger.info(f"解压完成:{de_file}")
  844. # print(de_file)
  845. return de_file
  846. ################################################################################
  847. url = urljoin(URL_AD_API, f"/v2/reports/{report_id}/download")
  848. resp = requests.get(url, headers=self.auth_headers, stream=True, allow_redirects=True)
  849. # print(resp.status_code)
  850. if resp.status_code in [200, 207]:
  851. try:
  852. logger.info(f"开始下载报告:{report_id}")
  853. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  854. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  855. 'aws_secret_access_key': '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'}
  856. s3_ = S3FileSystem(client_kwargs=kwargs)
  857. # print()
  858. with s3_.open(file_path, 'wb') as f:
  859. for data in resp.iter_content(chunk_size=10 * 1024):
  860. # print(resp.text)
  861. f.write(data)
  862. logger.info(f"报告{report_id}下载完成:{file_path}")
  863. if not decompress:
  864. return file_path
  865. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  866. data = gzip.GzipFile(fileobj=f, mode='rb')
  867. de_file = json.load(data)
  868. # logger.info(f"解压完成:{de_file}")
  869. # print(de_file)
  870. return de_file
  871. except:
  872. try:
  873. with open(
  874. f"{report_info['record_type']}_{report_info['report_date']}_{report_info['metrics']}_{report_info['segment']}_{report_info['creative_type']}_{str(self.profile_id)}.json.gz",
  875. 'wb') as f:
  876. for data in resp.iter_content(chunk_size=10 * 1024):
  877. f.write(data)
  878. if not decompress:
  879. return file_path
  880. with open(
  881. f"{report_info['record_type']}_{report_info['report_date']}_{report_info['metrics']}_{report_info['segment']}_{report_info['creative_type']}_{str(self.profile_id)}.json.gz",
  882. 'rb') as f: # 读取s3数据
  883. data = gzip.GzipFile(fileobj=f, mode='rb')
  884. de_file = json.load(data)
  885. # logger.info(f"解压完成:{de_file}")
  886. # print(de_file)
  887. return de_file
  888. except:
  889. logger.info(f"过期开始重试")
  890. self.get_v2_report(report_info['record_type'], report_info['report_date'], report_info['metrics'],
  891. report_info['segment'], report_info['creative_type'], report_info['download'])
  892. else:
  893. logger.info(f"状态码{resp.status_code},开始重试")
  894. self.get_v2_report(report_info['record_type'], report_info['report_date'], report_info['metrics'],
  895. report_info['segment'], report_info['creative_type'], report_info['download'])
  896. class SDClient(BaseClient):
  897. def get_campaigns(self, **params) -> List[dict]:
  898. url_path = "/sd/campaigns"
  899. return self._request(url_path, params=params)
  900. def get_campaigns_extended(self, **params) -> List[dict]:
  901. url_path = "/sd/campaigns/extended"
  902. return self._request(url_path, params=params)
  903. def get_adGroups(self,**params):
  904. url_path = '/sd/adGroups'
  905. return self._request(url_path, params=params)
  906. def iter_adGroups(self,**param):
  907. if "startIndex" not in param:
  908. param["startIndex"] = 0
  909. param["count"] = 5000
  910. while True:
  911. info:list = self.get_adGroups(**param)
  912. # print(info)
  913. if len(info) == 0:
  914. break
  915. param["startIndex"] += 5000
  916. yield info
  917. def get_ads(self,**params):
  918. url_path = '/sd/productAds'
  919. return self._request(url_path, params=params)
  920. def iter_ads(self,**param):
  921. if "startIndex" not in param:
  922. param["startIndex"] = 0
  923. param["count"] = 5000
  924. while True:
  925. info:list = self.get_ads(**param)
  926. # print(info)
  927. if len(info) == 0:
  928. break
  929. param["startIndex"] += 5000
  930. yield info
  931. def get_targets(self,**params):
  932. url_path = '/sd/targets'
  933. return self._request(url_path, params=params)
  934. def iter_targets(self,**param):
  935. if "startIndex" not in param:
  936. param["startIndex"] = 0
  937. param["count"] = 5000
  938. while True:
  939. info:list = self.get_targets(**param)
  940. # print(info)
  941. if len(info) == 0:
  942. break
  943. param["startIndex"] += 5000
  944. yield info
  945. def get_budget(self, campaignIds: list):
  946. url_path = "/sd/campaigns/budget/usage"
  947. body = {"campaignIds": campaignIds}
  948. return self._request(url_path, method="POST", body=body)
  949. def get_target_bidrecommendation(self,tactic:str,products:list,typeFilter:list,themes:dict,locale:str='en_US'):#
  950. url_path = '/sd/targets/recommendations'
  951. headers ={
  952. 'Content-Type':"application/vnd.sdtargetingrecommendations.v3.3+json",
  953. 'Accept':"application/vnd.sdtargetingrecommendations.v3.3+json"
  954. }
  955. # "tactic":"T00020",
  956. # "products":[{"asin":"B00MP57IOY"}],
  957. # "typeFilter":["PRODUCT"],
  958. # "themes":{"product":[{"name":"TEST","expression":[{"type":"asinBrandSameAs"}]}]}
  959. body = {
  960. "tactic":tactic,
  961. "products":products,
  962. "typeFilter":typeFilter,
  963. "themes":themes
  964. }
  965. return self._request(url_path, method="POST", headers=headers,body=body,params={"locale":locale})
  966. def get_v3_report(self,
  967. groupby:list,
  968. columns:list,
  969. startDate:str,
  970. endDate:str,
  971. reportType: Literal['sdCampaigns', 'sdPurchasedProduct', 'sdTargeting', 'sdSearchTerm'],
  972. timeUnit="DAILY",
  973. download=True,
  974. Rewrite=False):
  975. """
  976. Now about reportType is only sbPurchasedProduct available.
  977. @param groupby: 聚合条件
  978. @param columns: 需要获取的字段[campaign,purchasedAsin,targeting,searchTerm]
  979. @param startDate: 请求开始的日期
  980. @param endDate: 请求结束的日期
  981. @param reportType: 广告类型
  982. @param timeUnit: 时间指标-[DAILY, SUMMARY]
  983. @param download: 下载报告
  984. """
  985. ###############################################################################################
  986. if Rewrite:
  987. pid = self.profile_id
  988. report_info = {'groupby': groupby,
  989. 'columns': columns,
  990. 'startDate': startDate,
  991. 'endDate': endDate,
  992. 'reportType': reportType,
  993. 'timeUnit': timeUnit,
  994. 'download': download}
  995. reportrel = self.download_v3_report(report_info, None,
  996. f"s3://reportforspsbsd/zosi/us/sd/{startDate}_{endDate}_{reportType}_{str(groupby)}_{str(pid)}.json.gz")
  997. return reportrel
  998. ##################################################################################################
  999. url_path = "/reporting/reports"
  1000. headers = {
  1001. "Content-Type":"application/vnd.createasyncreportrequest.v3+json"
  1002. }
  1003. body = {
  1004. "name":"SD campaigns report",
  1005. "startDate":startDate,
  1006. "endDate":endDate,
  1007. "configuration":{
  1008. "adProduct":"SPONSORED_DISPLAY",
  1009. "groupBy":groupby,
  1010. "columns":columns,
  1011. "reportTypeId":reportType,
  1012. "timeUnit":timeUnit,
  1013. "format":"GZIP_JSON"
  1014. }
  1015. }
  1016. ret = self._request(url_path,method="POST",headers=headers,body=body)
  1017. # print(ret)
  1018. report_id = ret["reportId"]
  1019. status = ret["status"]
  1020. if status == "FAILURE":
  1021. raise Exception(ret)
  1022. logger.info(f"创建报告成功:{ret}")
  1023. while status in ["PROCESSING","PENDING"]:
  1024. logger.debug(f"报告{report_id}正在处理中...")
  1025. time.sleep(4)
  1026. try:
  1027. ret = self._request(f"/reporting/reports/{report_id}")
  1028. except:
  1029. time.sleep(15)
  1030. ret = self._request(f"/reporting/reports/{report_id}")
  1031. print(ret)
  1032. status = ret["status"]
  1033. if status == "FAILURE":
  1034. raise Exception(ret)
  1035. logger.info(f"报告处理完成:{ret}")
  1036. if download:
  1037. pid = self.profile_id
  1038. report_info = {'groupby':groupby,
  1039. 'columns':columns,
  1040. 'startDate':startDate,
  1041. 'endDate':endDate,
  1042. 'reportType': reportType,
  1043. 'timeUnit':timeUnit,
  1044. 'download':download}
  1045. reportrel= self.download_v3_report(report_info,ret['url'],f"s3://reportforspsbsd/zosi/us/sd/{startDate}_{endDate}_{reportType}_{str(groupby)}_{str(pid)}.json.gz")
  1046. return reportrel
  1047. else:
  1048. return ret
  1049. def download_v3_report(self, report_info, url, file_path: str, decompress: bool = True,Rewrite=False) -> str:
  1050. #######################################################################
  1051. if Rewrite:
  1052. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  1053. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  1054. 'aws_secret_access_key': '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'}
  1055. s3_ = S3FileSystem(client_kwargs=kwargs)
  1056. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  1057. data = gzip.GzipFile(fileobj=f, mode='rb')
  1058. de_file = json.load(data)
  1059. # logger.info(f"解压完成:{de_file}")
  1060. # print(de_file)
  1061. return de_file
  1062. # logger.info(f"解压完成:{de_file}")
  1063. # print(de_file)
  1064. #########################################################################
  1065. resp = requests.get(url, stream=True, allow_redirects=True)
  1066. # print(resp)
  1067. if resp.status_code in [200, 207]:
  1068. try:
  1069. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  1070. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  1071. 'aws_secret_access_key': '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'}
  1072. s3_ = S3FileSystem(client_kwargs=kwargs)
  1073. # print()
  1074. with s3_.open(file_path, 'wb') as f:
  1075. for data in resp.iter_content(chunk_size=10 * 1024):
  1076. f.write(data)
  1077. if not decompress:
  1078. return file_path
  1079. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  1080. data = gzip.GzipFile(fileobj=f, mode='rb')
  1081. de_file = json.load(data)
  1082. # logger.info(f"解压完成:{de_file}")
  1083. # print(de_file)
  1084. return de_file
  1085. except:
  1086. try:
  1087. with open(
  1088. f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz",
  1089. 'wb') as f:
  1090. for data in resp.iter_content(chunk_size=10 * 1024):
  1091. f.write(data)
  1092. if not decompress:
  1093. return file_path
  1094. with open(
  1095. f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz",
  1096. 'rb') as f: # 读取s3数据
  1097. data = gzip.GzipFile(fileobj=f, mode='rb')
  1098. de_file = json.load(data)
  1099. # logger.info(f"解压完成:{de_file}")
  1100. # print(de_file)
  1101. return de_file
  1102. except:
  1103. logger.info(f"过期开始重试")
  1104. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  1105. report_info['endDate'],
  1106. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  1107. else:
  1108. logger.info(f"状态码{resp.status_code},开始重试")
  1109. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  1110. report_info['endDate'],
  1111. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  1112. def get_v2_report(
  1113. self,
  1114. record_type: Literal['campaigns', 'adGroups', 'productAds', 'targets', 'asins'],
  1115. report_date: str,
  1116. metrics: List[str],
  1117. segment: Literal['matchedTarget'] = None,
  1118. tactic: Literal['T00020', 'T00030'] = None,
  1119. download: bool = True,
  1120. Rewrite=False
  1121. ):
  1122. """
  1123. @param download: 是否下载文件
  1124. @param record_type:
  1125. @param report_date: 格式为YYYYMMDD,以请求的卖家市场所对应的时区为准,超过60天的报告不可用
  1126. @param metrics:
  1127. @param segment:
  1128. @param tactic:
  1129. T00020: contextual targeting
  1130. T00030: audience targeting
  1131. @return:
  1132. """
  1133. url = f"/sd/{record_type}/report"
  1134. body = {
  1135. "reportDate": report_date,
  1136. "metrics": ",".join(metrics),
  1137. "tactic": tactic,
  1138. "segment": segment
  1139. }
  1140. ret = self._request(url, method="POST", body=body)
  1141. report_id = ret["reportId"]
  1142. status = ret["status"]
  1143. print(ret)
  1144. if status == "FAILURE":
  1145. raise Exception(ret)
  1146. logger.info(f"创建报告成功:{ret}")
  1147. while status == "IN_PROGRESS":
  1148. logger.debug(f"报告{report_id}正在处理中...")
  1149. time.sleep(4)
  1150. try:
  1151. ret = self._request(f"/v2/reports/{report_id}")
  1152. except:
  1153. time.sleep(15)
  1154. ret = self._request(f"/v2/reports/{report_id}")
  1155. print(ret)
  1156. status = ret["status"]
  1157. if status == "FAILURE":
  1158. raise Exception(ret)
  1159. logger.info(f"报告处理完成:{ret}")
  1160. if download:
  1161. pid = self.profile_id
  1162. report_info = {"record_type":record_type,"report_date":report_date,"metrics":metrics,"segment":segment,"tactic":tactic,"download":download}
  1163. reportrel= self.download_v2_report(report_info,report_id, f"s3://reportforspsbsd/zosi/us/sd/{str(report_date)}_{record_type}_{tactic}_{segment}_{str(pid)}.gz")
  1164. return reportrel
  1165. else:
  1166. return ret
  1167. def download_v2_report(self, report_info,report_id: str, file_path: str, decompress: bool = True) -> str:
  1168. url = urljoin(URL_AD_API, f"/v2/reports/{report_id}/download")
  1169. resp = requests.get(url, headers=self.auth_headers, stream=True, allow_redirects=True)
  1170. # print(resp.status_code)
  1171. if resp.status_code in [200,207]:
  1172. try:
  1173. logger.info(f"开始下载报告:{report_id}")
  1174. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  1175. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  1176. 'aws_secret_access_key': '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'}
  1177. s3_ = S3FileSystem(client_kwargs=kwargs)
  1178. # print()
  1179. with s3_.open(file_path, 'wb') as f:
  1180. for data in resp.iter_content(chunk_size=10 * 1024):
  1181. # print(resp.text)
  1182. f.write(data)
  1183. logger.info(f"报告{report_id}下载完成:{file_path}")
  1184. if not decompress:
  1185. return file_path
  1186. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  1187. data = gzip.GzipFile(fileobj=f, mode='rb')
  1188. de_file = json.load(data)
  1189. # logger.info(f"解压完成:{de_file}")
  1190. # print(de_file)
  1191. return de_file
  1192. except:
  1193. try:
  1194. with open(
  1195. f"{report_info['record_type']}_{report_info['report_date']}_{report_info['metrics']}_{report_info['segment']}_{report_info['tactic']}_{str(self.profile_id)}.json.gz",
  1196. 'wb') as f:
  1197. for data in resp.iter_content(chunk_size=10 * 1024):
  1198. f.write(data)
  1199. if not decompress:
  1200. return file_path
  1201. with open(
  1202. f"{report_info['record_type']}_{report_info['report_date']}_{report_info['metrics']}_{report_info['segment']}_{report_info['tactic']}_{str(self.profile_id)}.json.gz",
  1203. 'rb') as f: # 读取s3数据
  1204. data = gzip.GzipFile(fileobj=f, mode='rb')
  1205. de_file = json.load(data)
  1206. # logger.info(f"解压完成:{de_file}")
  1207. # print(de_file)
  1208. return de_file
  1209. except:
  1210. logger.info(f"过期开始重试")
  1211. self.get_v2_report(report_info['record_type'], report_info['report_date'], report_info['metrics'],
  1212. report_info['segment'], report_info['tactic'], report_info['download'])
  1213. else:
  1214. logger.info(f"状态码{resp.status_code},开始重试")
  1215. self.get_v2_report(report_info['record_type'],report_info['report_date'],report_info['metrics'],
  1216. report_info['segment'],report_info['tactic'],report_info['download'])
  1217. class Account(BaseClient):
  1218. def get_portfolios(self):
  1219. url_path = "/v2/portfolios/extended"
  1220. return self._request(url_path)
  1221. def iter_portfolios(self):
  1222. yield from self.get_portfolios()
  1223. AccountClient = Account
  1224. if __name__ == '__main__':
  1225. AWS_CREDENTIALS = {
  1226. 'lwa_client_id': 'amzn1.application-oa2-client.ebd701cd07854fb38c37ee49ec4ba109',
  1227. 'refresh_token': "Atzr|IwEBIL4ur8kbcwRyxVu_srprAAoTYzujnBvA6jU-0SMxkRgOhGjYJSUNGKvw24EQwJa1jG5RM76mQD2P22AKSq8qSD94LddoXGdKDO74eQVYl0RhuqOMFqdrEZpp1p4bIR6_N8VeSJDHr7UCuo8FiabkSHrkq7tsNvRP-yI-bnpQv4EayPBh7YwHVX3hYdRbhxaBvgJENgCuiEPb35Q2-Z6w6ujjiKUAK2VSbCFpENlEfcHNsjDeY7RCvFlwlCoHj1IeiNIaFTE9yXFu3aEWlExe3LzHv6PZyunEi88QJSXKSh56Um0e0eEg05rMv-VBM83cAqc5POmZnTP1vUdZO8fQv3NFLZ-xU6e1WQVxVPi5Cyqk4jYhGf1Y9t98N654y0tVvw74qNIsTrB-8bGS0Uhfe24oBEWmzObvBY3zhtT1d42myGUJv4pMTU6yPoS83zhPKm3LbUDEpBA1hvvc_09jHk7vUEAuFB-UAZzlht2C1yklzQ",
  1228. 'lwa_client_secret': 'cbf0514186db4df91e04a8905f0a91b605eae4201254ced879d8bb90df4b474d',
  1229. 'profile_id': "3006125408623189"
  1230. }
  1231. sp = SPClient(**AWS_CREDENTIALS)
  1232. rel = sp.iter_negativekeyword()
  1233. print(list(rel))
  1234. # print(rel)