Crawler para ler dados do Canal Eletrônico do Investidor
Para versão antiga do CEI que não possui captcha obrigatório (por enquanto), utilize o cei-crawler v2
Essa versão do crawler varre a Nova Area Logada do CEI. Essa área logada possui um captcha para que seja feito o login e por isso existem algumas estratégias de implementação para fazer o bypass do mesmo. Além disso, o CEI agora possui uma API. Tudo que o crawler faz basicamente é encapsular as chamadas dessas API's. Portanto, o formato dos dados vem direto do CEI, não há transformação feita por esse crawler. Sendo assim, caso haja algo estranho ou errado nos dados retornados, provavelmente é o próprio CEI que está retornando.
O cei-crawler
utiliza as seguintes dependências:
- puppeteer-core para navegar com o browser e resolver o captcha.
- axios para fazer as requisições http.
Caso o cei-crawler
tenha te ajudado e você queira fazer alguma doação pra me ajudar a mantê-lo, utilize o QR code abaixo :)
Mande também seu nome e usuário do GitHub (caso tenha) que eu coloco aqui no README. Obrigado!
PIX: [email protected]
Criei o cei-crawler
para um projeto de acompanhamento de investimentos. Caso esteja procurando algo nesse sentido, confira o Stoincs!
Basta instalar utilizando o NPM:
npm install --save cei-crawler
Crie uma instância do CeiCrawler
passando os parametros necessários e invoque o método desejado:
let ceiCrawler = new CeiCrawler('username', 'password', {/* options */});
ceiCrawler.login(); // Login é opcional, pois antes de cada método o cei-crawler irá verificar se já esta logado.
// A vantagem em realizar o login em um passo diferente é para o tratamento de erros
A nova área logada do CEI possui validação por captcha. Não há forma simples de resolver e por isso algumas estratégias de resolução são implementadas.
Essas estratégias são setadas na instanciação do crawler, com o objeto de options
. As disponíveis são:
Nessa estratégia de login, não é necessário informar usuário e senha porém deve-se informar o token
e o cache-guid
do usuário logado.
Essa estratégia é útil caso você possua algum serviço terceiro que faça o login no CEI e pegue o token pra você.
Exemplo:
const ceiCrawler = new CeiCrawler(_, _, {
loginOptions: {
strategy: 'raw-token'
},
auth: {
"cache-guid": "cache-guid do usuário logado",
token: "JWT do usuário logado"
}
});
const values = await ceiCrawler.getConsolidatedValues();
Nessa estratégia de login, o usuário será promptado para fazer o login ele mesmo em uma janela de browser que será aberta. O crawler tenta preencher usuário e senha para você de forma que o input manual é somente para resolução do Captcha. Uma vez feito o login, o crawler trata de pegar as credencias e seguir adiante chamando os métodos. Nas opções do login deve-se também ser informado um caminho do browser para que o puppeteer o controle.
Exemplo:
const ceiCrawler = new CeiCrawler('user', 'password', {
loginOptions: {
strategy: 'user-resolve',
browserPath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
}
});
const values = await ceiCrawler.getConsolidatedValues();
Retorna os investimentos consolidados num valor total e divididos em subcategorias
let consolidated = await ceiCrawler.getConsolidatedValues();
Resultado:
{
"total": 10000,
"subTotais": [
{
"categoriaProduto": "Renda Variável",
"totalPosicao": 5000,
"percentual": 0.5
},
{
"categoriaProduto": "Tesouro Direto",
"totalPosicao": 5000,
"percentual": 0.5
}
]
}
Retorna as posições da tela "Posição" em todas as categorias de investimentos.
Parâmetro | Tipo | Default | Descrição |
---|---|---|---|
date | Dte | null | Data da posição. Caso seja passado null ou nenhum valor, será usada a ultima data de processamento do CEI. |
page | Number | 1 | Paginação dos dados. Por default retorna a primeira página. |
let position = await ceiCrawler.getPosition();
Resultado:
{
"paginaAtual": 1,
"totalPaginas": 1,
"itens": [
{
"categoriaProduto": "RendaVariavel",
"tipoProduto": "Acao",
"descricaoTipoProduto": "Ações",
"posicoes": [
{
"id": "gfw2455-8a79-4127-990b-587sa37",
"temBloqueio": false,
"instituicao": "INTER DISTRIBUIDORA DE TITULOS E VALORES MOBILIARIOS LTDA",
"quantidade": 100,
"valorAtualizado": 2377.00,
"precoFechamento": 23.77,
"produto": "BIDI4 - BANCO INTER S.A.",
"tipo": "PN",
"marcacoes": [],
"codigoNegociacao": "BIDI4",
"documentoInstituicao": "358743882",
"existeLogotipo": false,
"disponivel": 100,
"documento": "48377283492",
"razaoSocial": "BANCO INTER S.A.",
"codigoIsin": "BRBRHEU2",
"distribuicao": "114",
"escriturador": "BANCO BRADESCO S/A",
"valorBruto": 0
}
],
"totalPosicao": 2377.00,
"totalItemsPagina": 1
},
{
"categoriaProduto": "TesouroDireto",
"tipoProduto": "TesouroDireto",
"descricaoTipoProduto": "Tesouro Direto",
"posicoes": [
{
"id": "hfd4564-e70a-4596-93fd-987654dvbhw",
"temBloqueio": false,
"instituicao": "XP INVESTIMENTOS CCTVM S/A",
"quantidade": 1.01,
"valorAtualizado": 2200,
"vencimento": "2024-08-15T00:00:00",
"valorAplicado": 2000,
"produto": "Tesouro IPCA+ 2024",
"marcacoes": [],
"documentoInstituicao": "8573938583",
"existeLogotipo": false,
"indexador": "IPCA",
"disponivel": 1.01,
"documento": "7658493485",
"codigoIsin": "VRSIYASU@",
"valorBruto": 2038,
"nomeTituloPublico": "Tesouro IPCA+ 2024",
"valorLiquido": 29882,
"percRentabilidadeContratada": 4.71
}
],
"totalPosicao": 22000,
"totalItemsPagina": 5
}
],
"detalheStatusCode": 0,
"excecoes": []
}
Retorna o detalhe de uma posição da lista anterior.
Parâmetro | Tipo | Default | Descrição |
---|---|---|---|
id | String | undefined | UUID da posição. Foi observado que o UUID de uma mesma posição pode mudar ao longo do tempo e essa requisição falhar após pegar a lista com o getPosition() |
category | String | undefined | Categoria da posição informada no método getPosition() . |
type | String | undefined | Tipo da posição informada no método getPosition() . |
let positionDetail = await ceiCrawler.getPositionDetail('gfw2455-8a79-4127-990b-587sa37', 'RendaVariavel', 'Acao');
Resultado:
{
"codigoIsin": "BRBIDIACNPR0",
"distribuicao": "114",
"empresa": "BANCO INTER S.A.",
"escriturador": "BANCO BRADESCO S/A",
"codigoNegociacao": "BIDI4",
"disponivel": 100,
"indisponivel": 0,
"quantidade": 100,
"marcacoes": [],
"possuiMarcacoes": false,
"existeLogotipo": false,
"documentoInstituicao": "358743882"
}
Retorna as movimentações da aba "Movimentação" no CEI.
Parâmetro | Tipo | Default | Descrição |
---|---|---|---|
startDate | Date | null | Data de inicio para trazer as movimentações. Caso null , será utilizada a ultima data de processamento do CEI menos 1 mês. |
endDate | Date | null | Data fim para trazer as movimentações. Caso null , será utilizada a ultima data de processamento do CEI. |
page | Number | 1 | Paginação dos dados. Por default retorna a primeira página. |
let accountStatement = await ceiCrawler.getAccountStatement();
Resultado:
{
"paginaAtual": 1,
"totalPaginas": 2,
"itens": [
{
"data": "2021-08-02T00:00:00",
"movimentacoes": [
{
"tipoOperacao": "Credito",
"tipoMovimentacao": "Juros Sobre Capital Próprio",
"nomeProduto": "BIDI4 - BANCO INTER S.A.",
"instituicao": "INTER DISTRIBUIDORA DE TITULOS E VALORES MOBILIARIOS LTDA",
"quantidade": 100,
"valorOperacao": 1.49,
"precoUnitario": 0.01
}
],
"totalItemsPagina": 1
},
{
"data": "2021-07-30T00:00:00",
"movimentacoes": [
{
"tipoOperacao": "Debito",
"tipoMovimentacao": "Transferência",
"nomeProduto": "ALZR11 - ALIANZA TRUST RENDA IMOBILIARIA FDO INV IMOB",
"instituicao": "RICO INVESTIMENTOS - GRUPO XP",
"quantidade": 5
},
{
"tipoOperacao": "Credito",
"tipoMovimentacao": "Transferência",
"nomeProduto": "ALZR11 - ALIANZA TRUST RENDA IMOBILIARIA FDO INV IMOB",
"instituicao": "XP INVESTIMENTOS CCTVM S/A",
"quantidade": 5
}
],
"totalItemsPagina": 2
}
],
"detalheStatusCode": 0,
"excecoes": []
}
Retorna os IPOs da tela "Ofertas Públicas" no CEI.
Parâmetro | Tipo | Default | Descrição |
---|---|---|---|
date | Date | null | Data de consulta. Caso seja passado null ou nenhum valor, será usada a ultima data de processamento do CEI. |
page | Number | 1 | Paginação dos dados. Por default retorna a primeira página. |
let ipos = await ceiCrawler.getIPOs();
Resultado:
{
"paginaAtual": 1,
"totalPaginas": 1,
"itens": [
{
"data": "2021-07-27T00:00:00",
"ofertasPublicas": [
{
"id": "c80c8b8f-62d2-4b48-b242-b0f310cfa95a",
"dataLiquidacao": "2021-07-27T00:00:00",
"nomeEmpresa": "INVESTO ETF MSCI US TECHNOLOGY FDO INV IND INV EXT",
"tipoOferta": "OUTRO",
"oferta": "ETF INVESTO",
"nomeInstituicao": "INTER DISTRIBUIDORA DE TITULOS E VALORES MOBILIARIOS LTDA",
"quantidade": 10,
"preco": 10,
"valor": 100
}
],
"totalItemsPagina": 1
}
],
"detalheStatusCode": 0,
"excecoes": []
}
Retorna o detalhe de uma posição da lista anterior.
Parâmetro | Tipo | Default | Descrição |
---|---|---|---|
id | String | undefined | UUID do IPO. Foi observado que o UUID de um mesmo IPO pode mudar ao longo do tempo e essa requisição falhar após pegar a lista com o getIPOs() |
let ipoDetail = await ceiCrawler.getIPODetail('c80c8b8f-62d2-4b48-b242-b0f310cfa95a');
Resultado:
{
"nomeProduto": "OUTRO INVESTO ETF MSCI US TECHNOLOGY FDO INV IND INV EXT",
"nomeInstituicao": "INTER DISTRIBUIDORA DE TITULOS E VALORES MOBILIARIOS LTDA",
"ativo": {
"nomeEmpresa": "INVESTO ETF MSCI US TECHNOLOGY FDO INV IND INV EXT",
"ticker": "USTK11L",
"oferta": "ETF INVESTO",
"codigoIsin": "BRUSTKCTF007"
},
"valores": {
"preco": 10,
"precoMaximo": 0,
"valor": 100
},
"reserva": {
"modalidade": "Compra/Integralização de cotas do ETF INVESTO",
"quantidade": 10,
"valor": 0
},
"quantidadeAlocada": 10,
"dataLiquidacao": "2021-07-27T00:00:00"
}
Retorna os dados da aba "Negociação" no CEI.
Parâmetro | Tipo | Default | Descrição |
---|---|---|---|
startDate | Date | null | Data de inicio para trazer as negociações. Caso null , será utilizada a ultima data de processamento do CEI menos 1 mês. |
endDate | Date | null | Data fim para trazer as negociações. Caso null , será utilizada a ultima data de processamento do CEI. |
page | Number | 1 | Paginação dos dados. Por default retorna a primeira página. |
let stockTransactions = await ceiCrawler.getStockTransactions();
Resultado:
{
"paginaAtual": 1,
"totalPaginas": 1,
"itens": [
{
"data": "2021-07-20T00:00:00",
"totalCompra": 1000,
"totalVenda": 0,
"negociacaoAtivos": [
{
"tipoMovimentacao": "Compra",
"mercado": "Mercado à Vista",
"nomeInstituicao": "RICO INVESTIMENTOS - GRUPO XP",
"codigoNegociacao": "PNVL3",
"quantidade": 100,
"preco": 10.00,
"valor": 1000.00
}
],
"totalItemsPagina": 1
}
],
"detalheStatusCode": 0,
"excecoes": []
}
Retorna os eventos da tela "Eventos Provisionados" no CEI.
Parâmetro | Tipo | Default | Descrição |
---|---|---|---|
date | Date | null | Data de consulta. Caso seja passado null ou nenhum valor, será usada a ultima data de processamento do CEI. |
page | Number | 1 | Paginação dos dados. Por default retorna a primeira página. |
let events = await ceiCrawler.getProvisionedEvents();
Resultado:
{
"totalValorLiquido": 8.62,
"paginaAtual": 1,
"totalPaginas": 1,
"itens": [
{
"id": "9cc87804-f9ae-143a-acfb-c953f38c72dd",
"produto": "WEGE3 - WEG S/A",
"tipo": "ON",
"tipoEvento": "JUROS SOBRE CAPITAL PRÓPRIO",
"previsaoPagamento": "2021-08-11T00:00:00",
"instituicao": "INTER DISTRIBUIDORA DE TITULOS E VALORES MOBILIARIOS LTDA",
"quantidade": 300,
"precoUnitario": 0.03,
"valorLiquido": 8.62,
"totalItemsPagina": 1
}
],
"detalheStatusCode": 0,
"excecoes": []
}
Retorna o detalhe de um evento provisionado da lista anterior.
Parâmetro | Tipo | Default | Descrição |
---|---|---|---|
id | String | undefined | UUID do evento. Foi observado que o UUID de um mesmo evento pode mudar ao longo do tempo e essa requisição falhar após pegar a lista com o getProvisionedEvents() |
let eventDetail = await ceiCrawler.getProvisionedEventDetail('9cc87804-f9ae-143a-acfb-c953f38c72dd');
Resultado:
{
"codigoNegociacao": "WEGE3",
"codigoIsin": "BRWEGEACNOR0",
"distribuicao": "202",
"escriturador": "BANCO BRADESCO S/A",
"empresa": "WEG S/A",
"dataAprovacao": "2021-03-23T00:00:00",
"dataAtualizacao": "2021-03-30T00:00:00",
"dataEx": "2021-03-29T00:00:00",
"impostoRenda": 15,
"valorImpostoRenda": 1.52,
"valorBruto": 10.14,
"disponivel": 100,
"indisponivel": 0,
"produto": "WEGE3 - WEG S/A",
"tipo": "ON",
"tipoEvento": "JUROS SOBRE CAPITAL PRÓPRIO",
"previsaoPagamento": "2021-08-11T00:00:00",
"quantidade": 100,
"precoUnitario": 0.03,
"valorLiquido": 8.62
}
Na criação de um CeiCrawler
é possivel especificar alguns valores para o parâmetro options
que modificam a forma que o crawler funciona. As opções são:
Propriedade | Tipo | Default | Descrição |
---|---|---|---|
debug | Boolean | false | Se true , printa mensages de debug no log. |
loginOptions.strategy | String | user-resolve |
Estratégia utilizada no login. Veja Login & Captcha para mais informações. |
loginOptions.browserPath | String | undefined |
Caminho para o executavél do browser que será controlado para resolucao do Captcha. Veja Login & Captcha para mais informações. |
auth.token | String | undefined |
Token JWT do usuário logado no CEI. Utilizado quando a estratégia de login é raw-token |
auth.cache-guid | String | undefined |
UUID da sessão do usuário logado no CEI. Utilizado quando a estratégia de login é raw-token |
Exemplo:
const ceiCrawlerOptions = {
debug: true,
loginOptions: {
strategy: 'user-resolve',
browserPath: 'path/to/browser.exe'
}
};
let ceiCrawler = new CeiCrawler('username', 'password', ceiCrawlerOptions);
O CEI Crawler possui um exceção própria, CeiCrawlerError
, que é lançada em alguns cenários. Essa exceção possui um atributo type
para te direcionar no tratamento:
type | Descrição |
---|---|
UNAUTHORIZED | Lançada quando uma request retorna 401. Isso pode significar que o token utiliza é inválido ou expirou. |
BAD_REQUEST | Lançada quando uma requisição falha por má formação. Pode ser um parâmetro errado, uma data menor que o limite minimo, etc. |
TOO_MANY_REQUESTS | O CEI faz throttling de requisições. Se ao usar o crawler você fizer muitas requisições rapidamente esse erro pode ser retornado |
INVALID_LOGIN_STRATEGY | Lançada quando informada uma estratégia de login inválida. |
Exemplo de como fazer um bom tratamento de erros:
const CeiCrawler = require('cei-crawler');
const { CeiErrorTypes } = require('cei-crawler')
const ceiCrawler = new CeiCrawler('usuario', 'senha', { navigationTimeout: 20000 });
try {
const positions = ceiCrawler.getPositions();
} catch (err) {
if (err.name === 'CeiCrawlerError') {
if (err.type === CeiErrorTypes.UNAUTHORIZED)
// Handle unauthrozied
else if (err.type === CeiErrorTypes.TOO_MANY_REQUESTS)
// Handle too many requests
// else ...
} else {
// Handle generic errors
}
}
MIT