web-dev-qa-db-fra.com

Comment effectuer un raclage Web Instagram non authentifié en réponse à des modifications récentes de l'API privée?

Il y a quelques mois, Instagram a commencé à rendre son API publique inutilisable en supprimant la plupart des fonctionnalités et en refusant d'accepter de nouvelles applications pour la plupart des portées d'autorisations. D'autres modifications ont été apportées cette semaine , ce qui limite encore les options offertes aux développeurs.

Beaucoup d'entre nous se sont tournés vers l'API Web privée d'Instagram pour mettre en œuvre la fonctionnalité dont nous disposions auparavant. Un ping/instagram_private_api hors concours parvient à reconstruire la plupart des fonctionnalités antérieures. Cependant, avec les modifications annoncées publiquement cette semaine, Instagram a également apporté des modifications sous-jacentes à son API privée, nécessitant des variables magiques, des agents utilisateurs et le hachage MD5. pour rendre possible les requêtes de scrap web. Ceci peut être constaté en suivant les versions récentes du référentiel git précédemment lié , et les changements exacts nécessaires pour continuer à extraire des données peuvent être visualisés ici .

Ces changements incluent:

  • Persistance du jeton Agent utilisateur & CSRF entre les demandes.
  • Faire une demande initiale à https://instagram.com/ pour saisir une clé magique rhx_gis dans le corps de la réponse.
  • Définition de l'en-tête X-Instagram-GIS, formé en concaténant de manière magique la clé rhx_gis et les variables de requête avant de les transmettre via un hachage MD5.

Une valeur inférieure à cette valeur entraînera une erreur 403. Ces modifications ont été implémentées avec succès dans le référentiel ci-dessus . Cependant, ma tentative dans JS continue d'échouer. Dans le code ci-dessous, j'essaie de récupérer les 9 premiers messages d'un scénario utilisateur. Les paramètres de requête qui déterminent ceci sont:

  • query_hash sur 42323d64886122307be10013ad2dcc44 (récupérez le support de la timeline de l'utilisateur).
  • variables.id de n'importe quel ID utilisateur sous forme de chaîne (l'utilisateur à partir duquel extraire le support).
  • variables.first, le nombre de publications à récupérer, sous forme d'entier.

Auparavant, cette requête pouvait être effectuée sans les modifications ci-dessus en récupérant simplement à partir de https://www.instagram.com/graphql/query/?query_hash=42323d64886122307be10013ad2dcc44&variables=%7B%22id%22%3A%225380311726%22%2C%22first%22%3A1%7D, car l'URL n'était pas protégée. 

Cependant, ma tentative d'implémentation réussie de la fonctionnalité dans le référentiel ci-dessus ne fonctionne pas et je ne reçois que 403 réponses d'Instagram. J'utilise superagent comme bibliothèque de demandes, dans un environnement de nœud. 

/*
** 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, csrfTokenCookie, 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`
    }));

Quoi d'autre devrais-je essayer? Pourquoi mon code échoue-t-il et le code fourni dans le référentiel ci-dessus fonctionne-t-il correctement?

Mise à jour (2018-04-17)

Pour la troisième fois au moins dans la semaine, Instagram a de nouveau mis à jour son API. La modification ne nécessite plus que le jeton CSRF fasse partie de la signature hachée.

La question ci-dessus a été mise à jour pour refléter cela.

Mise à jour (2018-04-14)

Instagram a de nouveau mis à jour son API privée graphql. Autant que quiconque puisse comprendre:

  • Il n'est plus nécessaire que l'agent utilisateur soit inclus dans le calcul X-Instagram-Gis md5.

La question ci-dessus a été mise à jour pour refléter cela.

23

Valeurs à persister

Vous ne persistez pas l'agent utilisateur (condition requise) dans la première requête à Instagram:

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

Devrait être:

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

Cela doit être maintenu dans chaque demande, avec le cookie csrftoken

Génération d'en-têtes X-Instagram-GIS

Comme votre réponse l'indique, vous devez générer l'en-tête X-Instagram-GIS à partir de deux propriétés, la valeur rhx_gis trouvée dans votre demande initiale et les variables de requête dans votre demande suivante. Ceux-ci doivent être hachés md5, comme indiqué dans votre fonction ci-dessus:

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

Uhm ... Je n'ai pas de nœud installé sur ma machine, je ne peux donc pas vérifier, mais il me semble qu'il vous manque une partie cruciale des paramètres de la chaîne de requête, à savoir le champ after:

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

A partir de ces queryVariables dépendent votre hachage MD5, celui-ci ne correspond donc pas à celui attendu. Essayez ça: je pense que ça va marcher.

MODIFIER:

En lisant attentivement votre code, cela n'a malheureusement aucun sens. J'en déduis que vous essayez d'extraire le flux complet d'images du flux d'un utilisateur.

Ensuite, vous devez non appeler la page d'accueil Instagram comme vous le faites maintenant (superagent.get('https://www.instagram.com/')), mais plutôt le flux de l'utilisateur (superagent.get('https://www.instagram.com/your_user')).

Attention: vous devez coder en dur le même agent d'utilisateur que celui que vous allez utiliser ci-dessous (et cela ne ressemble pas à vous ...).

Ensuite, vous devez extraire l'ID de la requête (c'est pas codé en dur, il change toutes les quelques heures, parfois quelques minutes; le codage en dur est insensé - toutefois, pour ce POC, vous pouvez le garder en dur), ainsi que le curseur_fin. Pour le curseur final, je choisirais quelque chose comme ceci: 

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

Vous avez maintenant tout ce dont vous avez besoin pour effectuer la requête 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();
1
Gianluca

query_hash n'est pas constant et continue à évoluer dans le temps.

Par exemple, les scripts ProfilePage incluent ces scripts:

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

Le hachage est situé dans l'un des scripts ci-dessus, par exemple. pour 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