Osintgram

Cómo usar Osintgram: cada comando explicado

Osintgram es un shell interactivo ligado a un único nombre de usuario objetivo: lo lanzas con python3 main.py <target> y después escribes un comando cada vez en un prompt "Run a command:". El comando list de la propia herramienta imprime 23 comandos, tres más de los que documenta el README, y todos están en la tabla de abajo.

19 min de lecturaEquipo de Osintgram

Osintgram no es un escáner al que apuntas a un nombre de usuario y dejas trabajando solo. main.py recibe un único argumento posicional obligatorio, construye un cliente de la API, imprime un banner y luego se queda en bucle sobre un prompt hasta que escribes quit. Cada comando se aplica a ese único objetivo, y no pasa nada hasta que escribes algo.

Lánzalo con python3 main.py <target> y escribe comandos en el prompt Run a command: . El list interno muestra 23 comandos; el README solo documenta 20. La escritura de archivos está desactivada por defecto: escribe antes FILE=y o JSON=y, o lanza con -f / -j. Los cuatro comandos de recolección de contactos (fwersemail, fwingsemail, fwersnumber, fwingsnumber) lanzan una llamada a la API por cada seguidor sin ningún backoff, y son los que hacen que te bloqueen.

Cómo funciona el shell

Esta guía da por hecho que tienes un clon funcional y un config/credentials.ini relleno; si no es así, empieza por cómo instalar Osintgram. En Kali vale lo mismo, con algunas trampas propias de la distro.

Lánzalo siempre desde la raíz del repositorio

src/config.py lee config/credentials.ini como ruta relativa, así que python3 ~/Osintgram/main.py target desde tu directorio personal no encuentra credenciales y no avisa de nada. Entra antes en el clon con cd. Ejecutarlo desde otro sitio es además la causa documentada de la issue #105, ModuleNotFoundError: No module named 'src.Osintgram'.

Al arrancar, el backend clásico imprime el banner del objetivo: Logged as <you>. Target: <target> [<numeric id>], más [PRIVATE PROFILE] cuando la cuenta es privada y [FOLLOWING] o [NOT FOLLOWING]. Lo imprime el constructor, y por eso aparece antes del logo ASCII. El banner de HikerAPI es más corto: sin línea de inicio de sesión y sin estado de seguimiento. Después llega el prompt: la cadena literal Run a command: , en amarillo.

El autocompletado con TAB está enganchado al diccionario de comandos a través de gnureadline en Linux y macOS, o pyreadline en Windows, así que los nombres parciales se completan. Todo lo que el diccionario no reconoce imprime Unknown command en rojo; una línea vacía solo imprime una línea en blanco. Tanto quit como exit imprimen Goodbye! y salen, y Ctrl-C está capturado para hacer lo mismo.

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
Lo que hay que escribir, no una sesión capturada: main.py imprime el prompt; el resto lo escribes tú. TAB completa los nombres de comando, pero no los interruptores FILE= y JSON=.

No estás atado al nombre de usuario con el que lanzaste la herramienta. El comando target pregunta Insert new target username: , resuelve la nueva cuenta y vuelve a imprimir el banner, sin un nuevo inicio de sesión. Tiene una pega: setTarget() añade el nombre del objetivo al directorio de salida cada vez que se ejecuta, así que después de un cambio tus archivos acaban en output/<first target>/<second target>/ en lugar de en una carpeta hermana.

Tres formas de lanzarlo

El README documenta tres modos de lanzamiento, y se diferencian en algo más que la sintaxis.

# 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
Las tres invocaciones que aparecen en el README, sustituyendo el marcador por un comando real.

El modo de comando único no es simplemente el shell con un turno ya jugado por ti. Cuando se pasa -c, main.py se salta printlogo() y el constructor suprime la línea Attempt to login..., así que la salida es mucho más silenciosa, y photos deja de preguntar cuántas descargar y se lo lleva todo. El bucle además se rompe tras exactamente una iteración, de modo que -c FILE=y activa el flag y sale sin ejecutar nada: usa -f y -j en su lugar.

