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

这是来自InTouchUtilscallWebsiteFunction 代码,它在同步模式下使用 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 API 的 Ajax 分页尝试修复错误

网站中的 Instagram 动态 - 使用 Instagram 基本显示 API

Instagram 基本显示 API

使用 Instagram 基本显示 API 获取用户媒体

如何为 Instagram 基本显示 API 实现 Instagram 或 Facebook 登录按钮的继续?

Instagram API 和分页