amz_ad_client.py 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320
  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, **body):
  603. url_path = "/sb/recommendations/bids"
  604. return self._request(url_path, method="POST", body=body)
  605. def get_v3_report(self,
  606. groupby:list,
  607. columns:list,
  608. startDate:str,
  609. endDate:str,
  610. reportType: Literal['sbCampaigns', 'sbPurchasedProduct', 'sbTargeting', 'sbSearchTerm'],
  611. timeUnit="DAILY",
  612. download=True,
  613. Rewrite=False):
  614. """
  615. Now about reportType is only sbPurchasedProduct available.
  616. @param groupby: 聚合条件
  617. @param columns: 需要获取的字段[campaign,purchasedAsin,targeting,searchTerm]
  618. @param startDate: 请求开始的日期
  619. @param endDate: 请求结束的日期
  620. @param reportType: 广告类型
  621. @param timeUnit: 时间指标-[DAILY, SUMMARY]
  622. @param download: 下载报告
  623. """
  624. ##############################################################################################
  625. if Rewrite:
  626. pid = self.profile_id
  627. report_info = {'groupby': groupby,
  628. 'columns': columns,
  629. 'startDate': startDate,
  630. 'endDate': endDate,
  631. 'reportType': reportType,
  632. 'timeUnit': timeUnit,
  633. 'download': download}
  634. reportrel = self.download_v3_report(report_info, None,
  635. f"s3://reportforspsbsd/zosi/us/sb/{startDate}_{endDate}_{reportType}_{str(groupby)}_{str(pid)}.json.gz")
  636. return reportrel
  637. ###############################################################################################
  638. url_path = "/reporting/reports"
  639. headers = {
  640. "Content-Type":"application/vnd.createasyncreportrequest.v3+json"
  641. }
  642. body = {
  643. "name":"SB campaigns report",
  644. "startDate":startDate,
  645. "endDate":endDate,
  646. "configuration":{
  647. "adProduct":"SPONSORED_BRANDS",
  648. "groupBy":groupby,
  649. "columns":columns,
  650. "reportTypeId":reportType,
  651. "timeUnit":timeUnit,
  652. "format":"GZIP_JSON"
  653. }
  654. }
  655. ret = self._request(url_path,method="POST",headers=headers,body=body)
  656. # print(ret)
  657. report_id = ret["reportId"]
  658. status = ret["status"]
  659. if status == "FAILURE":
  660. raise Exception(ret)
  661. logger.info(f"创建报告成功:{ret}")
  662. while status in ["PROCESSING","PENDING"]:
  663. logger.debug(f"报告{report_id}正在处理中...")
  664. time.sleep(4)
  665. try:
  666. ret = self._request(f"/reporting/reports/{report_id}")
  667. except:
  668. time.sleep(15)
  669. ret = self._request(f"/reporting/reports/{report_id}")
  670. print(ret)
  671. status = ret["status"]
  672. if status == "FAILURE":
  673. raise Exception(ret)
  674. logger.info(f"报告处理完成:{ret}")
  675. if download:
  676. pid = self.profile_id
  677. report_info = {'groupby': groupby,
  678. 'columns': columns,
  679. 'startDate': startDate,
  680. 'endDate': endDate,
  681. 'reportType': reportType,
  682. 'timeUnit': timeUnit,
  683. 'download': download}
  684. reportrel= self.download_v3_report(report_info,ret['url'],f"s3://reportforspsbsd/zosi/us/sb/{startDate}_{endDate}_{reportType}_{str(groupby)}_{str(pid)}.json.gz")
  685. return reportrel
  686. else:
  687. return ret
  688. def download_v3_report(self, report_info, url, file_path: str, decompress: bool = True,Rewrite=False) -> str:
  689. ###########################################################################################
  690. if Rewrite:
  691. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  692. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  693. 'aws_secret_access_key': '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'}
  694. s3_ = S3FileSystem(client_kwargs=kwargs)
  695. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  696. data = gzip.GzipFile(fileobj=f, mode='rb')
  697. de_file = json.load(data)
  698. # logger.info(f"解压完成:{de_file}")
  699. # print(de_file)
  700. return de_file
  701. #############################################################################################
  702. resp = requests.get(url, stream=True, allow_redirects=True)
  703. # print(resp)
  704. if resp.status_code in [200, 207]:
  705. try:
  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. # print()
  711. with s3_.open(file_path, 'wb') as f:
  712. for data in resp.iter_content(chunk_size=10 * 1024):
  713. f.write(data)
  714. if not decompress:
  715. return file_path
  716. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  717. data = gzip.GzipFile(fileobj=f, mode='rb')
  718. de_file = json.load(data)
  719. # logger.info(f"解压完成:{de_file}")
  720. # print(de_file)
  721. return de_file
  722. except:
  723. try:
  724. with open(
  725. f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz",
  726. '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 open(
  732. f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz",
  733. 'rb') as f: # 读取s3数据
  734. data = gzip.GzipFile(fileobj=f, mode='rb')
  735. de_file = json.load(data)
  736. # logger.info(f"解压完成:{de_file}")
  737. # print(de_file)
  738. return de_file
  739. except:
  740. logger.info(f"过期开始重试")
  741. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  742. report_info['endDate'],
  743. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  744. else:
  745. logger.info(f"状态码{resp.status_code},开始重试")
  746. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  747. report_info['endDate'],
  748. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  749. def get_v2_report(
  750. self,
  751. record_type: Literal['campaigns', 'adGroups', 'ads', 'targets', 'keywords'],
  752. report_date: str,
  753. metrics: List[str],
  754. segment: Literal['placement', 'query'] = None,
  755. creative_type: Literal['video', 'all'] = "all",
  756. download: bool = True,
  757. Rewrite=False
  758. ):
  759. """
  760. @param download: 是否下载文件
  761. @param record_type:
  762. @param report_date: 格式为YYYYMMDD,以请求的卖家市场所对应的时区为准,超过60天的报告不可用
  763. @param metrics:
  764. @param segment:
  765. @param creative_type:
  766. None:仅包含非视频广告
  767. 'video':仅包含视频广告
  768. 'all':包含视频和非视频广告
  769. @return:
  770. """
  771. ############################################################################################
  772. if Rewrite:
  773. pid = self.profile_id
  774. report_info = {"record_type": record_type, "report_date": report_date, "metrics": metrics,
  775. "segment": segment, "creative_type": creative_type, "download": download}
  776. reportrel = self.download_v2_report(report_info, None,
  777. f"s3://reportforspsbsd/zosi/us/sb/{str(report_date)}_{record_type}_{creative_type}_{segment}_{str(pid)}.gz"
  778. )
  779. return reportrel
  780. #############################################################################################
  781. url = f"/v2/hsa/{record_type}/report"
  782. body = {
  783. "reportDate": report_date,
  784. "metrics": ",".join(metrics),
  785. "creativeType": creative_type,
  786. "segment": segment
  787. }
  788. if record_type == "ads":
  789. body["creativeType"] = "all"
  790. ret = self._request(url, method="POST", body=body)
  791. report_id = ret["reportId"]
  792. status = ret["status"]
  793. if status == "FAILURE":
  794. raise Exception(ret)
  795. logger.info(f"创建报告成功:{ret}")
  796. while status == "IN_PROGRESS":
  797. logger.debug(f"报告{report_id}正在处理中...")
  798. time.sleep(4)
  799. try:
  800. ret = self._request(f"/v2/reports/{report_id}")
  801. except:
  802. time.sleep(15)
  803. ret = self._request(f"/v2/reports/{report_id}")
  804. print(ret)
  805. status = ret["status"]
  806. if status == "FAILURE":
  807. raise Exception(ret)
  808. logger.info(f"报告处理完成:{ret}")
  809. if download:
  810. pid = self.profile_id
  811. report_info = {"record_type": record_type, "report_date": report_date, "metrics": metrics,
  812. "segment": segment, "creative_type": creative_type, "download": download}
  813. 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")
  814. return reportrel
  815. else:
  816. return ret
  817. def download_v2_report(self, report_info, report_id: str, file_path: str, decompress: bool = True,Rewrite=False) -> str:
  818. ################################################################################
  819. if Rewrite:
  820. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  821. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  822. 'aws_secret_access_key': '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'}
  823. s3_ = S3FileSystem(client_kwargs=kwargs)
  824. # print()
  825. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  826. data = gzip.GzipFile(fileobj=f, mode='rb')
  827. de_file = json.load(data)
  828. # logger.info(f"解压完成:{de_file}")
  829. # print(de_file)
  830. return de_file
  831. ################################################################################
  832. url = urljoin(URL_AD_API, f"/v2/reports/{report_id}/download")
  833. resp = requests.get(url, headers=self.auth_headers, stream=True, allow_redirects=True)
  834. # print(resp.status_code)
  835. if resp.status_code in [200, 207]:
  836. try:
  837. logger.info(f"开始下载报告:{report_id}")
  838. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  839. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  840. 'aws_secret_access_key': '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'}
  841. s3_ = S3FileSystem(client_kwargs=kwargs)
  842. # print()
  843. with s3_.open(file_path, 'wb') as f:
  844. for data in resp.iter_content(chunk_size=10 * 1024):
  845. # print(resp.text)
  846. f.write(data)
  847. logger.info(f"报告{report_id}下载完成:{file_path}")
  848. if not decompress:
  849. return file_path
  850. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  851. data = gzip.GzipFile(fileobj=f, mode='rb')
  852. de_file = json.load(data)
  853. # logger.info(f"解压完成:{de_file}")
  854. # print(de_file)
  855. return de_file
  856. except:
  857. try: #s3://reportforspsbsd/zosi/us/sb/{str(report_date)}_{record_type}_{creative_type}_{segment}_{str(pid)}.gz
  858. # report_info = {"record_type": record_type, "report_date": report_date, "metrics": metrics,
  859. # "segment": segment, "creative_type": creative_type, "download": download}
  860. with open(
  861. 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",
  862. 'wb') as f:
  863. for data in resp.iter_content(chunk_size=10 * 1024):
  864. f.write(data)
  865. if not decompress:
  866. return file_path
  867. with open(
  868. 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",
  869. 'rb') as f: # 读取s3数据
  870. data = gzip.GzipFile(fileobj=f, mode='rb')
  871. de_file = json.load(data)
  872. # logger.info(f"解压完成:{de_file}")
  873. # print(de_file)
  874. return de_file
  875. except:
  876. logger.info(f"过期开始重试")
  877. self.get_v2_report(report_info['record_type'], report_info['report_date'], report_info['metrics'],
  878. report_info['segment'], report_info['creative_type'], report_info['download'])
  879. else:
  880. logger.info(f"状态码{resp.status_code},开始重试")
  881. self.get_v2_report(report_info['record_type'], report_info['report_date'], report_info['metrics'],
  882. report_info['segment'], report_info['creative_type'], report_info['download'])
  883. class SDClient(BaseClient):
  884. def get_campaigns(self, **params) -> List[dict]:
  885. url_path = "/sd/campaigns"
  886. return self._request(url_path, params=params)
  887. def get_campaigns_extended(self, **params) -> List[dict]:
  888. url_path = "/sd/campaigns/extended"
  889. return self._request(url_path, params=params)
  890. def get_adGroups(self,**params):
  891. url_path = '/sd/adGroups'
  892. return self._request(url_path, params=params)
  893. def iter_adGroups(self,**param):
  894. if "startIndex" not in param:
  895. param["startIndex"] = 0
  896. param["count"] = 5000
  897. while True:
  898. info:list = self.get_adGroups(**param)
  899. # print(info)
  900. if len(info) == 0:
  901. break
  902. param["startIndex"] += 5000
  903. yield info
  904. def get_ads(self,**params):
  905. url_path = '/sd/productAds'
  906. return self._request(url_path, params=params)
  907. def iter_ads(self,**param):
  908. if "startIndex" not in param:
  909. param["startIndex"] = 0
  910. param["count"] = 5000
  911. while True:
  912. info:list = self.get_ads(**param)
  913. # print(info)
  914. if len(info) == 0:
  915. break
  916. param["startIndex"] += 5000
  917. yield info
  918. def get_targets(self,**params):
  919. url_path = '/sd/targets'
  920. return self._request(url_path, params=params)
  921. def iter_targets(self,**param):
  922. if "startIndex" not in param:
  923. param["startIndex"] = 0
  924. param["count"] = 5000
  925. while True:
  926. info:list = self.get_targets(**param)
  927. # print(info)
  928. if len(info) == 0:
  929. break
  930. param["startIndex"] += 5000
  931. yield info
  932. def get_budget(self, campaignIds: list):
  933. url_path = "/sd/campaigns/budget/usage"
  934. body = {"campaignIds": campaignIds}
  935. return self._request(url_path, method="POST", body=body)
  936. def get_target_bidrecommendation(self,tactic:str,products:list,typeFilter:list,themes:dict,locale:str='en_US'):#
  937. url_path = '/sd/targets/recommendations'
  938. headers ={
  939. 'Content-Type':"application/vnd.sdtargetingrecommendations.v3.3+json",
  940. 'Accept':"application/vnd.sdtargetingrecommendations.v3.3+json"
  941. }
  942. # "tactic":"T00020",
  943. # "products":[{"asin":"B00MP57IOY"}],
  944. # "typeFilter":["PRODUCT"],
  945. # "themes":{"product":[{"name":"TEST","expression":[{"type":"asinBrandSameAs"}]}]}
  946. body = {
  947. "tactic":tactic,
  948. "products":products,
  949. "typeFilter":typeFilter,
  950. "themes":themes
  951. }
  952. return self._request(url_path, method="POST", headers=headers,body=body,params={"locale":locale})
  953. def get_v3_report(self,
  954. groupby:list,
  955. columns:list,
  956. startDate:str,
  957. endDate:str,
  958. reportType: Literal['sdCampaigns', 'sdPurchasedProduct', 'sdTargeting', 'sdSearchTerm'],
  959. timeUnit="DAILY",
  960. download=True,
  961. Rewrite=False):
  962. """
  963. Now about reportType is only sbPurchasedProduct available.
  964. @param groupby: 聚合条件
  965. @param columns: 需要获取的字段[campaign,purchasedAsin,targeting,searchTerm]
  966. @param startDate: 请求开始的日期
  967. @param endDate: 请求结束的日期
  968. @param reportType: 广告类型
  969. @param timeUnit: 时间指标-[DAILY, SUMMARY]
  970. @param download: 下载报告
  971. """
  972. ###############################################################################################
  973. if Rewrite:
  974. pid = self.profile_id
  975. report_info = {'groupby': groupby,
  976. 'columns': columns,
  977. 'startDate': startDate,
  978. 'endDate': endDate,
  979. 'reportType': reportType,
  980. 'timeUnit': timeUnit,
  981. 'download': download}
  982. reportrel = self.download_v3_report(report_info, None,
  983. f"s3://reportforspsbsd/zosi/us/sd/{startDate}_{endDate}_{reportType}_{str(groupby)}_{str(pid)}.json.gz")
  984. return reportrel
  985. ##################################################################################################
  986. url_path = "/reporting/reports"
  987. headers = {
  988. "Content-Type":"application/vnd.createasyncreportrequest.v3+json"
  989. }
  990. body = {
  991. "name":"SD campaigns report",
  992. "startDate":startDate,
  993. "endDate":endDate,
  994. "configuration":{
  995. "adProduct":"SPONSORED_DISPLAY",
  996. "groupBy":groupby,
  997. "columns":columns,
  998. "reportTypeId":reportType,
  999. "timeUnit":timeUnit,
  1000. "format":"GZIP_JSON"
  1001. }
  1002. }
  1003. ret = self._request(url_path,method="POST",headers=headers,body=body)
  1004. # print(ret)
  1005. report_id = ret["reportId"]
  1006. status = ret["status"]
  1007. if status == "FAILURE":
  1008. raise Exception(ret)
  1009. logger.info(f"创建报告成功:{ret}")
  1010. while status in ["PROCESSING","PENDING"]:
  1011. logger.debug(f"报告{report_id}正在处理中...")
  1012. time.sleep(4)
  1013. try:
  1014. ret = self._request(f"/reporting/reports/{report_id}")
  1015. except:
  1016. time.sleep(15)
  1017. ret = self._request(f"/reporting/reports/{report_id}")
  1018. print(ret)
  1019. status = ret["status"]
  1020. if status == "FAILURE":
  1021. raise Exception(ret)
  1022. logger.info(f"报告处理完成:{ret}")
  1023. if download:
  1024. pid = self.profile_id
  1025. report_info = {'groupby':groupby,
  1026. 'columns':columns,
  1027. 'startDate':startDate,
  1028. 'endDate':endDate,
  1029. 'reportType': reportType,
  1030. 'timeUnit':timeUnit,
  1031. 'download':download}
  1032. reportrel= self.download_v3_report(report_info,ret['url'],f"s3://reportforspsbsd/zosi/us/sd/{startDate}_{endDate}_{reportType}_{str(groupby)}_{str(pid)}.json.gz")
  1033. return reportrel
  1034. else:
  1035. return ret
  1036. def download_v3_report(self, report_info, url, file_path: str, decompress: bool = True,Rewrite=False) -> str:
  1037. #######################################################################
  1038. if Rewrite:
  1039. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  1040. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  1041. 'aws_secret_access_key': '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'}
  1042. s3_ = S3FileSystem(client_kwargs=kwargs)
  1043. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  1044. data = gzip.GzipFile(fileobj=f, mode='rb')
  1045. de_file = json.load(data)
  1046. # logger.info(f"解压完成:{de_file}")
  1047. # print(de_file)
  1048. return de_file
  1049. # logger.info(f"解压完成:{de_file}")
  1050. # print(de_file)
  1051. #########################################################################
  1052. resp = requests.get(url, stream=True, allow_redirects=True)
  1053. # print(resp)
  1054. if resp.status_code in [200, 207]:
  1055. try:
  1056. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  1057. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  1058. 'aws_secret_access_key': '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'}
  1059. s3_ = S3FileSystem(client_kwargs=kwargs)
  1060. # print()
  1061. with s3_.open(file_path, 'wb') as f:
  1062. for data in resp.iter_content(chunk_size=10 * 1024):
  1063. f.write(data)
  1064. if not decompress:
  1065. return file_path
  1066. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  1067. data = gzip.GzipFile(fileobj=f, mode='rb')
  1068. de_file = json.load(data)
  1069. # logger.info(f"解压完成:{de_file}")
  1070. # print(de_file)
  1071. return de_file
  1072. except:
  1073. try:
  1074. with open(
  1075. f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz",
  1076. 'wb') as f:
  1077. for data in resp.iter_content(chunk_size=10 * 1024):
  1078. f.write(data)
  1079. if not decompress:
  1080. return file_path
  1081. with open(
  1082. f"{report_info['groupby']}_{report_info['startDate']}_{report_info['endDate']}_{report_info['reportType']}_{str(self.profile_id)}.json.gz",
  1083. 'rb') as f: # 读取s3数据
  1084. data = gzip.GzipFile(fileobj=f, mode='rb')
  1085. de_file = json.load(data)
  1086. # logger.info(f"解压完成:{de_file}")
  1087. # print(de_file)
  1088. return de_file
  1089. except:
  1090. logger.info(f"过期开始重试")
  1091. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  1092. report_info['endDate'],
  1093. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  1094. else:
  1095. logger.info(f"状态码{resp.status_code},开始重试")
  1096. self.get_v3_report(report_info['groupby'], report_info['columns'], report_info['startDate'],
  1097. report_info['endDate'],
  1098. report_info['reportType'], report_info['timeUnit'], report_info['download'])
  1099. def get_v2_report(
  1100. self,
  1101. record_type: Literal['campaigns', 'adGroups', 'productAds', 'targets', 'asins'],
  1102. report_date: str,
  1103. metrics: List[str],
  1104. segment: Literal['matchedTarget'] = None,
  1105. tactic: Literal['T00020', 'T00030'] = None,
  1106. download: bool = True,
  1107. Rewrite=False
  1108. ):
  1109. """
  1110. @param download: 是否下载文件
  1111. @param record_type:
  1112. @param report_date: 格式为YYYYMMDD,以请求的卖家市场所对应的时区为准,超过60天的报告不可用
  1113. @param metrics:
  1114. @param segment:
  1115. @param tactic:
  1116. T00020: contextual targeting
  1117. T00030: audience targeting
  1118. @return:
  1119. """
  1120. url = f"/sd/{record_type}/report"
  1121. body = {
  1122. "reportDate": report_date,
  1123. "metrics": ",".join(metrics),
  1124. "tactic": tactic,
  1125. "segment": segment
  1126. }
  1127. ret = self._request(url, method="POST", body=body)
  1128. report_id = ret["reportId"]
  1129. status = ret["status"]
  1130. print(ret)
  1131. if status == "FAILURE":
  1132. raise Exception(ret)
  1133. logger.info(f"创建报告成功:{ret}")
  1134. while status == "IN_PROGRESS":
  1135. logger.debug(f"报告{report_id}正在处理中...")
  1136. time.sleep(4)
  1137. try:
  1138. ret = self._request(f"/v2/reports/{report_id}")
  1139. except:
  1140. time.sleep(15)
  1141. ret = self._request(f"/v2/reports/{report_id}")
  1142. print(ret)
  1143. status = ret["status"]
  1144. if status == "FAILURE":
  1145. raise Exception(ret)
  1146. logger.info(f"报告处理完成:{ret}")
  1147. if download:
  1148. pid = self.profile_id
  1149. report_info = {"record_type":record_type,"report_date":report_date,"metrics":metrics,"segment":segment,"tactic":tactic,"download":download}
  1150. 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")
  1151. return reportrel
  1152. else:
  1153. return ret
  1154. def download_v2_report(self, report_info,report_id: str, file_path: str, decompress: bool = True) -> str:
  1155. url = urljoin(URL_AD_API, f"/v2/reports/{report_id}/download")
  1156. resp = requests.get(url, headers=self.auth_headers, stream=True, allow_redirects=True)
  1157. # print(resp.status_code)
  1158. if resp.status_code in [200,207]:
  1159. try:
  1160. logger.info(f"开始下载报告:{report_id}")
  1161. kwargs = {'region_name': 'us-east-1', 'endpoint_url': "https://s3.amazonaws.com",
  1162. 'aws_access_key_id': 'AKIARBAGHTGORIFN44VQ',
  1163. 'aws_secret_access_key': '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'}
  1164. s3_ = S3FileSystem(client_kwargs=kwargs)
  1165. # print()
  1166. with s3_.open(file_path, 'wb') as f:
  1167. for data in resp.iter_content(chunk_size=10 * 1024):
  1168. # print(resp.text)
  1169. f.write(data)
  1170. logger.info(f"报告{report_id}下载完成:{file_path}")
  1171. if not decompress:
  1172. return file_path
  1173. with s3_.open(file_path, 'rb') as f: # 读取s3数据
  1174. data = gzip.GzipFile(fileobj=f, mode='rb')
  1175. de_file = json.load(data)
  1176. # logger.info(f"解压完成:{de_file}")
  1177. # print(de_file)
  1178. return de_file
  1179. except:
  1180. try:
  1181. with open(
  1182. f"{report_info['record_type']}_{report_info['report_date']}_{report_info['metrics']}_{report_info['segment']}_{report_info['tactic']}_{str(self.profile_id)}.json.gz",
  1183. 'wb') as f:
  1184. for data in resp.iter_content(chunk_size=10 * 1024):
  1185. f.write(data)
  1186. if not decompress:
  1187. return file_path
  1188. with open(
  1189. f"{report_info['record_type']}_{report_info['report_date']}_{report_info['metrics']}_{report_info['segment']}_{report_info['tactic']}_{str(self.profile_id)}.json.gz",
  1190. 'rb') as f: # 读取s3数据
  1191. data = gzip.GzipFile(fileobj=f, mode='rb')
  1192. de_file = json.load(data)
  1193. # logger.info(f"解压完成:{de_file}")
  1194. # print(de_file)
  1195. return de_file
  1196. except:
  1197. logger.info(f"过期开始重试")
  1198. self.get_v2_report(report_info['record_type'], report_info['report_date'], report_info['metrics'],
  1199. report_info['segment'], report_info['tactic'], report_info['download'])
  1200. else:
  1201. logger.info(f"状态码{resp.status_code},开始重试")
  1202. self.get_v2_report(report_info['record_type'],report_info['report_date'],report_info['metrics'],
  1203. report_info['segment'],report_info['tactic'],report_info['download'])
  1204. class Account(BaseClient):
  1205. def get_portfolios(self):
  1206. url_path = "/v2/portfolios/extended"
  1207. return self._request(url_path)
  1208. def iter_portfolios(self):
  1209. yield from self.get_portfolios()
  1210. AccountClient = Account
  1211. if __name__ == '__main__':
  1212. AWS_CREDENTIALS = {
  1213. 'lwa_client_id': 'amzn1.application-oa2-client.ebd701cd07854fb38c37ee49ec4ba109',
  1214. 'refresh_token': "Atzr|IwEBIL4ur8kbcwRyxVu_srprAAoTYzujnBvA6jU-0SMxkRgOhGjYJSUNGKvw24EQwJa1jG5RM76mQD2P22AKSq8qSD94LddoXGdKDO74eQVYl0RhuqOMFqdrEZpp1p4bIR6_N8VeSJDHr7UCuo8FiabkSHrkq7tsNvRP-yI-bnpQv4EayPBh7YwHVX3hYdRbhxaBvgJENgCuiEPb35Q2-Z6w6ujjiKUAK2VSbCFpENlEfcHNsjDeY7RCvFlwlCoHj1IeiNIaFTE9yXFu3aEWlExe3LzHv6PZyunEi88QJSXKSh56Um0e0eEg05rMv-VBM83cAqc5POmZnTP1vUdZO8fQv3NFLZ-xU6e1WQVxVPi5Cyqk4jYhGf1Y9t98N654y0tVvw74qNIsTrB-8bGS0Uhfe24oBEWmzObvBY3zhtT1d42myGUJv4pMTU6yPoS83zhPKm3LbUDEpBA1hvvc_09jHk7vUEAuFB-UAZzlht2C1yklzQ",
  1215. 'lwa_client_secret': 'cbf0514186db4df91e04a8905f0a91b605eae4201254ced879d8bb90df4b474d',
  1216. 'profile_id': "3006125408623189"
  1217. }
  1218. sp = SPClient(**AWS_CREDENTIALS)
  1219. rel = sp.iter_negativekeyword()
  1220. print(list(rel))
  1221. # print(rel)