El tercer modo es el que más importa en 2026. Si config.getHikerToken() devuelve algo, ya sea desde el campo hikerapi_token de credentials.ini o desde la variable de entorno HIKERAPI_TOKEN, main.py instancia HikerCLI en lugar de la clase clásica Osintgram, y no se produce ningún inicio de sesión en Instagram. La línea de arranque pasa a ser Connect to HikerAPI.... Tómatelo como la vía que todavía está pensada para funcionar, no como una garantía: la issue #2664 (2026-06-21) informa de que esa rama gestiona mal formatos alternativos de respuesta de usuario, y HikerAPI es un tercero de pago al que le entregas tus objetivos. Si la vía clásica de usuario y contraseña llega siquiera a autenticarte es otra cuestión, tratada en ¿sigue funcionando Osintgram?.

FlagQué haceNotas
-c, --commandEjecuta un solo comando y saleSuprime el logo y la línea de login; photos deja de preguntar
-f, --fileEscribe salida .txt durante la sesiónIdéntico a escribir FILE=y
-j, --jsonEscribe salida JSON durante la sesiónIdéntico a escribir JSON=y
-o, --outputLa ayuda dice "where to store photos"En realidad sustituye todo el directorio base de salida, para cualquier tipo de archivo
-C, --cookiesBorra la sesión en caché antes de empezarMismo efecto que el comando cache: config/settings.json se reinicia a un objeto vacío
El conjunto completo de flags, leído del bloque argparse de main.py. Son exactamente cinco; no existe ningún --file-output.

Todos los comandos de Osintgram

El README enumera 20 comandos. La función cmdlist() a la que llama el list interno imprime 23. Los tres que añade son cache, commentdata y target, y ninguno aparece en el bloque de comandos del README. doc/COMMANDS.md va todavía más atrasado: su bloque de cabecera se deja fuera fwersnumber y fwingsnumber (los dos tienen sección más abajo) y no documenta cache, commentdata ni target por ninguna parte. Si quieres la lista autoritativa, escribe list en el shell o lee el diccionario commands de main.py.

ComandoQué devuelveEstado y notas
addrsUbicaciones GPS etiquetadas en las publicaciones del objetivoCasi siempre vacío. Solo cuenta las publicaciones con lat y lng, y después geocodifica cada una a la inversa mediante Nominatim
cacheBorra el archivo de sesión en cachéSolo local, sin llamada de red. Imprime Cache Cleared. o Settings.json don't exist.; en HikerAPI se limita a decir Cache is already empty.
captionsPies de foto de las publicaciones del objetivoSu exportación a JSON escribe en el nombre de archivo equivocado; ver más abajo
commentdataTodos los comentarios de todas las publicaciones, con id y nombre de usuario del autorNo documentado en el README. Su exportación a JSON está mal formada
commentsNúmero total de comentarios en el conjunto de publicacionesRecorre el feed entero para contarlos
followersLista de seguidores: id, nombre de usuario, nombre completoSin gestor de throttling: aquí un límite de peticiones sale como un traceback en crudo
followingsCuentas a las que sigue el objetivoLe falta el mismo gestor que a followers
fwersemailCorreos públicos publicados por los seguidores del objetivoUna llamada extra a la API por seguidor. El principal imán de throttling
fwingsemailCorreos públicos de las cuentas a las que sigue el objetivoEl mismo patrón de una llamada por usuario
fwersnumberNúmeros de teléfono públicos de los seguidores del objetivoMismo patrón, más un nombre de archivo JSON mal escrito
fwingsnumberNúmeros de teléfono públicos de las cuentas a las que sigue el objetivoMismo patrón
hashtagsHashtags que usa el objetivoPasada por todo el feed
infoMetadatos del perfil como etiquetas entre corchetesUno de los pocos comandos que se salta la comprobación de perfil privado. Ignora FILE=y: solo escribe JSON
likesNúmero total de likes en el conjunto de publicacionesPasada por todo el feed
mediatypeCuántas publicaciones son fotos y cuántas vídeosPasada por todo el feed
photodesDescripciones de texto alternativo de las fotosMuerto. Llama al endpoint web retirado ?__a=1; la vía de HikerAPI responde Instagram has disabled this functionality.
photosDescarga las publicaciones como .jpg en la carpeta de salidaPregunta cuántas quieres; con -c se lo lleva todo. Enumera el feed entero antes de aplicar el límite
propicDescarga la foto de perfilTambién se salta la comprobación de perfil privado
storiesDescarga como .jpg o .mp4 las historias activas en ese momentoSe ha reportado KeyError: 'media_count' cuando el payload del reel no trae esa clave
taggedUsuarios a los que el objetivo etiquetó en sus propias publicacionesSin comprobación explícita, pero lee el feed, así que sigue necesitando acceso a él
targetCambia a un nuevo objetivo sin reiniciarNo documentado en el README. Anida el directorio de salida en cada cambio
wcommentedUsuarios que comentaron en las publicaciones, ordenados por númeroTodo el feed más una petición de comentarios por publicación: lento en cuentas activas
wtaggedUsuarios que etiquetaron al objetivo, ordenados por númeroLee el feed de etiquetados; su bucle de paginación cambia al feed del objetivo
Las descripciones parafrasean las cadenas que imprime cmdlist(). El estado se ha leído de main.py, src/Osintgram.py y src/hikercli.py más el rastreador de issues público; no hemos ejecutado la CLI contra una cuenta real de Instagram.

