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.
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: quitVous 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 infoLe 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.
| Option | Ce qu'elle fait | Remarques |
|---|---|---|
-c, --command | Exécute une seule commande puis quitte | Supprime le logo et la ligne de connexion ; photos ne pose plus de question |
-f, --file | Écrit une sortie .txt pour la session | Identique à la saisie de FILE=y |
-j, --json | Écrit une sortie JSON pour la session | Identique à la saisie de JSON=y |
-o, --output | Le 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, --cookies | Efface la session en cache avant le démarrage | Même effet que la commande cache : config/settings.json est réinitialisé à un objet vide |
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.
| Commande | Ce qu'elle renvoie | État et remarques |
|---|---|---|
addrs | Positions GPS taguées dans les publications de la cible | Généralement vide. Ne compte que les publications avec lat et lng, puis géocode chacune en inverse via Nominatim |
cache | Efface le fichier de session mis en cache | Local uniquement, aucun appel réseau. Affiche Cache Cleared. ou Settings.json don't exist. ; sur HikerAPI, elle dit simplement Cache is already empty. |
captions | Légendes des publications de la cible | Son export JSON écrit dans le mauvais fichier (voir plus bas) |
commentdata | Tous les commentaires de toutes les publications, avec l'identifiant et le nom de l'auteur | Non documentée dans le README. Son export JSON est malformé |
comments | Nombre total de commentaires sur l'ensemble des publications | Parcourt tout le fil pour compter |
followers | Liste des abonnés : id, nom d'utilisateur, nom complet | Aucun gestionnaire de throttling : une limite atteinte ici ressort en traceback brut |
followings | Comptes suivis par la cible | Même gestionnaire manquant que pour followers |
fwersemail | Adresses e-mail publiques publiées par les abonnés de la cible | Un appel API supplémentaire par abonné. Le principal aimant à throttling |
fwingsemail | Adresses e-mail publiques des comptes suivis par la cible | Même schéma d'un appel par utilisateur |
fwersnumber | Numéros de téléphone publics des abonnés de la cible | Même schéma, plus un nom de fichier JSON mal orthographié |
fwingsnumber | Numéros de téléphone publics des comptes suivis par la cible | Même schéma |
hashtags | Hashtags utilisés par la cible | Passe sur tout le fil |
info | Métadonnées du profil sous forme d'étiquettes entre crochets | L'une des rares commandes qui contourne le garde-fou profil privé. Ignore FILE=y : elle n'écrit jamais que du JSON |
likes | Nombre total de likes sur l'ensemble des publications | Passe sur tout le fil |
mediatype | Combien de publications sont des photos et combien des vidéos | Passe sur tout le fil |
photodes | Descriptions alt-text des photos | Morte. Elle appelle le point de terminaison web retiré ?__a=1 ; la voie HikerAPI répond Instagram has disabled this functionality. |
photos | Télécharge les publications en .jpg dans le dossier de sortie | Demande un nombre ; prend tout sous -c. Énumère tout le fil avant d'appliquer la limite |
propic | Télécharge la photo de profil | Contourne aussi le garde-fou profil privé |
stories | Télécharge les stories actives en .jpg ou .mp4 | KeyError: 'media_count' est signalé quand la charge utile du reel ne contient pas la clé |
tagged | Utilisateurs que la cible a identifiés dans ses propres publications | Pas de garde-fou explicite, mais elle lit le fil : il lui faut donc quand même l'accès au fil |
target | Bascule vers une nouvelle cible sans redémarrer | Non documentée dans le README. Imbrique le répertoire de sortie à chaque changement |
wcommented | Utilisateurs ayant commenté les publications, classés par nombre | Tout le fil, plus une récupération des commentaires par publication (lent sur les comptes actifs) |
wtagged | Utilisateurs ayant identifié la cible, classés par nombre | Lit le fil des identifications ; sa boucle de pagination bascule sur le fil de la cible |
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>.mp4C'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=y | Fichier réellement écrit | Problème |
|---|---|---|
captions | <target>_followings.json | Écrase le fichier écrit par followings. Bug de copier-coller dans get_captions |
photodes | <target>_descriptions.json | Le nom ne correspond pas à la commande |
fwersnumber | <target>_fwerssnumber.json | Double « 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 uniquement | Il n'existe aucun writer .txt ; FILE=y ne fait rien ici |
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.