281 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			281 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | ||
| 
 | ||
| namespace app\event;
 | ||
| 
 | ||
| use app\service\GoogleAdsAdService;
 | ||
| use Google\ApiCore\ApiException;
 | ||
| use GuzzleHttp\Client;
 | ||
| use GuzzleHttp\Exception\GuzzleException;
 | ||
| use support\Db;
 | ||
| use support\Request;
 | ||
| use support\Response;
 | ||
| use DI\Annotation\Inject;
 | ||
| 
 | ||
| //use QL\QueryList;
 | ||
| use support\Redis;
 | ||
| 
 | ||
| 
 | ||
| class GoogleAdsAds
 | ||
| {
 | ||
|     /**
 | ||
|      * @Inject
 | ||
|      * @var GoogleAdsAdService
 | ||
|      */
 | ||
| 
 | ||
|     private $googleAdsAdService;
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|     //微博热榜地址
 | ||
| //    const url = 'https://library.tiktok.com/api/v1/search?region=GB&type=1&start_time=1666540800&end_time=1666627200';
 | ||
| 
 | ||
|     const  userAgent = 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
 | ||
| 
 | ||
|     const type = 'googleadsads';
 | ||
|     const limit = 12;
 | ||
|     const sort_order = 'impression,desc';
 | ||
| 
 | ||
| 
 | ||
| //    const countries = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IS", "IE","IT", "LV", "LI", "LT", "LU", "MT", "NL", "NO", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "CH", "TR", "GB"] ;
 | ||
|     const countries = ["GB", "BE"];
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|     public function listAds(Request $request)
 | ||
|     {
 | ||
|         $options = $request->all();
 | ||
| 
 | ||
|         // 继续处理 Google Ads API 操作
 | ||
|         return $this->getAds($options);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * get groups
 | ||
|      * @throws ApiException
 | ||
|      */
 | ||
|     public function getAds($options): Response
 | ||
|     {
 | ||
|         $resourceName = $this->googleAdsAdService->runListAds($options['customer_id']);
 | ||
| 
 | ||
|         return $this->successResponse(['ads_list' => $resourceName]);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 每天爬取tiktok广告
 | ||
|      * @return void
 | ||
|      */
 | ||
|     public function update()
 | ||
|     {
 | ||
|         try {
 | ||
| 
 | ||
|             $client = new Client([
 | ||
|                 //允许重定向
 | ||
| //                'allow_redirects' => true,
 | ||
|             ]);
 | ||
| 
 | ||
| 
 | ||
|             // 获取前两天 0 点的时间戳
 | ||
|             $dayBeforeYesterdayStart = strtotime('-2 days 00:00:00');
 | ||
| 
 | ||
|             // 获取前一天 0 点的时间戳
 | ||
|             $yesterdayStart = strtotime('-1 day 00:00:00');
 | ||
|             $countryCache   = Redis::get(self::type . 'lastCountry');
 | ||
|             //全部跑完跳出
 | ||
|             if ($countryCache == 'All') {
 | ||
|                 dump($countryCache . '国家更新tiktok Ads 完成');
 | ||
|                 return;
 | ||
|             }
 | ||
| 
 | ||
|             if (empty($countryCache)) {
 | ||
|                 $countryCache = 'GB';
 | ||
|             }
 | ||
| 
 | ||
|             $searchIdCache = Redis::get(self::type . 'lastSearchId');
 | ||
|             $offsetCache   = Redis::get(self::type . 'nextOffset');
 | ||
|             $totalCache    = Redis::get(self::type . 'totalCache');
 | ||
|             if (!isset($searchIdCache) || empty($searchIdCache)) {
 | ||
|                 $searchIdCache = '';
 | ||
|             }
 | ||
|             if (!isset($offsetCache) || empty($searchIdCache)) {
 | ||
|                 $offsetCache = 0;
 | ||
|             }
 | ||
|             if (!isset($totalCache) || empty($searchIdCache)) {
 | ||
|                 $totalCache = 0;
 | ||
|             }
 | ||
| 
 | ||
|             //判断国家是否跑完。
 | ||
|             if ($totalCache > 0 && $offsetCache > ceil($totalCache / self::limit)) {
 | ||
|                 $key = array_search($countryCache, self::countries, true);
 | ||
|                 if (in_array($countryCache, self::countries) && isset(self::countries[$key + 1])) {
 | ||
|                     $countryCache = self::countries[$key + 1];
 | ||
|                     dump('更新' . $countryCache . '国家的tiktok Ads 完成');
 | ||
|                 } else {
 | ||
|                     $countryCache = 'All'; //赋值非正常国家的code中断定时任务
 | ||
|                     dump($countryCache . '国家更新tiktok Ads 完成');
 | ||
|                 }
 | ||
|                 return;
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
|             $start_time = $dayBeforeYesterdayStart;
 | ||
|             $end_time   = $yesterdayStart;
 | ||
|             //当前国家爬取
 | ||
|             $currentParams = null;
 | ||
|             $currentParams = ['country' => $countryCache, 'search_id' => $searchIdCache, 'type' => 1, 'start_time' => $start_time, 'end_time' => $end_time];
 | ||
| 
 | ||
| 
 | ||
|             $url = 'https://library.tiktok.com/api/v1/search?region=' . $currentParams['country'] . '&type=' . $currentParams['type'] . '&start_time=' . $currentParams['start_time'] . '&end_time=' . $currentParams['end_time'];
 | ||
| 
 | ||
|             $res = json_decode($client->post($url, [
 | ||
|                 'headers' => [
 | ||
|                     'accept' => 'application/json, text/plain, */*',
 | ||
|                     'accept-language' => 'zh-CN,zh;q=0.9',
 | ||
|                     'content-type' => 'application/json',
 | ||
|                     'cookie' => 'cookie: _ttp=2ov8Fc4C2CaNscHJd90O9fMhlpE; _ga=GA1.1.1025820618.1731926196; FPID=FPID2.2.Bcgkp%2Fk%2Bbn5w5YeSMR9wd9VpNHJwTUpkkaEqSdCEa0w%3D.1731926196; FPAU=1.2.944915349.1731926193; FPLC=mbVyryI5aG6IVpAvhs1JsgWjA7FVA6QsCJ7VbXhM7zWoXNp4rcD0IK7FNTTf%2FuOrqeOgqEhTd4NB3hY7q3aDVTGQa3WGHqxkGte4%2BBZxsrpaHFas9kb7DPRXM12T5Q%3D%3D; _ga_TEQXTT9FE4=GS1.1.1732097542.7.0.1732097542.0.0.857840528',
 | ||
|                     'origin' => 'https://library.tiktok.com',
 | ||
|                     'priority' => 'u=1, i',
 | ||
|                     'referer' => 'https://library.tiktok.com/ads?region=AT&start_time=1731945600000&end_time=1732032000000&adv_name=&adv_biz_ids=&query_type=&sort_type=last_shown_date,desc',
 | ||
|                     'sec-ch-ua' => '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
 | ||
|                     'sec-ch-ua-mobile' => '?0',
 | ||
|                     'sec-ch-ua-platform' => '"Windows"',
 | ||
|                     'sec-fetch-dest' => 'empty',
 | ||
|                     'sec-fetch-mode' => 'cors',
 | ||
|                     'sec-fetch-site' => 'same-origin',
 | ||
|                     'user-agent' => self::userAgent,
 | ||
| //                        'user-agent' => 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
 | ||
|                 ],
 | ||
|                 'json' => [
 | ||
|                     'query' => '',
 | ||
|                     'query_type' => '',
 | ||
|                     'adv_biz_ids' => '',
 | ||
|                     'order' => self::sort_order,
 | ||
|                     'offset' => (int)$offsetCache,
 | ||
|                     'search_id' => $currentParams['search_id'],
 | ||
|                     'limit' => self::limit,
 | ||
|                 ],
 | ||
| 
 | ||
|             ])->getBody()->getContents(), true);
 | ||
| //            dump($res);return;  //调试点
 | ||
| 
 | ||
|             if ($res['search_id'] != $searchIdCache) {
 | ||
|                 $searchIdCache = $res['search_id'];
 | ||
|                 dump('search_id更新 ' . $searchIdCache . ' 成功');
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
|             if ($res['total'] == 0 || $res['code'] != 0) {
 | ||
|                 dump('更新tiktok Ads接口响应异常:' . json_encode($res, JSON_UNESCAPED_UNICODE));
 | ||
|                 return;
 | ||
|             }
 | ||
|             $listAdsIds = [];
 | ||
|             foreach ($res['data'] as $ad) {
 | ||
|                 if ($ad['audit_status'] == 2 || empty($ad['videos'])) {
 | ||
|                     continue; //审核不过或者没视频不采集
 | ||
|                 }
 | ||
| 
 | ||
|                 $imagesJson     = is_array($ad['image_urls']) ? json_encode($ad['image_urls']) : json_encode([]);
 | ||
|                 $rejection_info = is_array($ad['rejection_info']) ? json_encode($ad['rejection_info']) : null;
 | ||
| //                dump($rejection_info);
 | ||
|                 $insertData[$ad['id']] = [
 | ||
|                     'ad_id' => $ad['id'],
 | ||
|                     'name' => $ad['name'],
 | ||
|                     'audit_status' => $ad['audit_status'],
 | ||
|                     'type' => $ad['type'],
 | ||
|                     'first_shown_date' => $ad['first_shown_date'],
 | ||
|                     'last_shown_date' => $ad['last_shown_date'],
 | ||
|                     'image_urls' => $imagesJson,
 | ||
|                     'estimated_audience' => $ad['estimated_audience'],
 | ||
|                     'spent' => $ad['spent'],
 | ||
|                     'impression' => $ad['impression'],
 | ||
|                     'show_mode' => $ad['show_mode'],
 | ||
|                     'rejection_info' => $rejection_info,
 | ||
|                     'sor_audit_status' => $ad['sor_audit_status'],
 | ||
|                     'country_code' => $countryCache,
 | ||
|                 ];
 | ||
|                 if (isset($ad['videos']) && !empty($ad['videos'])) {
 | ||
|                     // 遍历 "videos" 数组
 | ||
|                     foreach ($ad['videos'] as $video) {
 | ||
|                         $insertData[$ad['id']]['video_url'] = $video['video_url'];
 | ||
|                         $insertData[$ad['id']]['cover_img'] = $video['cover_img'];
 | ||
|                     }
 | ||
|                 }
 | ||
|                 $listAdsIds = array_column($insertData, 'ad_id');
 | ||
| 
 | ||
|             }
 | ||
| //            dump($insertData);return;
 | ||
| 
 | ||
| 
 | ||
|             if (empty($insertData)) return;
 | ||
| 
 | ||
|             //开启事务
 | ||
|             Db::beginTransaction();
 | ||
| 
 | ||
| 
 | ||
|             //删除原来的旧数据
 | ||
|             TiktokAd::query()->whereIn('ad_id', array_keys($insertData))->delete();
 | ||
|             //添加新的数据
 | ||
|             TiktokAd::query()->insert($insertData);
 | ||
| 
 | ||
|             //redis缓存
 | ||
|             Redis::set(self::type, json_encode($insertData, JSON_UNESCAPED_UNICODE));
 | ||
| 
 | ||
|             //redis缓存 记录更新时间
 | ||
|             $time = date('Y-m-d H:i:s');
 | ||
|             Redis::set(self::type . 'time', $time);
 | ||
|             Redis::set(self::type . 'lastCountry', $countryCache);
 | ||
|             Redis::set(self::type . 'nextOffset', ++$offsetCache); //记录下一次的offset
 | ||
|             Redis::set(self::type . 'totalCache', $res['total']);
 | ||
|             Redis::set(self::type . 'lastSearchId', $searchIdCache);
 | ||
|             if (!empty($listAdsIds)) {
 | ||
|                 Redis::rPush(self::type . 'AdsIds', ...$listAdsIds);
 | ||
|             }
 | ||
| 
 | ||
|             //提交事务
 | ||
|             Db::commit();
 | ||
|             //销毁$res
 | ||
|             unset($res);
 | ||
| 
 | ||
|             dump(date('Y-m-d H:i:s') . '更新' . self::type . '成功');
 | ||
|         } catch (GuzzleException|\Exception $exception) {
 | ||
|             //回滚事务
 | ||
|             Db::rollBack();
 | ||
|             dump('更' . self::type . '异常:' . $exception->getMessage());
 | ||
|             dump($exception);
 | ||
|         }
 | ||
| //        } catch (ClientExceptionInterface $e) {
 | ||
| //            // 捕获 4xx 错误
 | ||
| //            dump( 'Client error: ' . $e->getMessage() . "\n");
 | ||
| //        } catch (ServerExceptionInterface $e) {
 | ||
| //            // 捕获 5xx 错误
 | ||
| //            dump('Server error: ' . $e->getMessage() . "\n");
 | ||
| //        } catch (TransportExceptionInterface $e) {
 | ||
| //            // 捕获网络传输错误
 | ||
| //            dump('Transport error: ' . $e->getMessage() . "\n") ;
 | ||
| //        } catch (\Exception $e) {
 | ||
| //            // 捕获所有其他错误
 | ||
| //            dump('General error: ' . $e->getMessage() . "\n") ;
 | ||
| //        }
 | ||
|     }
 | ||
| 
 | ||
| 
 | ||
|     // 可以加入一些公共方法
 | ||
|     protected function successResponse($data): Response
 | ||
|     {
 | ||
|         return Json([
 | ||
|             'code' => 0,
 | ||
|             'msg' => 'ok',
 | ||
|             'data' => $data,
 | ||
|         ]);
 | ||
|     }
 | ||
| 
 | ||
|     protected function errorResponse($code, $message, $data = []): Response
 | ||
|     {
 | ||
|         return Json([
 | ||
|             'code' => $code,
 | ||
|             'msg' => $message ?: 'error',
 | ||
|             'data' => $data
 | ||
|         ]);
 | ||
|     }
 | ||
| }
 |