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.
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: quitNo 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 infoEl 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?.
| Flag | Qué hace | Notas |
|---|---|---|
-c, --command | Ejecuta un solo comando y sale | Suprime el logo y la línea de login; photos deja de preguntar |
-f, --file | Escribe salida .txt durante la sesión | Idéntico a escribir FILE=y |
-j, --json | Escribe salida JSON durante la sesión | Idéntico a escribir JSON=y |
-o, --output | La ayuda dice "where to store photos" | En realidad sustituye todo el directorio base de salida, para cualquier tipo de archivo |
-C, --cookies | Borra la sesión en caché antes de empezar | Mismo efecto que el comando cache: config/settings.json se reinicia a un objeto vacío |
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.
| Comando | Qué devuelve | Estado y notas |
|---|---|---|
addrs | Ubicaciones GPS etiquetadas en las publicaciones del objetivo | Casi siempre vacío. Solo cuenta las publicaciones con lat y lng, y después geocodifica cada una a la inversa mediante Nominatim |
cache | Borra 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. |
captions | Pies de foto de las publicaciones del objetivo | Su exportación a JSON escribe en el nombre de archivo equivocado; ver más abajo |
commentdata | Todos los comentarios de todas las publicaciones, con id y nombre de usuario del autor | No documentado en el README. Su exportación a JSON está mal formada |
comments | Número total de comentarios en el conjunto de publicaciones | Recorre el feed entero para contarlos |
followers | Lista de seguidores: id, nombre de usuario, nombre completo | Sin gestor de throttling: aquí un límite de peticiones sale como un traceback en crudo |
followings | Cuentas a las que sigue el objetivo | Le falta el mismo gestor que a followers |
fwersemail | Correos públicos publicados por los seguidores del objetivo | Una llamada extra a la API por seguidor. El principal imán de throttling |
fwingsemail | Correos públicos de las cuentas a las que sigue el objetivo | El mismo patrón de una llamada por usuario |
fwersnumber | Números de teléfono públicos de los seguidores del objetivo | Mismo patrón, más un nombre de archivo JSON mal escrito |
fwingsnumber | Números de teléfono públicos de las cuentas a las que sigue el objetivo | Mismo patrón |
hashtags | Hashtags que usa el objetivo | Pasada por todo el feed |
info | Metadatos del perfil como etiquetas entre corchetes | Uno de los pocos comandos que se salta la comprobación de perfil privado. Ignora FILE=y: solo escribe JSON |
likes | Número total de likes en el conjunto de publicaciones | Pasada por todo el feed |
mediatype | Cuántas publicaciones son fotos y cuántas vídeos | Pasada por todo el feed |
photodes | Descripciones de texto alternativo de las fotos | Muerto. Llama al endpoint web retirado ?__a=1; la vía de HikerAPI responde Instagram has disabled this functionality. |
photos | Descarga las publicaciones como .jpg en la carpeta de salida | Pregunta cuántas quieres; con -c se lo lleva todo. Enumera el feed entero antes de aplicar el límite |
propic | Descarga la foto de perfil | También se salta la comprobación de perfil privado |
stories | Descarga como .jpg o .mp4 las historias activas en ese momento | Se ha reportado KeyError: 'media_count' cuando el payload del reel no trae esa clave |
tagged | Usuarios a los que el objetivo etiquetó en sus propias publicaciones | Sin comprobación explícita, pero lee el feed, así que sigue necesitando acceso a él |
target | Cambia a un nuevo objetivo sin reiniciar | No documentado en el README. Anida el directorio de salida en cada cambio |
wcommented | Usuarios que comentaron en las publicaciones, ordenados por número | Todo el feed más una petición de comentarios por publicación: lento en cuentas activas |
wtagged | Usuarios que etiquetaron al objetivo, ordenados por número | Lee el feed de etiquetados; su bucle de paginación cambia al feed del objetivo |
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>.mp4Donde 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=y | Archivo que escribe en realidad | Problema |
|---|---|---|
captions | <target>_followings.json | Sobrescribe el archivo que escribió followings. Error de copiar y pegar en get_captions |
photodes | <target>_descriptions.json | El nombre no coincide con el comando |
fwersnumber | <target>_fwerssnumber.json | Doble "s", y la clave dentro del objeto es followings_phone_numbers |
commentdata | <target>_comment_data.json | Escrito a mano, con una coma final y sin separadores entre objetos, así que json.load() falla al leerlo |
info | Solo <target>_info.json | No existe ningún escritor de .txt; aquí FILE=y no hace nada |
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.