web-dev-qa-db-ja.com

最近のプライベートAPIの変更に対応して、認証されていないInstagram Webスクレイピングを実行するにはどうすればよいですか?

数ヶ月前、Instagramはほとんどの機能を削除し、ほとんどの権限スコープで新しいアプリケーションの受け入れを拒否することで、公開APIを動作不能にし始めました。 今週さらに変更が行われました これにより、開発者の選択肢がさらに制限されます。

私たちの多くは、以前持っていた機能を実装するために、InstagramのプライベートWeb APIに頼ってきました。 1つの傑出した ping/instagram_private_api 以前の機能のほとんどを再構築できますが、今週公開された変更により、InstagramはプライベートAPIに根本的な変更を加え、マジック変数、ユーザーエージェント、 Webスクレイピングリクエストを可能にするMD5ハッシュ。これは 以前にリンクされたgitリポジトリの最近のリリースに続く で見ることができ、データの取得を続けるために必要な正確な変更は こちらをご覧ください です。

これらの変更は次のとおりです。

  • リクエスト間でユーザーエージェントとCSRFトークンを永続化します。
  • https://instagram.com/に最初のリクエストを行い、応答本文からrhx_gisマジックキーを取得します。
  • X-Instagram-GISヘッダーを設定します。これは、rhx_gisキーとクエリ変数をMD5ハッシュに渡す前に魔法のように連結して形成されます。

これよりも小さいものはすべて403エラーになります。これらの変更は正常に実装されました 上記のリポジトリ内 、しかし、JSでの私の試みは失敗し続けます。以下のコードでは、ユーザーのタイムラインから最初の9件の投稿を取得しようとしています。これを決定するクエリパラメータは次のとおりです。

  • query_hash of 42323d64886122307be10013ad2dcc44(ユーザーのタイムラインからメディアを取得します)。
  • variables.id任意のユーザーIDの文字列(メディアを取得するユーザー)。
  • variables.first、取得する投稿の数(整数)。

以前は、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の応答のみを受け取ります。ノード環境で、要求ライブラリとしてスーパーエージェントを使用しています。

/*
** 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)

少なくとも1週間に3回は、Instagramが再びAPIを更新しました。この変更では、CSRFトークンがハッシュ署名の一部を形成する必要がなくなりました。

上記の質問は、これを反映するために更新されました。

更新(2018-04-14)

Instagramは再び専用のgraphql APIを更新しました。誰でも理解できる限り:

  • ユーザーエージェントをX-Instagram-Gis md5計算に含める必要がなくなりました。

上記の質問は、これを反映するために更新されました。

26

持続する値

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値と次のリクエストでのクエリ変数の2つのプロパティからrhx_gisヘッダーを生成する必要があります。上記の関数に示すように、これらはmd5ハッシュする必要があります。

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

そのため、Instagramクエリを呼び出すには、x-instagram-gisヘッダーを生成する必要があります。

このヘッダーを生成するには、次の文字列「{rhx_gis}:{path}」のmd5ハッシュを計算する必要があります。 rhx_gis値は、instagramページのソースコードのwindow._sharedDataグローバルjs変数に格納されます。

例:
このようなユーザー情報要求を取得しようとするとhttps://www.instagram.com/ {username} /?__ a = 1
httpヘッダーx-instagram-gisを追加して、どの値が
MD5("{rhx_gis}:/{username}/")

これはテストされており、100%動作します。問題が発生した場合は、お気軽にお問い合わせください。

4
olllejik

うーん...マシンにNodeがインストールされていないので、確認することはできませんが、クエリ文字列のパラメーターの重要な部分、つまりafterが欠落しているように見えますフィールド:

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

それらからqueryVariablesはMD5ハッシュに依存するため、予想されるものと一致しません。それを試してください:私はそれが動作することを期待しています。

編集:

コードを注意深く読んでも、残念ながらあまり意味がありません。ユーザーのフィードから写真の完全なストリームを取得しようとしていると推測します。

次に、あなたがする必要があるのはnot今やっているようにInstagramのホームページを呼び出す(superagent.get('https://www.instagram.com/'))であり、むしろユーザーのストリーム(superagent.get('https://www.instagram.com/your_user'))です。

注意:以下で使用するのとまったく同じユーザーエージェントをハードコーディングする必要があります(そして、あなたのようには見えません...)。

次に、クエリIDを抽出する必要があります(それはnotでハードコーディングされ、数時間ごと、場合によっては数分ごとに変更されます。 end_cursor。終了カーソルについては、次のようなものを探します:

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

これで、secondリクエストを作成するために必要なものがすべて揃いました。

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();
2
Gianluca

query_hashは一定ではなく、時間とともに変化し続けます。

たとえば、ProfilePageスクリプトには次のスクリプトが含まれています。

https://www.instagram.com/static/bundles/base/ConsumerCommons.js/9e645e0f38c3.jshttps://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
0
inDream