Otras cuatro entradas del diccionario no son comandos de datos: list y help imprimen esa misma lista, quit y exit salen. Y hay dos entradas que se gestionan por completo fuera del diccionario, FILE=y/n y JSON=y/n, que es justo por lo que el autocompletado con TAB nunca las ofrece.

Una advertencia se aplica a toda la tabla: nada devuelve datos si el backend no consigue autenticarse. Todas las filas dan por supuesto que has pasado el login, ya sea con una sesión que el cliente clásico acepte o con un token de HikerAPI. En 2026 lo que se rompe primero no son los comandos.

Cómo leer la salida de info

info es el comando que ejecutarás primero y el que más papeletas tiene para confundirte. Llama al endpoint privado users/{user_id}/full_detail_info/, lee content['user_detail']['user'] y después imprime una secuencia fija de etiquetas entre corchetes:

  • [ID], [FULL NAME], [BIOGRAPHY]: siempre se imprimen
  • [FOLLOWED], [FOLLOW]: siempre se imprimen, y no son lo que parecen
  • [BUSINESS ACCOUNT], y después [BUSINESS CATEGORY] solo cuando la cuenta es de empresa y no ha ocultado su categoría
  • [VERIFIED ACCOUNT], y después [EMAIL] solo cuando hay un correo público configurado
  • [HD PROFILE PIC]: siempre se imprime, como URL
  • [FB PAGE], [WHATSAPP NUMBER], [CITY], [ADDRESS STREET], [CONTACT PHONE NUMBER]: cada uno se imprime solo cuando el campo tiene valor

La trampa de [FOLLOWED] / [FOLLOW]

[FOLLOWED] es el número de seguidores y [FOLLOW] es el número de cuentas seguidas. Las etiquetas se leen justo al revés de lo que significan. La exportación a JSON lo zanja: esos dos mismos valores se escriben bajo las claves edge_followed_by y edge_follow, los nombres antiguos de la API web para followers y following. Si vas a transcribir las cifras, cógelas de la exportación a JSON.

El backend de HikerAPI imprime una línea [MEDIA] adicional con el número de publicaciones que la vía clásica no muestra nunca, así que el mismo comando devuelve una lista de campos distinta según el backend. Y cuando la llamada subyacente falla, el manejador imprime Oops... <target> non exist, please enter a valid username. y sale con código 2, un mensaje del que no deberías fiarte. La issue #1020 muestra a Instagram devolviendo un cuerpo de error que no es JSON y a la herramienta informando de ello como si el usuario no existiera.

