如何执行未经身份验证的Instagram网页抓取以响应最近的私有API更改?

时间:2018-04-12 02:20:01

标签: javascript web-scraping instagram instagram-api

几个月前,Instagram开始通过删除大多数功能并拒绝接受大多数权限范围的新应用程序来使其公共API无法运行。 Further changes were made this week进一步限制了开发人员的选择。

我们中的许多人已转向使用Instagram的私人网络API来实现我们之前拥有的功能。一个杰出的ping/instagram_private_api设法重建大部分先前的功能,然而,随着本周公开宣布的更改,Instagram也对他们的私有API进行了底层更改,需要魔术变量,用户代理和MD5哈希来制作网页抓取请求可能。这可以通过following the recent releases on the previously linked git repository看到,继续提取数据所需的确切更改可以be seen here

这些变化包括:

  • 坚持用户代理&请求之间的CSRF令牌。
  • https://instagram.com/发出初始请求以从响应正文中获取rhx_gis魔术密钥。
  • 设置X-Instagram-GIS标头,该标头是通过在传递MD5哈希之前神奇地连接rhx_gis密钥和查询变量而形成的。

任何小于此值都会导致403错误。这些更改已成功实现in the above repository,但是,我在JS中的尝试仍然失败。在下面的代码中,我试图从用户时间轴中获取前9个帖子。确定这一点的查询参数是:

    {li} query_hash 42323d64886122307be10013ad2dcc44(从用户的时间轴中获取媒体)。
  • variables.id任何用户ID作为字符串(从中获取媒体的用户)。
  • variables.first,要获取的帖子数,为整数。

以前,只需从https://www.instagram.com/graphql/query/?query_hash=42323d64886122307be10013ad2dcc44&variables=%7B%22id%22%3A%225380311726%22%2C%22first%22%3A1%7D进行GET,就可以在没有任何上述更改的情况下发出此请求,因为该网址未受保护。

但是,我尝试实现在上述存储库中成功编写的功能不起作用,我只收到来自Instagram的403个回复。我在节点环境中使用superagent作为我的请求库。

/*
** Retrieve an arbitrary cookie value by a given key.
*/
const getCookieValueFromKey = function(key, cookies) {
        const cookie = cookies.find(c => c.indexOf(key) !== -1);
        if (!cookie) {
            throw new Error('No key found.');
        }
        return (RegExp(key + '=(.*?);', 'g').exec(cookie))[1];
    };

/*
** Calculate the value of the X-Instagram-GIS header by md5 hashing together the rhx_gis variable and the query variables for the request.
*/
const generateRequestSignature = function(rhxGis, queryVariables) {
    return crypto.createHash('md5').update(`${rhxGis}:${queryVariables}`, 'utf8').digest("hex");
};

/*
** Begin
*/
const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5';

// Make an initial request to get the rhx_gis string
const initResponse = await superagent.get('https://www.instagram.com/');
const rhxGis = (RegExp('"rhx_gis":"([a-f0-9]{32})"', 'g')).exec(initResponse.text)[1];

const csrfTokenCookie = getCookieValueFromKey('csrftoken', initResponse.header['set-cookie']);

const queryVariables = JSON.stringify({
    id: "123456789",
    first: 9
});

const signature = generateRequestSignature(rhxGis, queryVariables);

const res = await superagent.get('https://www.instagram.com/graphql/query/')
    .query({
        query_hash: '42323d64886122307be10013ad2dcc44',
        variables: queryVariables
    })
    .set({
        'User-Agent': userAgent,
        'X-Instagram-GIS': signature,
        'Cookie': `rur=FRC;csrftoken=${csrfTokenCookie};ig_pr=1`
    }));

我还应该尝试什么?是什么让我的代码失败,上面的存储库中提供的代码工作正常?

更新(2018-04-17)

至少在一周内第3次,Instagram再次更新了他们的API。更改不再需要CSRF令牌构成散列签名的一部分。

上述问题已更新,以反映这一点。

更新(2018-04-14)

Instagram再次更新了他们的私有graphql API。任何人都可以搞清楚:

  • 不再需要将用户代理包含在X-Instagram-Gis md5计算中。

上述问题已更新,以反映这一点。

4 个答案:

答案 0 :(得分:16)

要保留的值

您在第一次查询Instagram时不会持久保存用户代理(要求):

const initResponse = await superagent.get('https://www.instagram.com/');

应该是:

const initResponse = await superagent.get('https://www.instagram.com/')
                     .set('User-Agent', userAgent);

这必须与csrftoken Cookie一起保存在每个请求中。

X-Instagram-GIS标题生成

如您的答案所示,您必须从两个属性生成X-Instagram-GIS标头,初始请求中的rhx_gis值以及下一个请求中的查询变量。这些必须是md5哈希值,如上面的函数所示:

const generateRequestSignature = function(rhxGis, queryVariables) {
    return crypto.createHash('md5').update(`${rhxGis}:${queryVariables}`, 'utf8').digest("hex");
};

答案 1 :(得分:4)

因此,要调用instagram查询,您需要生成 x-instagram-gis 标头。

要生成此标头,您需要计算下一个字符串“ {rhx_gis}:{path} ”的md5哈希值。 rhx_gis 值存储在 window._sharedData 全局js变量的instagram页面的源代码中。

示例:
如果您尝试获取此类 https://www.instagram.com/ {username} /?__ a = 1 的用户信息请求 您需要添加http标头 x-instagram-gis 以请求哪个值为
 MD5("{rhx_gis}:/{username}/")

这是经过测试并且100%有效,所以请随时询问是否出现问题。

答案 2 :(得分:2)

嗯......我的机器上没有安装Node,所以我无法确认,但在我看来你错过了querystring中参数的关键部分,即{{1} } field:

after

从那些const queryVariables = JSON.stringify({ id: "123456789", first: 4, after: "YOUR_END_CURSOR" }); 取决于您的MD5哈希值,那么,它与预期的哈希值不匹配。试试这个:我希望它能奏效。

编辑:

仔细阅读您的代码,遗憾的是没有多大意义。我推断您正在尝试从用户的Feed中获取完整的图片流。

然后,您需要做的是现在正在调用Instagram主页(queryVariables),而是用户的流(superagent.get('https://www.instagram.com/'))。

注意:您需要对下面要使用的用户代理进行硬编码(看起来不像你......)。

然后,你需要提取查询ID(它的不是硬编码,它每隔几个小时更改一次,有时是几分钟;硬编码是愚蠢的 - 然而,对于这个POC,你可以保持硬编码)和end_cursor。对于结束光标,我会选择这样的东西:

superagent.get('https://www.instagram.com/your_user')

现在,您拥有了制作第二个请求所需的一切:

const endCursor = (RegExp('end_cursor":"([^"]*)"', 'g')).exec(initResponse.text)[1];

答案 3 :(得分:0)

query_hash不是一成不变的,会随着时间的推移而不断变化。

例如,ProfilePage脚本包含以下脚本:

https://www.instagram.com/static/bundles/base/ConsumerCommons.js/9e645e0f38c3.js https://www.instagram.com/static/bundles/base/Consumer.js/1c9217689868.js

散列位于上述脚本之一,例如edge_followed_by

const res = await fetch(scriptUrl, { credentials: 'include' });
const rawBody = await res.text();
const body = rawBody.slice(0, rawBody.lastIndexOf('edge_followed_by'));
const hashes = body.match(/"\w{32}"/g);
// hashes[hashes.length - 2]; = edge_followed_by
// hashes[hashes.length - 1]; = edge_follow