Skip to main content

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

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

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

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

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

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

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

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

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

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

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

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"

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

  • ЗА URL-адресом предыдущей страницы следует .
  • ЗА URL-адресом следующей страницы следует .
  • ЗА URL-адресом последней страницы следует .
  • ЗА URL-адресом первой страницы следует .

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

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

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

URL-адреса в заголовке используют параметры запроса, чтобы указать, какая страница результатов возвращается. Параметры запроса в URL-адресах могут отличаться между конечными точками, однако каждая конечная точка с разбивкой на страницы будет использовать параметры запроса или запросы. (Некоторые конечные точки используют параметр, отличный от разбиения на страницы.) Во всех случаях можно использовать URL-адреса в заголовке для получения дополнительных страниц результатов. Дополнительные сведения о параметрах запроса см. в разделе AUTOTITLE.

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

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

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

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

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

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-адресов из заголовка можно написать сценарий для получения нескольких страниц результатов.

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

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

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

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

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ });

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.js.

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

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

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

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ });

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);