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

How to perform unauthenticated Instagram web scraping in response to recent private API changes?

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

我们中的许多人已经转向 Instagram 的私人网站 API 来实现我们以前拥有的功能。一位杰出的 ping/instagram_private_api manages to rebuild most of the prior functionality, however, with the publicly announced changes this week, Instagram also made underlying changes to their private API, requiring in magic variables, user-agents, and MD5 hashing to make web scraping requests possible. This can be seen by following the recent releases on the previously linked git repository, and the exact changes needed to continue fetching data can be seen here.

这些变化包括:

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

以前,由于 URL 未受保护,因此只需从 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。据任何人可以弄清楚:

上面的问题已经更新以反映这一点。

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

要坚持的价值观

您没有在对 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-GISheader代

如您的回答所示,您必须从两个属性生成 X-Instagram-GIS header,即在您的初始请求中找到的 rhx_gis 值,以及您下一个请求中的查询变量要求。这些必须经过 md5 哈希处理,如上面的函数所示:

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

嗯...我的机器上没有安装 Node,所以我无法确定,但在我看来你缺少查询字符串中参数的关键部分,即 after 字段:

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

从那些 queryVariables 取决于您的 MD5 哈希,然后,与预期的不匹配。试试看:我希望它能起作用。

编辑:

仔细阅读你的代码,不幸的是它没有多大意义。我推断您正在尝试从用户的供稿中获取完整的图片流。

那么,您需要做的是不是像现在这样调用Instagram主页(superagent.get('https://www.instagram.com/')),而是用户的流(superagent.get('https://www.instagram.com/your_user')).

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

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

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

现在您已具备发出 第二个 请求所需的一切:

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

const signature = generateRequestSignature(rhxGis, csrfTokenCookie, queryVariables);

const res = await superagent.get('https://www.instagram.com/graphql/query/')
    .query({
        query_hash: '42323d64886122307be10013ad2dcc44',
        variables: queryVariables
    })
    .set({
        'User-Agent': userAgent,
        'Accept': '*/*',
        'Accept-Language': 'en-US',
        'Accept-Encoding': 'gzip, deflate',
        'Connection': 'close',
        'X-Instagram-GIS': signature,
        'Cookie': `rur=${rurCookie};csrftoken=${csrfTokenCookie};mid=${midCookie};ig_pr=1`
    }).send();

因此,为了调用 instagram 查询,您需要生成 x-instagram-gis header。

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

示例:
如果您尝试像这样获取用户信息请求 https://www.instagram.com/{username}/?__a=1
需要加上httpheaderx-instagram-gis来请求哪个值为
MD5("{rhx_gis}:/{username}/")

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