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: joao.menighin@gmail.com
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 errosA 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
