all(); // 继续处理 Google Ads API 操作 return $this->getAssetRelations($options); } /** * get asset relations * @throws ApiException */ public function getAssetRelations($options) { $customers = $this->googleOAuthService->getGoogleAdCustomers([]); foreach ($customers as $customer) { // dump($customer); $googleAdsAssetRelationService = new GoogleAdsAssetRelationService($customer['customer_id']); $resourceName = $googleAdsAssetRelationService->runListAssetRelations($customer['customer_id']); } // return $this->successResponse(['ads_list' => $resourceName]); } /** * get asset relations * @throws ApiException */ public function getVideoAssetRelations($options) { $customers = $this->googleOAuthService->getGoogleAdCustomers([]); foreach ($customers as $customer) { $googleAdsAssetRelationService = new GoogleAdsAssetRelationService($customer['customer_id']); $resourceName = $googleAdsAssetRelationService->runListVideoAssetRelations($customer['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 ]); } }