Skip to main content

Использование разбиения на страницы в REST API

Узнайте, как перемещаться по ответам с разбивкой на страницы из REST API.

Сведения о разбиении на страницы

Если ответ REST API будет содержать множество результатов, GitHub разлагает результаты и возвращает подмножество результатов. Например, возвращается только 30 проблем из octocat/Spoon-Knife репозитория, GET /repos/octocat/Spoon-Knife/issues даже если репозиторий включает более 1600 открытых проблем. Это упрощает обработку ответа для серверов и пользователей.

Вы можете использовать link заголовок из ответа, чтобы запросить дополнительные страницы данных. Если конечная точка поддерживает per_page параметр запроса, вы можете контролировать, сколько результатов возвращаются на странице.

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

При разбиении ответа на страницы заголовки ответа будут включать link заголовок. Если конечная точка не поддерживает разбиение на страницы или если все результаты помещаются на одну страницу, link заголовок будет опущен.

Заголовок link содержит URL-адреса, которые можно использовать для получения дополнительных страниц результатов. Например, предыдущая, следующая, первая и последняя страница результатов.

Чтобы просмотреть заголовки ответа для определенной конечной точки, можно использовать curl, GitHub CLI или библиотеку, которую вы используете для выполнения запросов. Чтобы просмотреть заголовки ответов, если вы используете библиотеку для выполнения запросов, следуйте документации по этой библиотеке. Чтобы просмотреть заголовки ответа, если вы используете curl или GitHub CLI, передайте флаг с запросом --include . Например:

curl --include --request GET \
--url "https://api.github.com/repos/octocat/Spoon-Knife/issues" \
--header "Accept: application/vnd.github+json"

Если ответ разбиен на страницы, link заголовок будет выглядеть примерно так:

link: <https://api.github.com/repositories/1300192/issues?page=2>; rel="prev", <https://api.github.com/repositories/1300192/issues?page=4>; rel="next", <https://api.github.com/repositories/1300192/issues?page=515>; rel="last", <https://api.github.com/repositories/1300192/issues?page=1>; rel="first"

Заголовок link предоставляет URL-адрес для предыдущей, следующей, первой и последней страницы результатов:

  • ЗА URL-адресом предыдущей страницы следует rel="prev".
  • ЗА URL-адресом следующей страницы следует rel="next".
  • ЗА URL-адресом последней страницы следует rel="last".
  • ЗА URL-адресом первой страницы следует rel="first".

В некоторых случаях доступны только подмножество этих ссылок. Например, ссылка на предыдущую страницу не будет включена, если вы находитесь на первой странице результатов, а ссылка на последнюю страницу не будет включена, если она не может быть рассчитана.

URL-адреса из заголовка link можно использовать для запроса другой страницы результатов. Например, чтобы запросить последнюю страницу результатов на основе предыдущего примера:

curl --include --request GET \
--url "https://api.github.com/repositories/1300192/issues?page=515" \
--header "Accept: application/vnd.github+json"

URL-адреса в заголовке link используют параметры запроса, чтобы указать, какая страница результатов возвращается. Параметры запроса в link URL-адресах могут отличаться между конечными точками, однако каждая конечная точка с разбивкой на страницы будет использовать page/before``afterпараметры запроса или since запросы. (Некоторые конечные точки используют since параметр, отличный от разбиения на страницы.) Во всех случаях можно использовать URL-адреса в заголовке link для получения дополнительных страниц результатов. Дополнительные сведения о параметрах запроса см. в разделе Начало работы с REST API.

Изменение количества элементов на странице

Если конечная точка поддерживает per_page параметр запроса, вы можете управлять количеством результатов, возвращаемых на странице. Дополнительные сведения о параметрах запроса см. в разделе Начало работы с REST API.

Например, этот запрос использует per_page параметр запроса для возврата двух элементов на страницу:

curl --include --request GET \
--url "https://api.github.com/repos/octocat/Spoon-Knife/issues?per_page=2" \
--header "Accept: application/vnd.github+json"

Параметр per_page автоматически будет включен в link заголовок. Например:

