Превышение предела частоты запросов и максимального времени выполнения настраиваемой функции при извлечении данных из Pipedrive API - PullRequest
0 голосов
/ 09 июля 2020

Я пытаюсь экспортировать данные Pipedrive в таблицу Google, в частности, чтобы установить связь между двумя моими запросами. Итак, я сначала написал этот скрипт:

function GetPipedriveDeals2() {
  let ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheets = ss.getSheets();
  let sheet = ss.getActiveSheet();

   //the way the url is build next step is to iterate between the end because api only allows a fixed number of calls (100) this way i can slowly fill the sheet.
  let url    = "https://laptop.pipedrive.com/v1/products:(id)?start=";
  let limit  = "&limit=500";
  //let filter = "&filter_id=64";
  let pipeline = 1; // put a pipeline id specific to your PipeDrive setup 
  let start  = 1;
  //let end  = start+50;
  let token  = "&api_token=XXXXXXXXXXXXXXX";
  let response = UrlFetchApp.fetch(url+start+limit+token); //
  let dataAll = JSON.parse(response.getContentText()); 
  let dataSet = dataAll;
  //let prices = prices;
  //create array where the data should be put
  let rows = [], data;
  for (let i = 0; i < dataSet.data.length; i++) {
  data = dataSet.data[i];
    rows.push([data.id,
               GetPipedriveDeals4(data.id)
               ]);
  }

  Logger.log( 'function2' ,JSON.stringify(rows,null,8) );   // Log transformed data

  return rows;
}

// Standard functions to call the spreadsheet sheet and activesheet
function GetPipedriveDeals4(idNew) {
  let ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheets = ss.getSheets();
  let sheet = ss.getActiveSheet();

   //the way the url is build next step is to iterate between the end because api only allows a fixed number of calls (100) this way i can slowly fill the sheet.
  let url    = "https://laptop.pipedrive.com/v1/products/"+idNew+"/deals:(id,d93b458adf4bf84fefb6dbce477fe77cdf9de675)?start=";
  let limit  = "&limit=500";
  //let filter = "&filter_id=64";
  let pipeline = 1; // put a pipeline id specific to your PipeDrive setup 
  let start  = 1;
  //let end  = start+50;
  let token  = "&api_token=XXXXXXXXXXXXXXXXX"
  

  let response = UrlFetchApp.fetch(url+start+limit+token); //
  let dataAll = JSON.parse(response.getContentText()); 
  let dataSet = dataAll;
   //Logger.log(dataSet)
  //let prices = prices;
  //create array where the data should be put
  let rows = [], data;
  if(dataSet.data === null )return
  else {
    for (let i = 0; i < dataSet.data.length; i++) {
      data = dataSet.data[i];
      let idNew = data.id; 
      rows.push([data.id, data['d93b458adf4bf84fefb6dbce477fe77cdf9de675']]);
    }
  
  Logger.log( 'function4', JSON.stringify(rows,null,2) );   // Log transformed data
  return rows;
  }
}

Но он вообще не оптимизирован и запускается около 60 секунд, а скрипт Google выполняет пользовательские функции только в течение 30 секунд ... С помощью я получил эта вторая функция:

function getPipedriveDeals(apiRequestLimit){
  //Make the initial request to get the ids you need for the details.
  var idsListRequest = "https://laptop.pipedrive.com/v1/products:(id)?start=";
  var start  = 0;
  var limit  = "&limit="+apiRequestLimit;
  var token  = "&api_token=XXXXXXXXXXX";
  var response = UrlFetchApp.fetch(idsListRequest+start+limit+token);
  var data = JSON.parse(response.getContentText()).data;
  
  //For every id in the response, construct a url (the detail url) and push to a list of requests
  var requests = [];
  data.forEach(function(product){
    var productDetailUrl = "https://laptop.pipedrive.com/v1/products/"+product.id+"/deals:(id,d93b458adf4bf84fefb6dbce477fe77cdf9de675)?start=";
    requests.push(productDetailUrl+start+limit+token)
  })
  
  //With the list of detail request urls, make one call to UrlFetchApp.fetchAll(requests)
  var allResponses = UrlFetchApp.fetchAll(requests);
 // logger.log(allResponses);
  return allResponses; 
}

Но на этот раз все наоборот. Я достиг своего лимита запросов, установленного Pipedrive: https://pipedrive.readme.io/docs/core-api-concepts-rate-limiting (80 запросов за 2 се c).

Признаюсь, я больше не знаю, что думал о том, чтобы поместить OAuth2 в свой скрипт чтобы увеличить лимит запросов, но это кажется очень длинным и сложным. Я совсем не в своей области.

