Instagram 基本显示 API 分页
Posted
技术标签:
【中文标题】Instagram 基本显示 API 分页【英文标题】:Instagram Basic Display API Pagination 【发布时间】:2020-05-05 22:34:22 【问题描述】:对于使用 Instagram 基本显示 API 获得的媒体结果是否有分页功能?我已阅读以下文档,但没有任何使用分页的示例:
https://developers.facebook.com/docs/instagram-basic-display-api/reference/media/children https://developers.facebook.com/docs/graph-api/using-graph-api#paging我想限制响应中返回的媒体,例如第一个呼叫的媒体 1-15,然后获取下一组,例如16-30 在下一个电话。
TIA
【问题讨论】:
limit
& offset
应该适用于大多数查询。
@04FS 即使媒体不是来自相册,即来自普通帖子?你能举一个使用偏移量的例子吗?
@04FS nvm 我明白了。谢谢你:)
【参考方案1】:
我无法使用 CDS 的答案来解决问题。相反,使用了一种在返回的 json 格式字符串中查找“下一个”标签的方法,并直接使用它。
就我而言,我已经为 Instagram 构建了一个存储访问框架实现,因此流程如下:
在 SAF 向我的提供商发出的“添加行”调用中,我对 Instagram 进行了初始查询:
instagramQueryResult = queryInstagramAccount(instagramUserID, null); // Initially no "next" url
这个方法又是这样的:
private JSONObject queryInstagramAccount(String instagramUserID, String nextPageUrl)
String instagramToken = InTouchUtils.getInstagramAccessToken();
if ( instagramToken == null || DEFAULT_MEDIA_SERVICE_ACCESS_TOKEN_DEFAULT.equals(instagramToken))
return null;
// Returned from Instagram
String instagramRetval = null;
// What we send back from this method - normalized list of media plus any pagination data.
JSONObject returnResult = null;
// Used to build a normalized array of media objects, flattening out "CAROUSEL_ALBUM" return types
JSONArray dataArray = new JSONArray(), returnedArray = null;
// Initial response from Instagram as JSON prior to normalization
JSONObject instagramJSONResult = null;
// Parameters for the Volley call
HashMap<String,String> params = new HashMap<>();
params.put(INSTAGRAM_ACCESSTOKEN_KEY, InTouchUtils.getInstagramAccessToken());
// Build the query string
String url = null;
if ( nextPageUrl == null )
url = INSTAGRAM_GRAPH_URI + instagramUserID + MEDIA_MEDIA_EDGE;
String fieldsString = MEDIA_ID_KEY + "," +
MEDIA_TYPE_KEY + "," +
MEDIA_URL_KEY + "," +
MEDIA_THUMBNAIL_URL_KEY + "," +
MEDIA_UPDATED_TIME_KEY;
params.put(MEDIA_LIMIT_KEY, Long.toString(batchSize));
params.put(MEDIA_FIELDS_KEY, fieldsString);
else
// We've been given the fully created url to use
url = nextPageUrl;
params = null;
try
instagramRetval = InTouchUtils.callWebsiteFunction(url, params);
instagramJSONResult = new JSONObject(instagramRetval);
returnedArray = instagramJSONResult.getJSONArray(MEDIA_DATA_ARRAY);
if ( returnedArray.length() == 0)
return null;
for ( int i = 0; i < returnedArray.length(); i++)
JSONObject o = returnedArray.getJSONObject(i);
// this result could have types IMAGE, VIDEO or CAROUSEL_ALBUM. The latter type
// needs a subsequent call to get the children info
if (o.getString(MEDIA_TYPE_KEY).equals(MEDIA_TYPE_CAROUSEL))
// Here we need to make a separate call to get the carousel detail
String mediaID = null;
try
mediaID = o.getString(MEDIA_ID_KEY);
String childrenEdgeUrl = INSTAGRAM_GRAPH_URI + mediaID + MEDIA_CHILDREN_EDGE;
params = new HashMap<>();
params.put(INSTAGRAM_ACCESSTOKEN_KEY, InTouchUtils.getInstagramAccessToken());
String mediafieldsString = MEDIA_ID_KEY + "," +
MEDIA_TYPE_KEY + "," +
MEDIA_URL_KEY + "," +
MEDIA_THUMBNAIL_URL_KEY + "," +
MEDIA_UPDATED_TIME_KEY;
params.put(MEDIA_FIELDS_KEY, mediafieldsString);
String carouselRetval = InTouchUtils.callWebsiteFunction(childrenEdgeUrl, params);
JSONObject carouselJSON = new JSONObject(carouselRetval);
// Cycle through these entries
JSONArray carouselData = carouselJSON.getJSONArray(MEDIA_DATA_ARRAY);
if ( carouselData != null && carouselData.length() > 0)
for ( int x = 0; x < carouselData.length(); x++)
dataArray.put(carouselData.getJSONObject(x));
catch (Exception e)
Timber.d("Lifecycle: Exception processing carousel entry with ID %s, message: %s", mediaID, e.getMessage());
else
// Add to dataArray
dataArray.put(o);
catch (Exception e)
Timber.e("Exception getting Instagram info: %s", e.getMessage());
return null;
finally
returnedArray = null;
instagramRetval = null;
// See if there is pagination
JSONObject pagingObject = null;
try
pagingObject = instagramJSONResult.getJSONObject(MEDIA_PAGING_KEY);
catch (JSONException e)
// No paging returned, no problem
pagingObject = null;
returnResult = new JSONObject();
try
returnResult.put(MEDIA_DATA_ARRAY, dataArray);
if ( pagingObject != null )
returnResult.put(MEDIA_PAGING_KEY, pagingObject);
catch (JSONException e)
Timber.d("Lifecycle: exception gathering instagram data: %s", e.getMessage());
returnResult = null;
finally
instagramJSONResult = null;
return returnResult;
初始检查与常量 DEFAULT_MEDIA_SERVICE_ACCESS_TOKEN_DEFAULT 有关,该常量在我的 DocumentsProvider 的其他地方初始化为默认值,这意味着他们尚未输入他们的 Instagram 凭据,因此在这种情况下我会退出。
你看到对“InTouchUtils”的调用,这是我自己的类,它封装了一堆实用函数,比如使用 Volley 进行 Web API 调用。
这个方法是从 DocumentsProvider 中的几个地方调用的,所以其中一个参数是我是否正在处理 nextPageUrl
。如果不是(nextPageUrl 为空),我们构建默认 URL,我在其中为给定用户调用 Media“Edge”API。此方法将限制与 Instagram 访问令牌(都在我的应用程序的首选项方面定义)和字段字符串一起放在 params 哈希表中。
请注意,如果传入nextPageUrl
,那么我将完全绕过创建此网址,而只需使用nextPageUrl
。
这是来自InTouchUtils
的callWebsiteFunction
代码,它在同步模式下使用 Volley 来进行网站 API 调用(整个代码示例已经在单独的线程上运行,并且我在我的应用程序中授予了 INTERNET 权限) :
public static String callWebsiteFunction(String url, HashMap params) throws Exception
return callWebsiteFunction(url, params, VOLLEY_REQUEST_DEFAULT_TIMEOUT);
public static String callWebsiteFunction(String url, HashMap params, int timeoutInSeconds) throws Exception
RequestFuture<String> future = RequestFuture.newFuture();
String newUrl = null;
if ( params != null )
newUrl = InTouchUtils.createGetRequestUrl(url, params);
else
newUrl = url;
String result = null;
StringRequest request =
new StringRequest(Request.Method.GET,
newUrl,
future,
new Response.ErrorListener()
@Override
public void onErrorResponse(VolleyError error)
Timber.e("Got VolleyError: %s", error.getMessage());
)
;
InTouchUtils.addToRequestQueue(request);
try
// Using a blocking volley request
// See SO: https://***.com/questions/16904741/can-i-do-a-synchronous-request-with-volley
try
result = future.get(timeoutInSeconds, TimeUnit.SECONDS);
catch (InterruptedException e)
Timber.e("Got Interrupted Exception attempting Volley request: %s", e.getMessage());
catch (ExecutionException e)
Timber.e("Got Execution Exception attempting Volley request: %s", e.getMessage());
catch (TimeoutException e)
Timber.e("Got Timeout Exception attempting Volley request: %s", e.getMessage());
catch (Exception e)
Timber.e("Got General Exception");
throw e;
return result;
现在我有了结果,我可以处理它。首先要做的是将字符串转换为 JSONObject,这样我就可以开始解析它了。然后看看我是否通过解析“数据”键(我的代码中的常量MEDIA_DATA_ARRAY
)返回了一个 JSONArray 媒体项。
出于我的目的,我想做的是将返回的数据标准化为完整的图像和/或视频列表 - 所以我必须检查返回的内容是否为 CAROUSEL_ALBUM 类型,如果是,我制作另一个调用以获取该轮播的媒体子代。
最终,我重新打包所有媒体条目,以及从 Instagram 返回的任何分页,并将其返回给调用者。
现在回到调用者,我可以检查我得到了什么,看看我是否正在进行分页,特别是“下一个”url。
如果我没有,那么我会重置 SAF“加载”标志(这是一个 SAF 的事情,它会导致在您的提供程序获取更多信息时,在文件选择器中显示或不显示不确定的进度条条目),我完成了。请注意,“我没有”的定义是“分页”元素或“下一个”元素都不存在。这是因为您可能根本不会获得分页元素,或者您确实获得了分页元素但您没有在其中获得“下一个”元素。
如果我这样做了,我向 SAF 表明我正在“加载”,然后我启动一个线程(“BatchFetcher”),该线程基本上循环执行相同的调用以查询 Instagram,但只要传入“下一个”url找到一个:
if (instagramQueryResult == null || instagramQueryResult.length() == 0)
// Nothing in instagram for this user
Timber.d( "addRowstoQueryChildDocumentsCursor: I called queryInstagramAccount() but nothing was there!");
return;
JSONArray data = null;
try
data = instagramQueryResult.getJSONArray(MEDIA_DATA_ARRAY);
if ( data.length() == 0)
return;
catch (JSONException e)
// No data, nothing to do
Timber.d("Lifecycle: Found no media data for user, exception was: %s", e.getMessage());
return;
JSONObject paging = null;
String nextUrl = null;
try
paging = instagramQueryResult.getJSONObject(MEDIA_PAGING_KEY);
// If we get here, test to see if we have a "next" node. If so, that's what
// we need to query, otherwise we are done.
nextUrl = paging.getString(MEDIA_NEXT_KEY);
catch (JSONException e)
// No paging
paging = null;
nextUrl = null;
Timber.d( "addRowstoQueryChildDocumentsCursor: New query fetch got %d entries.", data.length());
if ( paging == null || nextUrl == null)
// We are done - add these to cache and cursor and clear loading flag
populateResultsToCacheAndCursor(data, cursor);
clearCursorLoadingNotification(cursor);
Timber.d( "addRowstoQueryChildDocumentsCursor: Directory retrieval is complete for parentDocumentId: " +
parentDocumentId +
" took " +
(System.currentTimeMillis()- startTimeForDirectoryQuery)+"ms.");
else
// Store our results to both the cache and cursor - cursor for the initial return,
// cache for when we come back after the Thread finishes
populateResultsToCacheAndCursor(data, cursor);
// Set the getExtras()
setCursorForLoadingNotification(cursor);
// Register this cursor with the Resolver to get notified by Thread so Cursor will then notify loader to re-load
Timber.d( "addRowstoQueryChildDocumentsCursor: registering cursor for notificationUri on: %s and starting BatchFetcher.", getChildDocumentsUri(parentDocumentId).toString());
cursor.setNotificationUri(getContext().getContentResolver(),getChildDocumentsUri(parentDocumentId));
// Start new thread
batchFetcher = new BatchFetcher(parentDocumentId, nextUrl);
batchFetcher.start();
线程“batchFetcher”处理检查媒体项目的返回值,并继续循环,直到找不到更多条目,不再从 Instagram 返回“下一个 url”,或者直到它被中断。 它填充一个内部缓存,在从 SAF 到我的提供程序的后续请求中读取该缓存,直到没有更多要获取的内容,在这种情况下,游标的“加载”方面被重置,SAF 将停止从我的提供者请求数据提供者。
【讨论】:
【参考方案2】:通过使用本文档中的分页参数找到答案:https://developers.facebook.com/docs/graph-api/using-graph-api#paging
目前,Basic Display API 默认返回最近的 20 个媒体。如果您想返回更多或更少,请使用以下网址:
https://graph.instagram.com/user-id/media?fields=media-fields-you-want-to-return&access_token=access -token&limit=number-of-media-you-want-to-return
要进行分页,您需要有一个“下一个”端点来调用。要尝试这一点,请将您的第一次通话限制为少于您拥有的媒体数量。您应该获得 3 个用于分页的端点:
"paging":
"cursors":
"before": "abc",
"after": "def"
,
"next": "ghi"
现在将您的下一个端点添加到上面的原始 url: https://graph.instagram.com/user-id/media?fields=media-fields-you-want-to-return&access_token=access-token&limit=number-of-media-you-want-to-return&next=next-endpoint
【讨论】:
limit
是否有最大限制?
@JDT 好像是100
我发现将 &next 添加到原始 url 对我来说根本不起作用。作为“下一个”返回的 url 完全包含所需的一切。我所要做的就是将它用作“下一个”API 调用,然后根据我在原始调用中所做的所有“设置”获得下一组数据(即“下一个”url 已经包含端点,字段、访问令牌和限制)。然后在随后的返回中,我只是检查一下是否也有一个“下一个”,如果有,在循环中再次使用它,直到返回字符串中不再有“下一个”。以上是关于Instagram 基本显示 API 分页的主要内容,如果未能解决你的问题,请参考以下文章
网站中的 Instagram 动态 - 使用 Instagram 基本显示 API