link: <https://api.github.com/repositories/1300192/issues?per_page=2&page=2>; rel="next", <https://api.github.com/repositories/1300192/issues?per_page=2&page=7715>; rel="last"

Скриптирование с разбивкой на страницы

Вместо ручного копирования URL-адресов из заголовка link можно написать сценарий для получения нескольких страниц результатов.

В следующих примерах используется библиотека Octokit.js javaScript и GitHub. Дополнительные сведения о Octokit.js см. в разделе Начало работы с REST API и Octokit.js README.

Пример использования метода Octokit.js разбивки на страницы

Чтобы получить результаты с разбивкой на страницы с помощью Octokit.js, можно использовать octokit.paginate(). octokit.paginate() Возвращает следующую страницу результатов, пока не достигнет последней страницы, а затем возвращает все результаты в виде одного массива. Несколько конечных точек возвращают результаты с разбивкой на страницы в виде массива в объекте, а не возвращать результаты с разбивкой на страницы в виде массива. octokit.paginate() всегда возвращает массив элементов, даже если необработанный результат был объектом.

Например, этот скрипт получает все проблемы из octocat/Spoon-Knife репозитория. Хотя она запрашивает 100 проблем за раз, функция не возвращается до достижения последней страницы данных.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
});

const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "octocat",
  repo: "Spoon-Knife",
  per_page: 100,
  headers: {
    "X-GitHub-Api-Version": "2022-11-28",
  },
});

console.log(data)

Вы можете передать необязательную функцию octokit.paginate() карты в конец разбиения на страницы до достижения последней страницы или сократить использование памяти, сохраняя только подмножество ответа. Вы также можете выполнять octokit.paginate.iterator() итерацию по одной странице за раз, а не запрашивать каждую страницу. Дополнительные сведения см . в документации по Octokit.js.

Пример создания метода разбиения на страницы

Если вы используете другой язык или библиотеку, у которых нет метода разбиения на страницы, можно создать собственный метод разбиения на страницы. Этот пример по-прежнему использует библиотеку Octokit.js для выполнения запросов, но не используется octokit.paginate().

Функция getPaginatedData выполняет запрос к конечной точке с octokit.request()помощью . Данные из ответа обрабатываются parseDataпутем обработки случаев, когда данные не возвращаются или случаи, когда возвращаемые данные являются объектом вместо массива. Затем обработанные данные добавляются в список, содержащий все собранные на страницы данные. Если ответ содержит заголовок и если link заголовок содержит link ссылку на следующую страницу, функция использует шаблон RegEx (nextPattern), чтобы получить URL-адрес для следующей страницы. Затем функция повторяет предыдущие шаги, теперь используя этот новый URL-адрес. link После того как заголовок больше не содержит ссылку на следующую страницу, возвращаются все результаты.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
});

async function getPaginatedData(url) {
  const nextPattern = /(?<=<)([\S]*)(?=>; rel="Next")/i;
  let pagesRemaining = true;
  let data = [];

  while (pagesRemaining) {
    const response = await octokit.request(`GET ${url}`, {
      per_page: 100,
      headers: {
        "X-GitHub-Api-Version":
          "2022-11-28",
      },
    });

    const parsedData = parseData(response.data)
    data = [...data, ...parsedData];

    const linkHeader = response.headers.link;

    pagesRemaining = linkHeader && linkHeader.includes(`rel=\"next\"`);

    if (pagesRemaining) {
      url = linkHeader.match(nextPattern)[0];
    }
  }

  return data;
}

function parseData(data) {
  // If the data is an array, return that
    if (Array.isArray(data)) {
      return data
    }

  // Some endpoints respond with 204 No Content instead of empty array
  //   when there is no data. In that case, return an empty array.
  if (!data) {
    return []
  }

  // Otherwise, the array of items that we want is in an object
  // Delete keys that don't include the array of items
  delete data.incomplete_results;
  delete data.repository_selection;
  delete data.total_count;
  // Pull out the array of items
  const namespaceKey = Object.keys(data)[0];
  data = data[namespaceKey];

  return data;
}

const data = await getPaginatedData("/repos/octocat/Spoon-Knife/issues");

console.log(data);