В общем, я просто хотел бы иметь сценарий, который не выполняет запросы слишком быстро, но без превышение 30 секунд, установленных скриптом Google Apps.

--------------------- РЕДАКТИРОВАТЬ --- ТЕСТ --- FOREACH80 ---- ---------------------------------

 function getPipedriveProducts(){
  //Make the initial request to get the ids you need for the details.
  var idsListRequest = "https://laptop.pipedrive.com/v1/products:(id)?start=";
  var start  = 0;
  var limit  = "&limit=500";
  var token  = "&api_token=XXXXXXXXXXXXXXXXXXX";
  var response = UrlFetchApp.fetch(idsListRequest+start+limit+token);
  var data = JSON.parse(response.getContentText()).data;
  
  //For every id in the response, construct a url (the detail url) and push to a list of requests
   const batch = new Set;
  let requests = [];
  data.forEach(function(product){
    var productDetailUrl = "https://laptop.pipedrive.com/v1/products/" + product.id + "/deals:(id,d93b458adf4bf84fefb6dbce477fe77cdf9de675)?start=";
    requests.push(productDetailUrl+start+limit+token);
    if(requests.length === 79) {
      batch.add(requests);
      requests = [];
    }
  })
  const allResponses = [...batch].flatMap(requests => {
    Utilities.sleep(2000);
    return UrlFetchApp.fetchAll(requests);
   Logger.log(allResponses) 
  });
}

введите описание изображения здесь

Ответы [ 2 ]

1 голос
/ 09 июля 2020

Chunking

Одна из наиболее важных концепций при работе с API-интерфейсами - это фрагменты, поскольку вам нужно избегать ограничения скорости, согласовывать планирование запросов, распараллеливать вычисления, загружающие CPU, и т. Д. c. Существует бесчисленное множество способов разбить массив на куски (см. полсотни ответов в этом каноническом Q&A только для JavaScript).

Вот небольшая настраиваемая утилита, адаптированная к ситуации, когда кто-то хочет разбить плоский массив на массив массивов определенного размера / шаблона (что обычно имеет место при фрагментировании запроса):

/**
 * @typedef {object} ChunkifyConfig
 * @property {number} [size]
 * @property {number[]} [limits]
 * 
 * @summary splits an array into chunks
 * @param {any[]} source 
 * @param {ChunkifyConfig}
 * @returns {any[][]}
 */
const chunkify = (source, {
  limits = [],
  size
} = {}) => {

  const output = [];

  if (size) {
    const {
      length
    } = source;

    const maxNumChunks = Math.ceil((length || 1) / size);
    let numChunksLeft = maxNumChunks;

    while (numChunksLeft) {
      const chunksProcessed = maxNumChunks - numChunksLeft;
      const elemsProcessed = chunksProcessed * size;
      output.push(source.slice(elemsProcessed, elemsProcessed + size));
      numChunksLeft--;
    }

    return output;
  }

  const {
    length
  } = limits;

  if (!length) {
    return [Object.assign([], source)];
  }

  let lastSlicedElem = 0;

  limits.forEach((limit, i) => {
    const limitPosition = lastSlicedElem + limit;
    output[i] = source.slice(lastSlicedElem, limitPosition);
    lastSlicedElem = limitPosition;
  });

  const lastChunk = source.slice(lastSlicedElem);
  lastChunk.length && output.push(lastChunk);

  return output;
};

const sourceLimited = [1, 1, 2, 2, 2, 3];
const outputLimited = chunkify(sourceLimited, { limits: [2, 1] });
console.log({ source : sourceLimited, output : outputLimited });

const sourceSized = ["ES5", "ES6", "ES7", "ES8", "ES9"];
const outputSized = chunkify(sourceSized, { size: 2 });
console.log({ source : sourceSized, output : outputSized });

Оттуда единственное, что вам нужно, - это пройти по массиву, ожидая завершения каждого фрагмента, чтобы сделать его применимым к вашей ситуации. Помните, что запросы могут завершаться ошибкой по любому количеству причин - вы должны сохранять последний успешно обработанный фрагмент.

1 голос
/ 09 июля 2020
  • Создать Установить из 80 запросов каждый

  • Выполнить каждое установленное значение с помощью fetchAll

  const batch = new Set;
  let requests = [];
  data.forEach(function(product){
    var productDetailUrl = "https://example.com";
    requests.push(productDetailUrl+start+limit+token);
    if(requests.length === 80) {
      batch.add(requests);
      requests = [];
    }
  })
  const allResponses = [...batch].flatMap(requests => {
    Utilities.sleep(2000);
    return UrlFetchApp.fetchAll(requests);
  });
...