Guardar la salida, y los nombres de archivo que chocan

No se escribe nada en disco a menos que lo pidas. Escribe FILE=y para salida de texto y JSON=y para JSON en cualquier momento de la sesión; la herramienta lo confirma con Write to file: enabled y Export to JSON: enabled, y FILE=n / JSON=n vuelven a desactivarlos. Lanzarlo con -f o -j pone esos mismos booleanos antes del primer comando.

En el master actual, los resultados caen en un subdirectorio por objetivo. En la etiqueta de la versión 1.3 caían en un output/ plano, y por eso las guías antiguas muestran otra estructura. El archivo .txt contiene la tabla ASCII de PrettyTable en crudo, file.write(str(t)), no CSV, así que no esperes abrirlo en una hoja de cálculo.

output/
|-- dont_delete_this_folder.txt
`-- <target>/
    |-- <target>_followers.txt
    |-- <target>_followers.json
    |-- <target>_propic.jpg
    |-- <target>_<photo id>.jpg
    `-- <target>_<story id>.mp4
Estructura de salida en master. El flag -o sustituye la base "output", no solo la ruta de las imágenes.

Donde la cosa se enreda es en los nombres. Tres comandos escriben JSON con un nombre de archivo que no se corresponde con el comando que has escrito, uno de ellos sobrescribe un resultado anterior y otros dos rompen el patrón a su manera.

Comando con JSON=yArchivo que escribe en realidadProblema
captions<target>_followings.jsonSobrescribe el archivo que escribió followings. Error de copiar y pegar en get_captions
photodes<target>_descriptions.jsonEl nombre no coincide con el comando
fwersnumber<target>_fwerssnumber.jsonDoble "s", y la clave dentro del objeto es followings_phone_numbers
commentdata<target>_comment_data.jsonEscrito a mano, con una coma final y sin separadores entre objetos, así que json.load() falla al leerlo
infoSolo <target>_info.jsonNo existe ningún escritor de .txt; aquí FILE=y no hace nada
Leído de los bloques de escritura de src/Osintgram.py en master.

No ejecutes captions después de followings en la misma sesión

Con JSON=y activado, captions escribe en <target>_followings.json. Si antes has volcado la lista de cuentas seguidas, ejecutar captions la sustituye por los datos de los pies de foto sin preguntar, sin copia de seguridad y sin ningún aviso. Exporta los pies de foto en una sesión aparte, o renombra antes el volcado de followings.

Los comandos que hacen que te limiten

Los cuatro comandos de recolección de contactos comparten una misma arquitectura. get_fwersemail() pagina primero la lista entera de seguidores, imprimiendo un contador en marcha Catched N followers email. Solo después pregunta Do you want to get all emails? y/n: . Y entonces, por cada seguidor recogido, lanza una llamada user_info independiente y se queda con el registro solo si hay un correo público configurado.

De ahí se siguen dos consecuencias. Tu límite se aplica después de la enumeración completa, así que responder "n" y pedir 200 correos sigue recorriendo antes toda la lista de seguidores. Y el límite solo cuenta las coincidencias, de modo que un objetivo cuyos seguidores rara vez publican un correo se enumera igualmente casi de principio a fin. En esos bucles no hay sleep, ni jitter, ni backoff; la issue #657 es un parche de la comunidad que añade uno.

Pedir menos resultados no sale más barato

La lista de seguidores se enumera al completo antes de que aparezca la pregunta, y después se lanza una llamada de perfil por cada seguidor. En el backend clásico eso es volumen de peticiones que Instagram te apunta en la cuenta; en HikerAPI el mismo bucle llama a user_by_id_v2 una vez por usuario, así que es volumen de peticiones que pagas. Ninguno de los dos backends te da una muestra barata de una lista de seguidores grande.

