Сведения о разбиении на страницы
Если ответ REST API будет содержать множество результатов, GitHub разлагает результаты и возвращает подмножество результатов. Например, возвращается только 30 проблем из octocat/Spoon-Knife
репозитория, GET /repos/octocat/Spoon-Knife/issues
даже если репозиторий включает более 1600 открытых проблем. Это упрощает обработку ответа для серверов и пользователей.
Вы можете использовать link
заголовок из ответа, чтобы запросить дополнительные страницы данных. Если конечная точка поддерживает per_page
параметр запроса, вы можете контролировать, сколько результатов возвращаются на странице.
В этой статье показано, как запрашивать дополнительные страницы результатов для ответов с разбивкой на страницы, как изменить количество результатов, возвращаемых на каждой странице, и как написать скрипт для получения нескольких страниц результатов.
Использование link
заголовков
При разбиении ответа на страницы заголовки ответа будут включать 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 проблем за раз, функция не возвращается до достижения последней страницы данных.
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)
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
После того как заголовок больше не содержит ссылку на следующую страницу, возвращаются все результаты.
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);
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);