Osintgram

Comment utiliser Osintgram : chaque commande expliquée

Osintgram est un shell interactif lié à un seul nom d'utilisateur cible : vous le lancez avec python3 main.py <target>, puis vous tapez une commande à la fois à une invite « Run a command: ». La commande list intégrée affiche 23 commandes, soit trois de plus que ce que documente le README, et elles figurent toutes dans le tableau ci-dessous.

18 min de lectureÉquipe Osintgram

Osintgram n'est pas un scanner que l'on pointe sur un nom d'utilisateur avant d'aller voir ailleurs. main.py prend un argument positionnel obligatoire, construit un client API, affiche une bannière, puis boucle sur une invite jusqu'à ce que vous tapiez quit. Chaque commande s'applique à cette cible unique, et rien ne se produit tant que vous n'avez rien saisi.

Lancez avec python3 main.py <target>, puis saisissez vos commandes à l'invite Run a command: . La commande list intégrée affiche 23 commandes ; le README n'en documente que 20. L'écriture de fichiers est désactivée par défaut. Tapez d'abord FILE=y ou JSON=y, ou lancez avec -f / -j. Les quatre commandes de collecte de contacts (fwersemail, fwingsemail, fwersnumber, fwingsnumber) émettent un appel API par abonné sans aucun backoff : ce sont elles qui vous font bloquer.

Comment fonctionne le shell

Ce guide suppose un clone fonctionnel et un config/credentials.ini renseigné ; sinon, commencez par comment installer Osintgram. Sur Kali, c'est pareil, avec quelques pièges propres à la distribution.

Lancez toujours depuis la racine du dépôt

src/config.py lit config/credentials.ini comme un chemin relatif : depuis votre répertoire personnel, python3 ~/Osintgram/main.py target ne trouve donc aucun identifiant, et sans le moindre message. Faites d'abord un cd dans le clone. Exécuter le script depuis ailleurs est aussi la cause documentée de l'issue #105, ModuleNotFoundError: No module named 'src.Osintgram'.

Au démarrage, le backend classique affiche la bannière de la cible, à savoir Logged as <you>. Target: <target> [<numeric id>], plus [PRIVATE PROFILE] quand le compte est privé, et soit [FOLLOWING], soit [NOT FOLLOWING]. C'est le constructeur qui l'affiche, d'où son apparition avant le logo ASCII. La bannière HikerAPI est plus courte : pas de ligne de connexion, pas d'état d'abonnement. Vient ensuite l'invite : la chaîne littérale Run a command: , en jaune.

La complétion par TAB est branchée sur le dictionnaire de commandes via gnureadline sous Linux et macOS, ou pyreadline sous Windows : les noms partiels se complètent donc. Tout ce que le dictionnaire ne reconnaît pas affiche Unknown command en rouge ; une ligne vide se contente de produire une ligne vide. quit et exit affichent tous deux Goodbye! puis quittent, et Ctrl-C est intercepté pour faire la même chose.

Run a command: FILE=y
Run a command: JSON=y
Run a command: info
Run a command: followers
Run a command: target
Run a command: quit
Ce qu'il faut taper, et non une session capturée : main.py affiche l'invite, le reste est votre saisie. TAB complète les noms de commandes, mais pas les bascules FILE= et JSON=.

Vous n'êtes pas prisonnier du nom d'utilisateur avec lequel vous avez démarré. La commande target affiche Insert new target username: , résout le nouveau compte et réaffiche la bannière, sans nouvelle connexion. Elle a un défaut : setTarget() ajoute le nom de la cible au répertoire de sortie à chaque exécution, si bien qu'après un changement vos fichiers atterrissent dans output/<first target>/<second target>/ plutôt que dans un dossier frère.

Trois façons de le lancer

Le README documente trois modes de lancement, et leurs différences ne sont pas que syntaxiques.

# 1. interactive shell
python3 main.py <target username>

# 2. one command, then exit
python3 main.py <target username> --command info

# 3. HikerAPI backend, no Instagram login of your own
HIKERAPI_TOKEN=<hikerapi token> python3 main.py <target username> -c info
Les trois invocations listées dans le README, avec une commande réelle en substitution.

