Сведения о разбиении на страницы
Если ответ REST API будет содержать множество результатов, GitHub разлагает результаты и возвращает подмножество результатов. Например, возвращается только 30 проблем из octocat/Spoon-Knife
репозитория, GET /repos/octocat/Spoon-Knife/issues
даже если репозиторий включает более 1600 открытых проблем. Это упрощает обработку ответа для серверов и пользователей.
В этом руководстве показано, как запрашивать дополнительные страницы результатов для ответов с разбивкой на страницы, как изменить количество результатов, возвращаемых на каждой странице, и как написать скрипт для получения нескольких страниц результатов.
Использование заголовков ссылок
При разбиении ответа на страницы заголовки ответа будут содержать заголовок ссылки. Заголовок ссылки будет опущен, если конечная точка не поддерживает разбиение на страницы или если все результаты помещаются на одну страницу. Заголовок ссылки содержит URL-адреса, которые можно использовать для получения дополнительных страниц результатов. Чтобы просмотреть заголовки ответа, если вы используете 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: <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-адресом предыдущей страницы следует
rel="prev"
. - ЗА URL-адресом следующей страницы следует
rel="next"
. - ЗА URL-адресом последней страницы следует
rel="last"
. - ЗА URL-адресом первой страницы следует
rel="first"
.
В некоторых случаях доступны только подмножество этих ссылок. Например, ссылка на предыдущую страницу не будет включена, если вы находитесь на первой странице результатов, а ссылка на последнюю страницу не будет включена, если она не может быть рассчитана.
URL-адреса из заголовка ссылки можно использовать для запроса другой страницы результатов. Например, чтобы запросить последнюю страницу результатов на основе предыдущего примера:
curl --include --request GET \
--url "https://api.github.com/repositories/1300192/issues?page=515" \
--header "Accept: application/vnd.github+json"
URL-адреса в заголовке ссылки используют параметры запроса, чтобы указать, какая страница результатов возвращается. Параметры запроса в URL-адресах ссылок могут отличаться между конечными точками: каждая конечная точка с разбивкой на страницы будет использовать page``after``before
/параметры запроса или since
параметры запроса. (Некоторые конечные точки используют since
параметр, отличный от разбиения на страницы.) Во всех случаях можно использовать URL-адреса в заголовке ссылки для получения дополнительных страниц результатов. Дополнительные сведения о параметрах запроса см. в разделе "Начало работы с 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: <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-адреса из заголовка ссылки, можно написать скрипт для получения нескольких страниц результатов.
В следующих примерах используются javaScript и GitHubбиблиотеки Octokit.js. Дополнительные сведения о 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: "https://HOSTNAME/api/v3", }); const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", { owner: "octocat", repo: "Spoon-Knife", per_page: 100, }); console.log(data)
import { Octokit } from "octokit";
const octokit = new Octokit({
baseUrl: "https://HOSTNAME/api/v3",
});
const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
owner: "octocat",
repo: "Spoon-Knife",
per_page: 100,
});
console.log(data)
Вы можете передать необязательную функцию octokit.paginate()
карты в конец разбиения на страницы до достижения последней страницы или сократить использование памяти, сохраняя только подмножество ответа. Вы также можете выполнять octokit.paginate.iterator()
итерацию по одной странице за раз, а не запрашивать каждую страницу. Дополнительные сведения см . в документации по Octokit.js.
Пример создания метода разбиения на страницы
Если вы используете другой язык или библиотеку, у которых нет метода разбиения на страницы, можно создать собственный метод разбиения на страницы. Этот пример по-прежнему использует библиотеку Octokit.js для выполнения запросов, но не зависит octokit.paginate()
от него.
Функция getPaginatedData
выполняет запрос к конечной точке с octokit.request()
помощью . Данные из ответа обрабатываются parseData
путем обработки случаев, когда данные не возвращаются или случаи, когда возвращаемые данные являются объектом вместо массива. Затем обработанные данные добавляются в список, содержащий все собранные на страницы данные. Если ответ содержит заголовок ссылки и если заголовок ссылки содержит ссылку на следующую страницу, функция использует шаблон RegEx (nextPattern
), чтобы получить URL-адрес для следующей страницы. Затем функция повторяет предыдущие шаги, теперь используя этот новый URL-адрес. После того как заголовок ссылки больше не содержит ссылку на следующую страницу, возвращаются все результаты.
import { Octokit } from "octokit"; const octokit = new Octokit({ baseUrl: "https://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, }); 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: "https://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,
});
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);