El aspecto que tiene un bloqueo depende del comando. Los cuatro comandos de contactos capturan ClientThrottledError e imprimen Error: Instagram blocked the requests. Please wait a few minutes before you try again. followers y followings no tienen ese manejador, así que la misma situación se escapa como un traceback en crudo que termina en urllib.error.HTTPError: HTTP Error 429: Too Many Requests, la forma reportada en la issue #394.

El manejador tampoco te salva los datos. En la issue #366 un usuario llegó a Catched 41643 followers email antes del bloqueo, y la ruta de recuperación se estrelló después con TypeError: string indices must be integers, descartando todos los registros recogidos. La issue #342 informa de un corte en torno a los 35.000 seguidores sobre un objetivo de seis cifras. Esas son las únicas cifras firmes que existen: no se ha publicado ningún umbral fiable de tiempo real, y cualquier guía que te dé uno en minutos está adivinando.

photos, comments, likes, mediatype, hashtags, wcommented y commentdata paginan el feed entero, y los dos últimos piden además los comentarios de cada publicación. Rara vez provocan un bloqueo, pero en una cuenta con miles de publicaciones son mucho más lentos de lo que parecen.

Comandos que no devuelven nada

Un Sorry! No results found :-( en rojo no es un error. Significa que la petición funcionó y que el objetivo no tiene datos de ese tipo. Casi todos los comandos pueden mostrarlo.

addrs es el sospechoso habitual. Solo cuenta las publicaciones cuyo objeto de ubicación lleva latitud y longitud, y las publicaciones con geoetiqueta escasean desde que Instagram retiró el mapa de fotos. Lo poco que haya se geocodifica luego a la inversa mediante Nominatim, que a su vez tiene su propio límite de peticiones.

photodes es otro caso distinto: no está vacío, está muerto. Sigue pidiendo https://www.instagram.com/<target>/?__a=1 y buscando dentro de graphql.user.edge_owner_to_timeline_media, un endpoint sin autenticar que ya no devuelve ese JSON. En el backend de HikerAPI la función se ha reducido a una sola línea que imprime Instagram has disabled this functionality. Dalo por eliminado.

stories no devuelve nada cuando no hay nada en directo, lo cual es normal, pero además tiene un parser frágil: la issue #1258 lo muestra abortando con KeyError: 'media_count' cuando el payload del reel no incluye esa clave.

Objetivos privados, en breve

Si el banner muestra [PRIVATE PROFILE] junto a [NOT FOLLOWING], casi todos los comandos de la tabla se paran antes de hacer ninguna petición. Una única comprobación mira si la cuenta es privada y no seguida, imprime Impossible to execute command: user has private profile y ofrece Do you want send a follow request? [Y/N]: . El backend de HikerAPI es todavía más tajante: con que sea privada ya bloquea, sin ofrecer solicitud de seguimiento. Ningún flag cambia eso. El panorama completo, qué comandos sobreviven y qué sigue exponiendo Instagram en público, está en ¿funciona Osintgram con cuentas privadas?.

Hacer el mismo trabajo sin la CLI

El repertorio de comandos es realmente amplio. Lo que hace incómodo a Osintgram es todo lo que lo rodea: un objetivo por sesión, un archivo de credenciales, limitación de peticiones sin backoff, nombres de archivo que se pisan entre sí y una vía de login que falla a mucha gente antes de que nada de esto importe. Si vas a usar la CLI para investigación de verdad, mantén las sesiones cortas y exporta un conjunto de datos cada vez.

Si lo único que quieres es el informe, una consulta alojada te ahorra todo el aparato: no hay repositorio que clonar, ni cuenta de Instagram propia que arriesgar, ni bucle de límite de peticiones al que hacer de niñera. Cubre solo perfiles públicos, el mismo techo que tiene la CLI.

Preguntas frecuentes

Osintgram es una herramienta OSINT independiente y no está afiliada a Instagram ni a Meta. Estas guías describen únicamente software de código abierto documentado públicamente e investigación con fuentes abiertas. Usa estas técnicas de forma legal, sobre objetivos que estés autorizado a investigar, y nunca para acosar o vigilar a particulares.