Como usar o Osintgram: todos os comandos explicados
O Osintgram é um shell interativo preso a um único nome de usuário alvo: você o inicia com python3 main.py <target> e digita um comando por vez em um prompt "Run a command:". O comando list interno imprime 23 comandos, três a mais do que o README documenta, e todos eles estão na tabela abaixo.
O Osintgram não é um scanner que você aponta para um nome de usuário e deixa rodando sozinho. O main.py recebe um único argumento posicional obrigatório, monta um cliente de API, imprime um banner e depois fica em loop em um prompt até você digitar quit. Todo comando se aplica a esse único alvo, e nada acontece enquanto você não digitar algo.
Inicie com python3 main.py <target> e digite os comandos no prompt Run a command: . O list interno mostra 23 comandos; o README documenta apenas 20. A gravação em arquivo vem desativada por padrão: digite FILE=y ou JSON=y antes, ou inicie com -f / -j. Os quatro comandos de coleta de contatos (fwersemail, fwingsemail, fwersnumber, fwingsnumber) fazem uma chamada de API por seguidor, sem nenhum backoff, e são justamente os que fazem você ser bloqueado.
Como funciona o shell
Este guia parte de um clone funcionando e de um config/credentials.ini preenchido; se não for o seu caso, comece por como instalar o Osintgram. No Kali vale o mesmo, com algumas armadilhas específicas da distro.
Sempre execute a partir da raiz do repositório
O src/config.py lê o config/credentials.ini como caminho relativo, então python3 ~/Osintgram/main.py target a partir do seu diretório home simplesmente não encontra credencial nenhuma. Entre no clone com cd antes. Rodar de outro lugar também é a causa documentada da issue #105, ModuleNotFoundError: No module named 'src.Osintgram'.
Na inicialização, o backend clássico imprime o banner do alvo: Logged as <you>. Target: <target> [<numeric id>], mais [PRIVATE PROFILE] quando a conta é privada e [FOLLOWING] ou [NOT FOLLOWING]. Quem imprime isso é o construtor, e é por isso que o banner aparece antes do logo em ASCII. O banner do HikerAPI é mais curto: sem linha de login, sem estado de follow. Depois vem o prompt: a string literal Run a command: , em amarelo.
O autocompletar com TAB é ligado ao dicionário de comandos pelo gnureadline no Linux e no macOS, ou pelo pyreadline no Windows, então nomes parciais são completados. Qualquer coisa que o dicionário não reconheça imprime Unknown command em vermelho; uma linha vazia apenas imprime uma linha em branco. quit e exit imprimem Goodbye! e encerram, e o Ctrl-C é capturado para fazer a mesma coisa.
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: quitVocê não fica preso ao nome de usuário com que iniciou. O comando target pergunta Insert new target username: , resolve a nova conta e reimprime o banner, sem novo login. Ele tem um defeito: o setTarget() acrescenta o nome do alvo ao diretório de saída toda vez que roda, então, depois de uma troca, seus arquivos vão parar em output/<first target>/<second target>/ em vez de uma pasta irmã.
Três formas de iniciar a ferramenta
O README documenta três modos de execução, e eles diferem em mais coisas do que só a sintaxe.
# 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 infoO modo de comando único não é apenas o shell com uma rodada já feita por você. Quando -c está definido, o main.py pula o printlogo() e o construtor suprime a linha Attempt to login..., então a saída fica bem mais silenciosa, e o photos para de perguntar quantas fotos baixar e leva tudo. O loop também é interrompido depois de exatamente uma iteração, então -c FILE=y alterna a flag e sai sem executar nada; use -f e -j no lugar.
O terceiro modo é o que mais importa em 2026. Se o config.getHikerToken() retornar qualquer coisa (vinda do campo hikerapi_token do credentials.ini ou da variável de ambiente HIKERAPI_TOKEN), o main.py instancia a HikerCLI em vez da classe clássica Osintgram, e nenhum login no Instagram acontece. A linha de inicialização passa a ser Connect to HikerAPI.... Encare esse caminho como o que ainda foi projetado para funcionar, e não como uma garantia: a issue #2664 (2026-06-21) relata que esse branch lida mal com formatos alternativos de resposta de usuário, e o HikerAPI é um terceiro pago a quem você entrega seus alvos. Se o caminho clássico de usuário e senha ainda autentica é outra questão, tratada em o Osintgram ainda funciona.
| Flag | O que faz | Observações |
|---|---|---|
-c, --command | Executa um comando e sai | Suprime o logo e a linha de login; o photos para de perguntar |
-f, --file | Grava a saída em .txt durante a sessão | Idêntico a digitar FILE=y |
-j, --json | Grava a saída em JSON durante a sessão | Idêntico a digitar JSON=y |
-o, --output | O texto de ajuda diz "where to store photos" | Na prática substitui todo o diretório-base de saída, para qualquer tipo de arquivo |
-C, --cookies | Limpa a sessão em cache antes de iniciar | Mesmo efeito do comando cache: o config/settings.json é redefinido para um objeto vazio |
Todos os comandos do Osintgram
O README lista 20 comandos. A função cmdlist(), que o list interno chama, imprime 23. Os três a mais são cache, commentdata e target, e nenhum deles aparece no bloco de comandos do README. O doc/COMMANDS.md está ainda mais atrasado: o bloco de cabeçalho dele deixa de fora fwersnumber e fwingsnumber (os dois têm seções mais abaixo) e não documenta cache, commentdata nem target em lugar nenhum. Se você quer a lista definitiva, digite list no shell ou leia o dicionário commands no main.py.
| Comando | O que retorna | Status e observações |
|---|---|---|
addrs | Localizações GPS marcadas nos posts do alvo | Quase sempre vem vazio. Conta apenas posts com lat e lng e depois faz geocodificação reversa de cada um pelo Nominatim |
cache | Apaga o arquivo de sessão em cache | Só local, sem chamada de rede. Imprime Cache Cleared. ou Settings.json don't exist.; no HikerAPI ele apenas diz Cache is already empty. |
captions | Legendas dos posts do alvo | A exportação em JSON grava no nome de arquivo errado (veja abaixo) |
commentdata | Todos os comentários de todos os posts, com id e nome de usuário do autor | Não documentado no README. A exportação em JSON sai malformada |
comments | Número total de comentários em todos os posts | Percorre o feed inteiro para contar |
followers | Lista de seguidores: id, nome de usuário, nome completo | Sem tratamento de throttle: um limite de taxa aqui aparece como traceback cru |
followings | Contas que o alvo segue | A mesma ausência de tratamento do followers |
fwersemail | E-mails públicos publicados pelos seguidores do alvo | Uma chamada de API extra por seguidor. O grande ímã de throttle |
fwingsemail | E-mails públicos das contas que o alvo segue | O mesmo formato de uma chamada por usuário |
fwersnumber | Telefones públicos dos seguidores do alvo | O mesmo formato, mais um nome de arquivo JSON escrito errado |
fwingsnumber | Telefones públicos das contas que o alvo segue | O mesmo formato |
hashtags | Hashtags que o alvo usa | Passa pelo feed inteiro |
info | Metadados do perfil como rótulos entre colchetes | Um dos poucos comandos que passa por cima da trava de perfil privado. Ignora o FILE=y: só grava JSON |
likes | Número total de curtidas em todos os posts | Passa pelo feed inteiro |
mediatype | Quantos posts são fotos e quantos são vídeos | Passa pelo feed inteiro |
photodes | Descrições em texto alternativo das fotos | Morto. Chama o endpoint web ?__a=1, já aposentado; o caminho do HikerAPI responde Instagram has disabled this functionality. |
photos | Baixa os posts como .jpg na pasta de saída | Pergunta a quantidade; com -c leva tudo. Enumera o feed inteiro antes de aplicar o limite |
propic | Baixa a foto de perfil | Também passa por cima da trava de perfil privado |
stories | Baixa os stories ativos no momento como .jpg ou .mp4 | KeyError: 'media_count' é relatado quando o payload do reel não traz a chave |
tagged | Usuários que o alvo marcou nos próprios posts | Sem trava explícita, mas ele lê o feed, então ainda precisa de acesso a ele |
target | Troca para um novo alvo sem reiniciar | Não documentado no README. Aninha o diretório de saída a cada troca |
wcommented | Usuários que comentaram nos posts, ordenados por quantidade | Feed inteiro mais uma busca de comentários por post, lento em contas ativas |
wtagged | Usuários que marcaram o alvo, ordenados por quantidade | Lê o feed de marcações; o loop de paginação dele passa para o feed do alvo |
Outras quatro entradas do dicionário não são comandos de dados: list e help imprimem essa mesma lista, quit e exit encerram. Duas entradas são tratadas completamente fora do dicionário (FILE=y/n e JSON=y/n), e é por isso que o TAB nunca as completa.
Uma ressalva vale para a tabela inteira: nada retorna dados se o backend não conseguir autenticar. Toda linha pressupõe que você passou do login, seja com uma sessão que o cliente clássico aceite, seja com um token do HikerAPI. Em 2026, não são os comandos que quebram primeiro.
Lendo a saída do info
O info é o comando que você vai rodar primeiro e o que tem mais chance de te enganar. Ele chama o endpoint privado users/{user_id}/full_detail_info/, lê content['user_detail']['user'] e imprime uma sequência fixa de rótulos entre colchetes:
[ID],[FULL NAME],[BIOGRAPHY]: sempre impressos[FOLLOWED],[FOLLOW]: sempre impressos, e não são o que parecem[BUSINESS ACCOUNT]e, na sequência,[BUSINESS CATEGORY]apenas quando a conta é comercial e não escondeu a categoria[VERIFIED ACCOUNT]e, na sequência,[EMAIL]apenas quando há um e-mail público definido[HD PROFILE PIC]: sempre impresso, como URL[FB PAGE],[WHATSAPP NUMBER],[CITY],[ADDRESS STREET],[CONTACT PHONE NUMBER]: cada um impresso apenas quando o campo está preenchido
A armadilha do [FOLLOWED] / [FOLLOW]
[FOLLOWED] é a contagem de seguidores e [FOLLOW] é a contagem de seguindo. Os rótulos parecem dizer justamente o contrário do que significam. A exportação em JSON resolve a dúvida: os mesmos dois valores são gravados nas chaves edge_followed_by e edge_follow, os nomes antigos da web API para seguidores e seguindo. Se for transcrever os números, tire-os da exportação em JSON.
O backend do HikerAPI imprime uma linha [MEDIA] a mais, com a contagem de posts que o caminho clássico nunca mostra, então o mesmo comando devolve uma lista de campos diferente em cada backend. E, quando a chamada por baixo lança uma exceção, o handler imprime Oops... <target> non exist, please enter a valid username. e sai com o código 2. É uma mensagem em que você não deveria confiar. A issue #1020 mostra o Instagram devolvendo um corpo de erro que não é JSON e a ferramenta relatando isso como usuário inexistente.
Salvando a saída e os nomes de arquivo que colidem
Nada é gravado em disco a menos que você peça. Digite FILE=y para saída em texto e JSON=y para JSON em qualquer momento da sessão; a ferramenta confirma com Write to file: enabled e Export to JSON: enabled, e FILE=n / JSON=n desligam tudo de novo. Iniciar com -f ou -j define os mesmos booleanos antes do primeiro comando.
No master atual, os resultados vão para um subdiretório por alvo. Na tag de release 1.3 eles caíam em um output/ plano, e é por isso que guias mais antigos mostram outro layout. O arquivo .txt guarda a tabela ASCII crua do PrettyTable (file.write(str(t))), e não CSV, então não espere abri-lo em uma planilha.
output/
|-- dont_delete_this_folder.txt
`-- <target>/
|-- <target>_followers.txt
|-- <target>_followers.json
|-- <target>_propic.jpg
|-- <target>_<photo id>.jpg
`-- <target>_<story id>.mp4É na nomenclatura que a coisa fica bagunçada. Três comandos gravam JSON com um nome de arquivo que não corresponde ao comando digitado, um deles sobrescreve um resultado anterior e outros dois quebram o padrão cada um do seu jeito.
| Comando com JSON=y | Arquivo que ele realmente grava | Problema |
|---|---|---|
captions | <target>_followings.json | Sobrescreve o arquivo que o followings gravou. Bug de copiar e colar em get_captions |
photodes | <target>_descriptions.json | O nome não corresponde ao comando |
fwersnumber | <target>_fwerssnumber.json | Dois "s", e a chave dentro do objeto é followings_phone_numbers |
commentdata | <target>_comment_data.json | Escrito à mão, com vírgula sobrando e sem separadores entre os objetos, então o json.load() falha nele |
info | Apenas <target>_info.json | Não existe nenhum writer de .txt; o FILE=y não faz nada aqui |
Não rode captions depois de followings na mesma sessão
Com JSON=y ativado, o captions grava em <target>_followings.json. Se você exportou a lista de seguindo antes, rodar o captions substitui tudo por dados de legenda, sem confirmação, sem backup e sem aviso. Exporte as legendas em outra sessão ou renomeie o dump de followings antes.
Os comandos que fazem você levar throttle
Os quatro comandos de coleta de contatos compartilham a mesma arquitetura. O get_fwersemail() primeiro pagina a lista inteira de seguidores, imprimindo um contador Catched N followers email que vai subindo. Só depois disso ele pergunta Do you want to get all emails? y/n: . Em seguida, para cada seguidor coletado, dispara uma chamada user_info separada e só guarda o registro se houver um e-mail público definido.
Daí decorrem duas consequências. Seu limite é aplicado depois da enumeração completa, então responder "n" e pedir 200 e-mails ainda percorre a lista de seguidores inteira antes. E o limite conta apenas as correspondências, então um alvo cujos seguidores raramente publicam e-mail acaba enumerado quase do começo ao fim de qualquer jeito. Não há sleep, jitter nem backoff nesses loops; a issue #657 é um patch da comunidade que adiciona um.
Pedir menos resultados não deixa a operação mais barata
A lista de seguidores é enumerada por completo antes de o prompt aparecer, e depois é disparada uma chamada de perfil por seguidor. No backend clássico, isso é volume de requisições que o Instagram contabiliza contra você; no HikerAPI, o mesmo loop chama user_by_id_v2 uma vez por usuário, então é volume de requisições que você paga. Nenhum dos dois backends te dá uma amostra barata de uma lista grande de seguidores.
A cara de um bloqueio depende do comando. Os quatro comandos de contato capturam ClientThrottledError e imprimem Error: Instagram blocked the requests. Please wait a few minutes before you try again. Já followers e followings não têm esse tratamento, então a mesma condição escapa como um traceback cru terminando em urllib.error.HTTPError: HTTP Error 429: Too Many Requests, o formato relatado na issue #394.
O handler também não salva os seus dados. Na issue #366, um usuário chegou a Catched 41643 followers email antes do bloqueio, e o caminho de recuperação então quebrou com TypeError: string indices must be integers, descartando todos os registros coletados. A issue #342 relata um corte por volta de 35.000 seguidores em um alvo com seis dígitos. Esses são os únicos números concretos que existem: nenhum limite confiável em tempo de relógio foi publicado, e qualquer guia que te dê um valor em minutos está chutando.
photos, comments, likes, mediatype, hashtags, wcommented e commentdata paginam o feed inteiro, e os dois últimos ainda buscam os comentários de cada post por cima disso. Eles raramente disparam um bloqueio, mas em uma conta com milhares de posts são bem mais lentos do que parecem.
Comandos que não retornam nada
Um Sorry! No results found :-( em vermelho não é um erro. Quer dizer que a requisição deu certo e que o alvo não tem dados daquele tipo. A maioria dos comandos pode emitir essa mensagem.
O addrs é o suspeito de sempre. Ele só conta os posts em que o objeto de localização traz latitude e longitude, e posts geolocalizados ficaram raros desde que o Instagram aposentou o mapa de fotos. O pouco que existe passa por geocodificação reversa no Nominatim, que por sua vez também tem limite de taxa.
O photodes é um caso diferente: ele não está vazio, está morto. Ele ainda busca https://www.instagram.com/<target>/?__a=1 e tenta acessar graphql.user.edge_owner_to_timeline_media, um endpoint sem autenticação que não devolve mais esse JSON. No backend do HikerAPI, a função foi reduzida a uma única linha que imprime Instagram has disabled this functionality. Trate-o como removido.
O stories não retorna nada quando não há nada no ar, o que é normal, mas ele também tem um parser frágil: a issue #1258 mostra o comando abortando com KeyError: 'media_count' quando o payload do reel não inclui essa chave.
Alvos privados, em resumo
Se o banner mostra [PRIVATE PROFILE] junto de [NOT FOLLOWING], quase todo comando da tabela para antes de fazer qualquer requisição. Uma única trava verifica se a conta é privada e não seguida, imprime Impossible to execute command: user has private profile e oferece Do you want send a follow request? [Y/N]: . O backend do HikerAPI é ainda mais seco: só de ser privada já bloqueia, sem prompt de solicitação de follow. Nenhuma flag muda isso. O quadro completo (quais comandos sobrevivem e o que o Instagram ainda expõe publicamente) está em o Osintgram funciona em contas privadas.
Fazendo o mesmo trabalho sem a CLI
O conjunto de comandos é realmente amplo. O que torna o Osintgram incômodo é tudo o que existe em volta: um alvo por sessão, um arquivo de credenciais, throttling sem backoff, nomes de arquivo que se sobrescrevem e um caminho de login que falha para muita gente antes de qualquer uma dessas coisas importar. Se você usa a CLI para pesquisa de verdade, mantenha as sessões curtas e exporte um conjunto de dados por vez.
Se você só quer o relatório, uma consulta hospedada pula todo esse aparato: não há repositório para clonar, nenhuma conta do Instagram sua em risco e nenhum loop de rate limit para ficar vigiando. Ela cobre apenas perfis públicos, o mesmo teto que a CLI tem.