Le mode commande unique n'est pas seulement le shell avec un tour joué à votre place. Quand -c est défini, main.py saute printlogo() et le constructeur supprime la ligne Attempt to login... : la sortie est donc beaucoup plus silencieuse, et photos cesse de demander combien de fichiers télécharger pour tout prendre. La boucle s'interrompt aussi après exactement une itération, si bien que -c FILE=y bascule le drapeau et quitte sans rien exécuter. Utilisez plutôt -f et -j.

Le troisième mode est celui qui compte le plus en 2026. Si config.getHikerToken() renvoie quoi que ce soit (depuis le champ hikerapi_token de credentials.ini ou depuis la variable d'environnement HIKERAPI_TOKEN), main.py instancie HikerCLI au lieu de la classe classique Osintgram, et aucune connexion Instagram n'a lieu. La ligne de démarrage devient Connect to HikerAPI.... Voyez-y la voie encore conçue pour fonctionner plutôt qu'une voie garantie : l'issue #2664 (2026-06-21) signale que cette branche gère mal les formats alternatifs de réponse utilisateur, et HikerAPI est un tiers payant à qui vous confiez vos cibles. Savoir si la voie classique par identifiant et mot de passe vous authentifie encore est une autre question, traitée dans Osintgram fonctionne-t-il encore.

OptionCe qu'elle faitRemarques
-c, --commandExécute une seule commande puis quitteSupprime le logo et la ligne de connexion ; photos ne pose plus de question
-f, --fileÉcrit une sortie .txt pour la sessionIdentique à la saisie de FILE=y
-j, --jsonÉcrit une sortie JSON pour la sessionIdentique à la saisie de JSON=y
-o, --outputLe texte d'aide annonce « where to store photos »Remplace en réalité tout le répertoire de base de sortie, pour tous les types de fichiers
-C, --cookiesEfface la session en cache avant le démarrageMême effet que la commande cache : config/settings.json est réinitialisé à un objet vide
L'ensemble complet des options, lu dans le bloc argparse de main.py. Il y en a exactement cinq ; --file-output n'existe pas.

Toutes les commandes Osintgram

Le README liste 20 commandes. La fonction cmdlist() qu'appelle la commande list intégrée en affiche 23. Les trois qu'elle ajoute sont cache, commentdata et target, et aucune n'apparaît dans le bloc de commandes du README. doc/COMMANDS.md est encore plus en retard : son bloc d'en-tête omet fwersnumber et fwingsnumber (les deux ont pourtant une section plus bas), et il ne documente nulle part cache, commentdata ni target. Pour la liste qui fait foi, tapez list dans le shell ou lisez le dictionnaire commands de main.py.

CommandeCe qu'elle renvoieÉtat et remarques
addrsPositions GPS taguées dans les publications de la cibleGénéralement vide. Ne compte que les publications avec lat et lng, puis géocode chacune en inverse via Nominatim
cacheEfface le fichier de session mis en cacheLocal uniquement, aucun appel réseau. Affiche Cache Cleared. ou Settings.json don't exist. ; sur HikerAPI, elle dit simplement Cache is already empty.
captionsLégendes des publications de la cibleSon export JSON écrit dans le mauvais fichier (voir plus bas)
commentdataTous les commentaires de toutes les publications, avec l'identifiant et le nom de l'auteurNon documentée dans le README. Son export JSON est malformé
commentsNombre total de commentaires sur l'ensemble des publicationsParcourt tout le fil pour compter
followersListe des abonnés : id, nom d'utilisateur, nom completAucun gestionnaire de throttling : une limite atteinte ici ressort en traceback brut
followingsComptes suivis par la cibleMême gestionnaire manquant que pour followers
fwersemailAdresses e-mail publiques publiées par les abonnés de la cibleUn appel API supplémentaire par abonné. Le principal aimant à throttling
fwingsemailAdresses e-mail publiques des comptes suivis par la cibleMême schéma d'un appel par utilisateur
fwersnumberNuméros de téléphone publics des abonnés de la cibleMême schéma, plus un nom de fichier JSON mal orthographié
fwingsnumberNuméros de téléphone publics des comptes suivis par la cibleMême schéma
hashtagsHashtags utilisés par la ciblePasse sur tout le fil
infoMétadonnées du profil sous forme d'étiquettes entre crochetsL'une des rares commandes qui contourne le garde-fou profil privé. Ignore FILE=y : elle n'écrit jamais que du JSON
likesNombre total de likes sur l'ensemble des publicationsPasse sur tout le fil
mediatypeCombien de publications sont des photos et combien des vidéosPasse sur tout le fil
photodesDescriptions alt-text des photosMorte. Elle appelle le point de terminaison web retiré ?__a=1 ; la voie HikerAPI répond Instagram has disabled this functionality.
photosTélécharge les publications en .jpg dans le dossier de sortieDemande un nombre ; prend tout sous -c. Énumère tout le fil avant d'appliquer la limite
propicTélécharge la photo de profilContourne aussi le garde-fou profil privé
storiesTélécharge les stories actives en .jpg ou .mp4KeyError: 'media_count' est signalé quand la charge utile du reel ne contient pas la clé
taggedUtilisateurs que la cible a identifiés dans ses propres publicationsPas de garde-fou explicite, mais elle lit le fil : il lui faut donc quand même l'accès au fil
targetBascule vers une nouvelle cible sans redémarrerNon documentée dans le README. Imbrique le répertoire de sortie à chaque changement
wcommentedUtilisateurs ayant commenté les publications, classés par nombreTout le fil, plus une récupération des commentaires par publication (lent sur les comptes actifs)
wtaggedUtilisateurs ayant identifié la cible, classés par nombreLit le fil des identifications ; sa boucle de pagination bascule sur le fil de la cible
Les descriptions paraphrasent les chaînes affichées par cmdlist(). L'état est lu dans main.py, src/Osintgram.py et src/hikercli.py ainsi que dans le suivi public des issues ; nous n'avons pas exécuté la CLI sur un compte Instagram réel.

Quatre autres entrées du dictionnaire ne sont pas des commandes de données : list et help affichent cette même liste, quit et exit quittent. Deux saisies sont traitées entièrement en dehors du dictionnaire (FILE=y/n et JSON=y/n), d'où le fait que la complétion par TAB ne les propose jamais.

Une réserve vaut pour tout le tableau : rien ne renvoie de données si le backend ne parvient pas à s'authentifier. Chaque ligne suppose que vous avez passé la connexion, avec une session acceptée par le client classique ou avec un token HikerAPI. En 2026, ce ne sont pas les commandes qui cassent en premier.

Lire la sortie de info

info est la commande que vous lancerez en premier et celle qui risque le plus de vous induire en erreur. Elle appelle le point de terminaison privé users/{user_id}/full_detail_info/ et lit content['user_detail']['user'], puis affiche une séquence fixe d'étiquettes entre crochets :

  • [ID], [FULL NAME], [BIOGRAPHY] : toujours affichés
  • [FOLLOWED], [FOLLOW] : toujours affichés, et pas ce qu'ils semblent être
  • [BUSINESS ACCOUNT], puis [BUSINESS CATEGORY] uniquement si le compte est professionnel et n'a pas masqué sa catégorie
  • [VERIFIED ACCOUNT], puis [EMAIL] uniquement si une adresse e-mail publique est renseignée
  • [HD PROFILE PIC] : toujours affiché, sous forme d'URL
  • [FB PAGE], [WHATSAPP NUMBER], [CITY], [ADDRESS STREET], [CONTACT PHONE NUMBER] : chacun affiché uniquement si le champ est renseigné

Le piège [FOLLOWED] / [FOLLOW]

[FOLLOWED] est le nombre d'abonnés et [FOLLOW] le nombre d'abonnements. Les étiquettes disent l'inverse de ce qu'elles signifient. L'export JSON tranche : ces deux mêmes valeurs y sont écrites sous les clés edge_followed_by et edge_follow, les anciens noms de l'API web pour followers et following. Si vous recopiez ces chiffres, prenez-les dans l'export JSON.

Le backend HikerAPI affiche une ligne [MEDIA] supplémentaire avec le nombre de publications, que la voie classique ne montre jamais : la même commande produit donc une liste de champs différente selon le backend. Et quand l'appel sous-jacent lève une exception, le gestionnaire affiche Oops... <target> non exist, please enter a valid username. puis sort avec le code 2, un message auquel il ne faut pas se fier. L'issue #1020 montre Instagram renvoyant un corps d'erreur non JSON et l'outil le rapportant comme un utilisateur inexistant.

Enregistrer la sortie, et les noms de fichiers qui se marchent dessus

Rien n'est écrit sur le disque sans que vous le demandiez. Tapez FILE=y pour une sortie texte et JSON=y pour du JSON à n'importe quel moment de la session ; l'outil confirme par Write to file: enabled et Export to JSON: enabled, et FILE=n / JSON=n les désactivent. Lancer avec -f ou -j positionne les mêmes booléens avant la première commande.

Sur le master actuel, les résultats atterrissent dans un sous-répertoire propre à la cible. Sur le tag de version 1.3, ils atterrissaient dans un output/ plat, d'où la mise en page différente des guides plus anciens. Le fichier .txt contient le tableau ASCII PrettyTable brut (file.write(str(t))) et non du CSV : n'espérez donc pas l'ouvrir dans un tableur.

output/
|-- dont_delete_this_folder.txt
`-- <target>/
    |-- <target>_followers.txt
    |-- <target>_followers.json
    |-- <target>_propic.jpg
    |-- <target>_<photo id>.jpg
    `-- <target>_<story id>.mp4
Arborescence de sortie sur master. L'option -o remplace la base « output », pas seulement le chemin des images.

C'est sur les noms que cela se gâte. Trois commandes écrivent leur JSON sous un nom de fichier qui ne correspond pas à la commande tapée, l'une d'elles écrase un résultat précédent, et deux autres cassent le schéma à leur manière.

Commande avec JSON=yFichier réellement écritProblème
captions<target>_followings.jsonÉcrase le fichier écrit par followings. Bug de copier-coller dans get_captions
photodes<target>_descriptions.jsonLe nom ne correspond pas à la commande
fwersnumber<target>_fwerssnumber.jsonDouble « s », et la clé à l'intérieur de l'objet est followings_phone_numbers
commentdata<target>_comment_data.jsonÉcrit à la main avec une virgule finale et sans séparateur entre les objets : json.load() échoue dessus
info<target>_info.json uniquementIl n'existe aucun writer .txt ; FILE=y ne fait rien ici
Lu dans les blocs d'écriture de src/Osintgram.py sur master.

N'exécutez pas captions après followings dans la même session

Avec JSON=y activé, captions écrit dans <target>_followings.json. Si vous avez d'abord exporté la liste des abonnements, lancer captions la remplace par les légendes, sans confirmation, sans sauvegarde et sans avertissement. Exportez les légendes dans une session distincte, ou renommez d'abord le fichier des abonnements.

Les commandes qui déclenchent le throttling

Les quatre commandes de collecte de contacts partagent la même architecture. get_fwersemail() pagine d'abord la liste entière des abonnés, en affichant un compteur Catched N followers email qui défile. Ce n'est qu'ensuite qu'elle demande Do you want to get all emails? y/n: . Puis, pour chaque abonné collecté, elle émet un appel user_info distinct et ne conserve l'enregistrement que si une adresse e-mail publique est renseignée.

Deux conséquences en découlent. Votre limite est appliquée après l'énumération complète : répondre « n » et demander 200 adresses parcourt quand même toute la liste des abonnés au préalable. Et la limite ne compte que les correspondances, si bien qu'une cible dont les abonnés publient rarement une adresse est énumérée presque de bout en bout quoi qu'il arrive. Ces boucles ne contiennent ni sleep, ni jitter, ni backoff ; l'issue #657 est un correctif communautaire qui en ajoute un.

Demander moins de résultats ne coûte pas moins cher

La liste des abonnés est énumérée intégralement avant que l'invite apparaisse, et un appel de profil est ensuite émis pour chaque abonné. Sur le backend classique, c'est un volume de requêtes qu'Instagram vous décompte ; sur HikerAPI, la même boucle appelle user_by_id_v2 une fois par utilisateur, donc c'est un volume de requêtes que vous payez. Aucun des deux backends ne vous donne un échantillon bon marché d'une grande liste d'abonnés.

L'allure d'un blocage dépend de la commande. Les quatre commandes de contacts interceptent ClientThrottledError et affichent Error: Instagram blocked the requests. Please wait a few minutes before you try again. followers et followings n'ont pas de gestionnaire équivalent : la même condition s'échappe donc en traceback brut se terminant par urllib.error.HTTPError: HTTP Error 429: Too Many Requests, la forme rapportée dans l'issue #394.

Le gestionnaire ne sauvegarde pas vos données pour autant. Dans l'issue #366, un utilisateur a atteint Catched 41643 followers email avant le blocage, et le chemin de récupération a ensuite planté sur TypeError: string indices must be integers, jetant tous les enregistrements collectés. L'issue #342 fait état d'une coupure autour de 35 000 abonnés sur une cible à six chiffres. Ce sont les seuls chiffres concrets qui existent : aucun seuil horaire fiable n'a été publié, et tout guide qui vous en donne un en minutes fait de la devinette.

photos, comments, likes, mediatype, hashtags, wcommented et commentdata paginent tous le fil en entier, et les deux dernières récupèrent en plus les commentaires publication par publication. Elles déclenchent rarement un blocage, mais sur un compte comptant des milliers de publications, elles sont bien plus lentes qu'elles n'en ont l'air.

Les commandes qui ne renvoient rien

Un Sorry! No results found :-( en rouge n'est pas une erreur. Cela signifie que la requête a abouti et que la cible n'a pas de données de ce type. La plupart des commandes peuvent l'afficher.

addrs est le suspect habituel. Elle ne compte que les publications dont l'objet de localisation porte à la fois une latitude et une longitude, et les publications géolocalisées sont rares depuis qu'Instagram a retiré la carte des photos. Ce qui existe malgré tout est ensuite géocodé en inverse via Nominatim, lui-même soumis à des limites de débit.

photodes est un autre cas de figure : elle n'est pas vide, elle est morte. Elle interroge toujours https://www.instagram.com/<target>/?__a=1 et va chercher graphql.user.edge_owner_to_timeline_media, un point de terminaison non authentifié qui ne renvoie plus ce JSON. Sur le backend HikerAPI, la fonction a été réduite à une seule ligne qui affiche Instagram has disabled this functionality. Considérez-la comme supprimée.

stories ne renvoie rien quand rien n'est en ligne, ce qui est normal, mais son parseur est également fragile : l'issue #1258 la montre s'interrompant sur KeyError: 'media_count' quand la charge utile du reel ne contient pas cette clé.

Cibles privées, en bref

Si la bannière affiche [PRIVATE PROFILE] à côté de [NOT FOLLOWING], presque toutes les commandes du tableau s'arrêtent avant d'émettre la moindre requête. Un garde-fou unique vérifie si le compte est privé et non suivi, affiche Impossible to execute command: user has private profile, et propose Do you want send a follow request? [Y/N]: . Le backend HikerAPI est encore plus expéditif : le caractère privé suffit à bloquer, sans proposition de demande d'abonnement. Aucune option n'y change quoi que ce soit. Le tableau complet (quelles commandes survivent et ce qu'Instagram expose encore publiquement) se trouve dans Osintgram fonctionne-t-il sur les comptes privés.

Faire le même travail sans la CLI

L'éventail de commandes est réellement large. Ce qui rend Osintgram pénible, c'est tout ce qui l'entoure : une cible par session, un fichier d'identifiants, un throttling sans backoff, des noms de fichiers qui s'écrasent, et une procédure de connexion qui échoue pour beaucoup de gens avant même que tout cela compte. Si vous utilisez la CLI pour de vraies recherches, gardez des sessions courtes et exportez un jeu de données à la fois.

Si vous ne voulez que le rapport, une recherche hébergée vous épargne tout cet appareillage : aucun dépôt à cloner, aucun compte Instagram personnel à risquer, aucune boucle de limitation à surveiller. Elle ne couvre que les profils publics, le même plafond que la CLI.

Questions fréquentes

Osintgram est un outil OSINT indépendant, sans aucune affiliation avec Instagram ni Meta. Ces guides décrivent uniquement des logiciels open source publiquement documentés et de la recherche en sources ouvertes. Utilisez ces techniques dans le respect de la loi, sur des cibles que vous êtes autorisé à investiguer, et jamais pour harceler ou surveiller des particuliers.