Entre essas destacam-se as Informações de Saúde (vide link), produtos de ações integradas do governo brasileiro nessa área. Apesar de os dados estarem públicos em em diversos formatos, a sua extração automática não é tão trivial.
Neste artigo irei explorar ferramentas poderosas do GNU/Linux, em especial o meu tripleto favorito Shell, SED e AWK, além do utilitário cURL e do SGBD PostgreSQL, na missão de extrair de forma automática alguns indicadores do DATASUS disponíveis na Internet.
Apesar de basear-se em um caso altamente específico, os indicadores do DATASUS, as ideias e trechos de instruções contidas nesse texto podem servir para automatizar a obtenção de dados provenientes de outras fontes. Então vamos começar!
Antes de qualquer coisa, precisamos preparar as estruturas de banco de dados em que serão armazenados os indicadores coletados. No PostgreSQL, crie as estruturas segundo as instruções SQL a seguir:
-- criação do esquema específico CREATE SCHEMA indicador; -- criação da tabela de entrada CREATE TABLE indicador.datasus_entrada ( cod_munic integer, vlr_dado integer ); COMMENT ON TABLE indicador.datasus_entrada IS 'Tabela de entrada para os indicadores do DATASUS para os Municípios'; -- criação da tabela final CREATE TABLE indicador.datasus_municipio ( cod_munic integer NOT NULL, num_ano_mes integer NOT NULL, num_inter_hospi_sus integer, num_consu_medic_sus integer, num_estab_urgen_sus integer, num_equip_saude_famil integer, PRIMARY KEY (cod_munic, num_ano_mes) ); COMMENT ON TABLE indicador.datasus_municipio IS 'Indicadores mensais do DATASUS para os Municípios'; COMMENT ON COLUMN indicador.datasus_municipio.cod_munic IS 'Código do Município (IBGE)'; COMMENT ON COLUMN indicador.datasus_municipio.num_ano_mes IS 'Ano e Mês do Indicador (ex: 201209 => Set/2012)'; COMMENT ON COLUMN indicador.datasus_municipio.num_inter_hospi_sus IS 'Internações Hospitalares pelo SUS'; COMMENT ON COLUMN indicador.datasus_municipio.num_consu_medic_sus IS 'Consultas Médicas pelo SUS'; COMMENT ON COLUMN indicador.datasus_municipio.num_estab_urgen_sus IS 'Estabelecimentos com Atendimento de Urgência SUS'; COMMENT ON COLUMN indicador.datasus_municipio.num_equip_saude_famil IS 'Equipes de Saúde da Família';Repare que foram definidos comentários para as tabelas e colunas em questão, uma vez que o padrão de nomenclatura que costumo adotar pode não te parecer trivial. :D
Em seguida, crie um arquivo com o nome sugestivo carregar_indice_datasus.sh e inclua as seguintes intruções em linguagem Shell Script:
#!/bin/bash if [ $# -ne 1 ] then echo "Sintaxe: $0 <indicador>" echo "Indicadores: qrbr qabr aturgbr equipebr" exit 1 fi ind="$1" # parametrizar de acordo com indicador especificado case "$ind" in "qrbr") nom="Internações Hospitalares pelo SUS" col="num_inter_hospi_sus" sis="sih" ;; "qabr") nom="Consultas Médicas pelo SUS" col="num_consu_medic_sus" sis="sia" ;; "aturgbr") nom="Estabelecimentos com Atendimento de Urgência SUS" col="num_estab_urgen_sus" sis="cnes" ;; "equipebr") nom="Equipes de Saúde da Família" col="num_equip_saude_famil" sis="cnes" ;; *) echo "Índice desconhecido: $ind" exit 2 esac url="http://tabnet.datasus.gov.br/cgi/tabcgi.exe?$sis/cnv/$ind.def" tmp="$ind.tmp" res="$ind.res" csv="$ind.csv" echo "Indicador: $nom ($ind)" # buscar último período disponível echo "Buscando último período..." curl -s $url > $tmp arq=$(awk 'BEGIN{FS="\""}/dbf" SELECTED/{print$2}' $tmp) mes=$(echo $arq | sed 's/^.*\([0-9]\{2\}\)\.dbf/\1/') ano=$(echo $arq | sed 's/^.*\([0-9]\{2\}\)[0-9]\{2\}\.dbf/20\1/') rm -f $tmp echo "Último período disponível: $mes/$ano (arquivo $arq)" # definir parâmetros da requisição case "$ind" in "qrbr") par="Linha=Munic%EDpio&Coluna=--N%E3o-Ativa--&Incremento=Interna%E7%F5es&Arquivos=$arq&SMicrorregi%E3o=TODAS_AS_CATEGORIAS__&SReg.Metropolitana=TODAS_AS_CATEGORIAS__&SAglomerado_urbano=TODAS_AS_CATEGORIAS__&SCapital=TODAS_AS_CATEGORIAS__&SUnidade_Federa%E7%E3o=TODAS_AS_CATEGORIAS__&SProcedimento=TODAS_AS_CATEGORIAS__&SGrupo_procedimento=TODAS_AS_CATEGORIAS__&SSubgrupo_proced.=TODAS_AS_CATEGORIAS__&SForma_organiza%E7%E3o=TODAS_AS_CATEGORIAS__&SComplexidade=TODAS_AS_CATEGORIAS__&SFinanciamento=TODAS_AS_CATEGORIAS__&SRubrica_FAEC=TODAS_AS_CATEGORIAS__&SRegra_contratual=TODAS_AS_CATEGORIAS__&SNatureza=TODAS_AS_CATEGORIAS__&SRegime=TODAS_AS_CATEGORIAS__&SNatureza_jur%EDdica=TODAS_AS_CATEGORIAS__&SEsfera_jur%EDd%EDca=TODAS_AS_CATEGORIAS__&SGest%E3o=TODAS_AS_CATEGORIAS__&zeradas=exibirlz&formato=prn&mostre=Mostra" ;; "qabr") par="Linha=Munic%EDpio&Coluna=--N%E3o-Ativa--&Incremento=Qtd.apresentada&Arquivos=$arq&SMicrorregi%E3o=TODAS_AS_CATEGORIAS__&SReg.Metropolitana=TODAS_AS_CATEGORIAS__&SAglomerado_urbano=TODAS_AS_CATEGORIAS__&SCapital=TODAS_AS_CATEGORIAS__&SUnidade_Federa%E7%E3o=TODAS_AS_CATEGORIAS__&SProcedimento=1075&SProcedimento=1076&SProcedimento=1077&SProcedimento=1078&SProcedimento=1079&SProcedimento=1080&SProcedimento=1081&SProcedimento=1082&SProcedimento=1083&SProcedimento=1084&SProcedimento=1085&SProcedimento=1086&SProcedimento=1087&SProcedimento=1088&SProcedimento=1089&SProcedimento=1090&SProcedimento=1091&SProcedimento=1092&SProcedimento=1093&SProcedimento=1094&SProcedimento=1114&SProcedimento=1115&SProcedimento=1117&SProcedimento=1125&SProcedimento=1126&SProcedimento=1127&SProcedimento=1128&SProcedimento=1129&SProcedimento=1130&SProcedimento=1131&SProcedimento=1132&SProcedimento=1133&SProcedimento=1134&SProcedimento=1135&SProcedimento=1136&SProcedimento=1137&SProcedimento=1138&SProcedimento=1139&SProcedimento=1140&SProcedimento=1141&SProcedimento=1142&SProcedimento=1143&SProcedimento=1144&SProcedimento=1145&SProcedimento=1146&SProcedimento=1147&SProcedimento=1148&SProcedimento=1149&SProcedimento=1150&SProcedimento=1169&SProcedimento=1170&SProcedimento=1189&SProcedimento=1190&SProcedimento=1191&SProcedimento=1192&SProcedimento=1193&SProcedimento=1194&SProcedimento=1195&SProcedimento=1196&SProcedimento=1197&SGrupo_procedimento=TODAS_AS_CATEGORIAS__&SSubgrupo_proced.=TODAS_AS_CATEGORIAS__&SForma_organiza%E7%E3o=TODAS_AS_CATEGORIAS__&SComplexidade=TODAS_AS_CATEGORIAS__&SFinanciamento=TODAS_AS_CATEGORIAS__&SSubtp_Financiament=TODAS_AS_CATEGORIAS__&SRegra_contratual=TODAS_AS_CATEGORIAS__&SCar%E1ter_Atendiment=TODAS_AS_CATEGORIAS__&SGest%E3o=TODAS_AS_CATEGORIAS__&SDocumento_registro=TODAS_AS_CATEGORIAS__&SEsfera_administrat=TODAS_AS_CATEGORIAS__&STipo_de_prestador=TODAS_AS_CATEGORIAS__&SAprova%E7%E3o_produ%E7%E3o=TODAS_AS_CATEGORIAS__&zeradas=exibirlz&formato=prn&mostre=Mostra" ;; "aturgbr") par="Linha=Munic%EDpio&Coluna=--N%E3o-Ativa--&Incremento=SUS&Arquivos=$arq&SRegi%E3o=TODAS_AS_CATEGORIAS__&SUnidade_Federa%E7%E3o=TODAS_AS_CATEGORIAS__&SCapital=TODAS_AS_CATEGORIAS__&SMicrorregi%E3o=TODAS_AS_CATEGORIAS__&SReg.Metropolitana=TODAS_AS_CATEGORIAS__&SAglomerado_urbano=TODAS_AS_CATEGORIAS__&SEnsino%2FPesquisa=TODAS_AS_CATEGORIAS__&SEsfera_Administrativa=TODAS_AS_CATEGORIAS__&SNatureza=TODAS_AS_CATEGORIAS__&STipo_de_Estabelecimento=TODAS_AS_CATEGORIAS__&STipo_de_Gest%E3o=TODAS_AS_CATEGORIAS__&STipo_de_Prestador=TODAS_AS_CATEGORIAS__&zeradas=exibirlz&formato=prn&mostre=Mostra" ;; "equipebr") par="Linha=Munic%EDpio&Coluna=--N%E3o-Ativa--&Incremento=Quantidade&Arquivos=$arq&SRegi%E3o=TODAS_AS_CATEGORIAS__&SUnidade_Federa%E7%E3o=TODAS_AS_CATEGORIAS__&SCapital=TODAS_AS_CATEGORIAS__&SMicrorregi%E3o=TODAS_AS_CATEGORIAS__&SReg.Metropolitana=TODAS_AS_CATEGORIAS__&SAglomerado_urbano=TODAS_AS_CATEGORIAS__&SEnsino%2FPesquisa=TODAS_AS_CATEGORIAS__&SEsfera_Administrativa=TODAS_AS_CATEGORIAS__&SNatureza=TODAS_AS_CATEGORIAS__&STipo_de_Estabelecimento=TODAS_AS_CATEGORIAS__&STipo_de_Gest%E3o=TODAS_AS_CATEGORIAS__&STipo_de_Prestador=TODAS_AS_CATEGORIAS__&STipo_da_Equipe=1&STipo_da_Equipe=2&STipo_da_Equipe=3&zeradas=exibirlz&formato=prn&mostre=Mostra" ;; esac # obter dados via requisição HTTP POST echo "Buscando dados do servidor..." curl -s -d $par $url > $res # extrair trecho e converter em formato CSV sed -n '/<PRE>/,/PRE>/p' $res | tr -d '\r' | sed \ -e '/^"[0-9]/!d' -e 's/^"\([0-9]\+\).*;/\1;/' \ -e '/^000000/d' -e 's/;-$/;0/' > $csv # manipulações no banco de dados export PGDATABASE="banco" export PGHOST="servidor" export PGUSER="usuario" # popular tabela de entrada echo "Populando tabela de entrada..." cat $csv | psql -c "TRUNCATE TABLE indicador.datasus_entrada; COPY indicador.datasus_entrada FROM stdin CSV DELIMITER ';'" # popular tabela final echo "Populando tabela final..." echo "UPDATE indicador.datasus_municipio SET $col = NULL WHERE num_ano_mes = $ano$mes" | psql echo "INSERT INTO indicador.datasus_municipio (cod_munic, num_ano_mes, $col) SELECT cod_munic, $ano$mes, vlr_dado FROM indicador.datasus_entrada" | psql echo "UPDATE indicador.datasus_municipio a SET $col = vlr_dado FROM indicador.datasus_entrada b WHERE a.cod_munic = b.cod_munic AND num_ano_mes = $ano$mes" | psql psql -c "TRUNCATE TABLE indicador.datasus_entrada; ANALYZE indicador.datasus_municipio"Lembre de dar permissão de execução nesse arquivo antes de rodá-lo. Esse script realiza os seguintes procedimentos:
- valida a informação de um parâmetro (entre "qrbr", "qabr", "aturgbr" e "equipebr") que identifica qual dos indicadores do DATASUS ele irá buscar
- utiliza a ferramenta cURL para fazer uma requisição HTTP do tipo GET e com isso obter o último período disponibilizado (i.e., Mês/Ano) para o indicador
- através de SED e AWK, extrai os valores de ano, mês e nome do arquivo a ser recuperado
- efetua uma segunda requisição HTTP com o cURL, desta vez do tipo POST, para agora assim obter os dados finais em formato pseudo-CSV
- usando SED, faz o parse da página HTML obtida e extrai apenas os dados desejados, transformando-os em um arquivo do tipo CSV puro
- com o auxílio do utilitário psql, interface cliente do PostgreSQL via terminal, atualiza as tabelas que criamos
Ufa! Espero que tenha sido claro nessa explicação do script... :D
Grave o arquivo e agora tente executar o script! Sem especificar parâmetro, ele exibirá o seguinte:
$ ./carregar_indice_datasus.sh Sintaxe: ./carregar_indice_datasus.sh <indicador> Indicadores: qrbr qabr aturgbr equipebrChegou o grande momento! Ao especificar "qrbr", temos a seguinte saída:
$ ./carregar_indice_datasus.sh qrbr Indicador: Internações Hospitalares pelo SUS (qrbr) Buscando último período... Último período disponível: 06/2012 (arquivo qrbr1206.dbf) Buscando dados do servidor... Populando tabela de entrada... Populando tabela final... UPDATE 0 INSERT 0 5652 UPDATE 5652 ANALYZEExecuções subsequentes serão um pouco diferentes. Veja:
$ ./carregar_indice_datasus.sh aturgbr Indicador: Estabelecimentos com Atendimento de Urgência SUS (aturgbr) Buscando último período... Último período disponível: 06/2012 (arquivo stbr1206.dbf) Buscando dados do servidor... Populando tabela de entrada... Populando tabela final... UPDATE 5652 ERROR: duplicate key value violates unique constraint "datasus_municipio_pkey" DETAIL: Key (cod_munic, num_ano_mes)=(110001, 201206) already exists. UPDATE 5652 ANALYZEComo produtos intermediários, teremos os arquivos no formato CSV. Veja o conteúdo deles:
$ head -3 *.csv ==> aturgbr.csv <== 110001;1 110037;1 110040;1 ==> equipebr.csv <== 110001;5 110037;5 110040;3 ==> qabr.csv <== 110001;13718 110037;3216 110040;0 ==> qrbr.csv <== 110001;173 110037;60 110040;31Mas o nosso objetivo final era armazenar esses dados no banco de dados. Olha só como ficou:
$ psql -c "SELECT * FROM indicador.datasus_municipio LIMIT 5" cod_munic | num_ano_mes | num_inter_hospi_sus | num_consu_medic_sus | num_estab_urgen_sus | num_equip_saude_famil -----------+-------------+---------------------+---------------------+---------------------+----------------------- 110001 | 201206 | 173 | 13718 | 1 | 5 110037 | 201206 | 60 | 3216 | 1 | 5 110040 | 201206 | 31 | 0 | 1 | 3 110034 | 201206 | 21 | 0 | 1 | 2 110002 | 201206 | 438 | 40356 | 11 | 13 (5 rows)Ou seja, para cada município (i.e., código do IBGE) e período (i.e., ano/mês), obtivemos quatro indicadores de saúde do DATASUS.
E aí, demorou muito pra executar? Não mediu o tempo..? Sem problemas, execute isso agora:
$ time for i in qrbr qabr aturgbr equipebr; do ./carregar_indice_datasus.sh $i; doneEssa instrução executará de forma serializada o script para cada possível indicador. Observe o final da saída. Pra mim apareceu o seguinte:
real 0m15.776s user 0m0.976s sys 0m0.260sOu seja, levou menos de 16 segundos para coletar mais de 22 mil linhas de dados do DATASUS transmitidos em 700 kB em um total de 8 requisições HTTP e ainda gravar tudo no banco de dados de forma estruturada... E isso via terminal e sem qualquer intervenção humana - porque pessoas erram! :D