Um pouco sobre Scraping

Scraping é algo que com certeza movimenta bilhões de dólares por ano na economia mundial, basicamente é o ato de extrair dados de sites de forma automatizada, falo um pouco sobre neste primeiro texto onde mostro como extraí milhares de frases do site pensador, e aqui um pouco sobre a ideia de web scraping neste segundo. Dito isto, mão na massa!

Como extrair este site?

O site Painel Covid do Maranhão tem uma interface onde tudo tem algum restrição por cookies e códigos gerados aleatoriamente, mas além disso conta também existem outros problemas, mas vamos ver como podemos analisar o site e fazer sua extração dos dados de vacinação do estado.

Analisando

imagem1

A página inicial do site é esta e temos que clicar em vacinas, tente pegar os endereços do site de forma direta não funcionará, por que será? Será por quê? Vamos investigar algumas coisas, pra isso, pressine F12.

2

“Depois de pressionar F12, você deve algo assim:”

3

Note que usando essa ferramenta, podemos clicar em cada requisição, existem alguns tipos de requisições do protocolo HTTP, podemos ver os dados de cada uma e entender o que o site está realizando.

4

Recomendo que gaste um tempo observando os itens, pesquise os que não entender e recarregue a página algumas vezes para entender como ela realmente funciona, é um processo investigativo para podermos automatizar as requisições realizadas pelo navegador. Se você for mais raiz, possível que goste do mitmproxy, esta ferramenta mostra cada requisição e resposta feita.

Mas porque fazer a análise? Muitos sites utilizam tecnologias diferentes, alguns utilizam APIs para alimentar o front-end, criam mecanismos de autenticação internos para realizar requisições e mitigar o CSRF, talvez algum cookie ou alguma outra coisa que pode ficar no seu pé. Pra descobrir isso, primeiro olho os cabeçalhos, a requisição e as respostas de cada requisição e vejo o que muda para rastrear onde exatamente surgiu a informação.

Com o passar do tempo neste site, você vai perceber que apareceu um fator de autenticação nas requisições, em alguma requisição esse token foi gerado, onde? Fazendo uma pesquisa, encontramos a requisição responsável, no caso está no arquivo JavaScript “main-es2015.1fd5ebecf22be55d7c2e.js”, o nome desse arquivo pode mudar facilmente, então precisamos de um jeito para encontrá-lo sempre! Veja na imagem abaixo que nos cabeçalhos da requisição apareceu o campo Authorization:

image

Além disso, depois de analisar as requisições, percebemos que este site é apenas uma casca, o front-end consome uma API para alimentar as informações que são exibidas. No próprio registro acima, podemos ver que o domínio de requisição foi alterado.

Com um pouco mais de investigação nessa página, achamos os dados de vacinação por município, que é o que queremos. Para facilitar e não precisarmos registrar cidade por cidade, podemos extrair todos os números que a identificam no IBGE, eles são usados para fazer a requisição no endpoint e precisamos identificar onde estão os dados que queremos. Para isso, precisamos utilizar mais a página, depois de clicar nos dados de Vacinação Estadual, abrirá uma tela onde tem os dados regionais e municipais, nós queremos o segundo. Então, ao acessá-lo, vemos que os dados já estão organizados em um JSON, o que nos faz muito felizes.

7

8

Realizando a extração

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import requests
import json
from unidecode import unidecode
import re


class Maranhao:
    def __init__(self):
        url = "https://painel-covid19.saude.ma.gov.br"
        response = requests.get("https://painel-covid19.saude.ma.gov.br")
        element = re.findall(r'src="main.*"', response.text)[0].split('"')[1]
        self.__token = requests.get(url + "/" + element).content.decode()
        self.__token = (
            re.findall(r"token:\".*?\"", self.__token)[0].split(":")[1].replace('"', "")
        )
        self.__headers = {"Authorization": f"Bearer {self.__token}"}
        self.__endpoint = {
            "todas_cidades": "https://painel-covid19-back.saude.ma.gov.br/v1/covid19/municipios/vacinas/resumo",
            "cidade_detalhada": "https://painel-covid19-back.saude.ma.gov.br/v1/covid19/municipios/vacinas/",
        }
        self.__codigo_ibge = []
        self.__data = {}
        self.start()

    def start(self):
        self.get_cidades_ibge()
        data = self.get_cidade()
        self.save(data)

    def make_request(self, url):
        return json.loads(requests.get(url, headers=self.__headers).content)

    def get_cidades_ibge(self):
        cidades = self.make_request(self.__endpoint["todas_cidades"])
        for cidade in cidades:
            self.__codigo_ibge.append(cidade["codigo_ibge"])

    def save(self, data):
        for _data in data:
            city = unidecode(_data["prefeitura"]).replace(" ", "_")
            file = open(f"{city}.json", "w")
            file.write(json.dumps(_data))
            file.close()

    def get_cidade(self):
        _data = []
        for codigo in self.__codigo_ibge:
            cidade = self.make_request(
                self.__endpoint["cidade_detalhada"] + str(codigo)
            )
            _data.append(
                {
                    "prefeitura": cidade["municipio"],
                    "pupulacao-12+": cidade["populacao_vacina_12_17"]
                    + cidade["populacao_vacina_18"],
                    "doses-imunizacao": "8.658",
                    "cobertura-populacional": round(
                        cidade["cobertura_populacional"] * 100, 2
                    ),
                    "doses-distribuidas-total": cidade["doses_recebidas"],
                    "doses-aplicadas-total": cidade["doses_aplicadas"],
                    "cobertura-de-doses-aplicadas": round(
                        cidade["cobertura_geral"] * 100, 2
                    ),
                    "doses-distribuidas-tipo": {
                        "coronavac": {
                            "1a_dose": cidade["vacinas"][0]["doses_recebidas_d1"],
                            "2a_dose": cidade["vacinas"][0]["doses_recebidas_d2"],
                            "dr-cv": cidade["vacinas"][0]["doses_recebidas_d3"],
                            "total-recebidas-cv": cidade["vacinas"][0][
                                "doses_recebidas_total"
                            ],
                        },
                        "astrazeneca": {
                            "1a_dose": cidade["vacinas"][1]["doses_recebidas_d1"],
                            "2a_dose": cidade["vacinas"][1]["doses_recebidas_d2"],
                            "dr-az": cidade["vacinas"][1]["doses_recebidas_d3"],
                            "total-recebidas-az": cidade["vacinas"][1][
                                "doses_recebidas_total"
                            ],
                        },
                        "pfizer": {
                            "1a_dose": cidade["vacinas"][2]["doses_recebidas_d1"],
                            "2a_dose": cidade["vacinas"][2]["doses_recebidas_d2"],
                            "dr-pfz": cidade["vacinas"][2]["doses_recebidas_d3"],
                            "total-recebidas-pf": cidade["vacinas"][2][
                                "doses_recebidas_total"
                            ],
                        },
                        "janssen": {
                            "du": cidade["vacinas"][3]["doses_recebidas_total"]
                        },
                    },
                    "doses-aplicadas-tipo": {
                        "coronavac": {
                            "1a_dose": cidade["vacinas"][0]["doses_aplicadas_d1"],
                            "2a_dose": cidade["vacinas"][0]["doses_aplicadas_d2"],
                            "dr-cv": cidade["vacinas"][0]["doses_aplicadas_d3"],
                            "total-aplicada-cv": cidade["vacinas"][0][
                                "doses_aplicadas_total"
                            ],
                        },
                        "astrazeneca": {
                            "1a_dose": cidade["vacinas"][1]["doses_aplicadas_d1"],
                            "2a_dose": cidade["vacinas"][1]["doses_aplicadas_d2"],
                            "dr-az": cidade["vacinas"][1]["doses_aplicadas_d3"],
                            "total-aplicada-az": cidade["vacinas"][1][
                                "doses_aplicadas_total"
                            ],
                        },
                        "pfizer": {
                            "1a_dose": cidade["vacinas"][2]["doses_aplicadas_d1"],
                            "2a_dose": cidade["vacinas"][2]["doses_aplicadas_d2"],
                            "dr-pfz": cidade["vacinas"][2]["doses_aplicadas_d3"],
                            "total-aplicada-pfz": cidade["vacinas"][2][
                                "doses_aplicadas_total"
                            ],
                        },
                        "janssen": {
                            "du": cidade["vacinas"][3]["doses_aplicadas_total"]
                        },
                    },
                    "cobertura-aplicadas-tipo": {
                        "coronavac": {
                            "1a_dose": cidade["vacinas"][0]["cobertura_d1"],
                            "2a_dose": cidade["vacinas"][0]["cobertura_d2"],
                            "dr-cv": cidade["vacinas"][0]["cobertura_d3"],
                            "total-cobertura-aplicada-cv": cidade["vacinas"][0][
                                "cobertura_total"
                            ],
                        },
                        "astrazeneca": {
                            "1a_dose": cidade["vacinas"][1]["cobertura_d1"],
                            "2a_dose": cidade["vacinas"][1]["cobertura_d2"],
                            "dr-az": cidade["vacinas"][1]["cobertura_d3"],
                            "total-cobertura-aplicada-az": cidade["vacinas"][1][
                                "cobertura_total"
                            ],
                        },
                        "pfizer": {
                            "1a_dose": cidade["vacinas"][2]["cobertura_d1"],
                            "2a_dose": cidade["vacinas"][2]["cobertura_d2"],
                            "dr-pfz": cidade["vacinas"][2]["cobertura_d3"],
                            "total-cobertura-aplicada-pfz": cidade["vacinas"][2][
                                "cobertura_total"
                            ],
                        },
                        "janssen": {"du": cidade["vacinas"][3]["cobertura_total"]},
                    },
                    "grupo_vacina": {
                        "coronavac": {
                            "doses_recebidas_coronavac_d2_indigena": cidade["vacinas"][
                                0
                            ]["doses_aplicadas"][0],
                            "doses_recebidas_coronavac_d3_indigena": cidade["vacinas"][
                                0
                            ]["doses_aplicadas"][1],
                        },
                        "astrazeneca": {
                            "doses_recebidas_coronavac_d2_indigena": cidade["vacinas"][
                                1
                            ]["doses_aplicadas"][0],
                            "doses_recebidas_coronavac_d3_indigena": cidade["vacinas"][
                                1
                            ]["doses_aplicadas"][1],
                        },
                        "pfizer": {
                            "doses_recebidas_coronavac_d2_indigena": cidade["vacinas"][
                                2
                            ]["doses_aplicadas"][0],
                            "doses_recebidas_coronavac_d3_indigena": cidade["vacinas"][
                                2
                            ]["doses_aplicadas"][1],
                        },
                    },
                }
            )
            print(_data)
        return _data


Maranhao()

Para melhor compreender este código, recomendo: Programando com classe sem smokin - Parte 1

  • Linha 1 a 4: Faz a importação das bibliotecas necessárias para realizar a extração dos dados
  • Linha 7: Criamos a classe Maranhao
  • Linha 8: Criamos o método que é iniciado quando a classe é instanciada
  • Linha 9 a 23: Fazemos as requisições mais básicas, pegamos o token que está no javascript usando regex, definimos um cabeçalho para as requisições com o token de autenticação e declaramos as variáveis usadas em toda a classe
  • Linha 25 a 28: Criamos o método start que será nosso método principal e executado assim que a classe é instanciada
  • Linha 30 e 31: Como sempre fazemos requisições e nosso cabeçalho não muda, o método ajuda a gente a se comunicar apenas com os endpoints, sem se preocupar tanto com o cabeçalho
  • Linha 33 a 36: Fazemos uma requisição para pegar do endpoint todos os identificadores das cidades do IBGE, usaremos para fazer as requisições
  • Linha 38 a 43: A ideia aqui é pegar cada dado extraído e gerar um arquivo JSON para cada cidade
  • Linha 45 a 179: Fazemos a extração dos dados da cidade e depois organizamos os dados em um formato que queremos de forma arbitrária, deixando de modo que seja mais fácil trabalhar com eles de acordo com um sistema já existente, a importância aqui é adaptar o modelo. Depois de criado o vetor com os dicionários contendo os dados, é retornado o vetor com os dados de todas as cidades!

Pulo do gato: certificado SSL

Verifique no projeto de extração dos dados de vacinação que existe um arquivo pem, se você rodou o programa acima, receberá um erro sobre o certificado. Ele é usado para fazer a comunicação do frontend com o backend, precisamos então copiá-lo. Com o comando abaixo, podemos extrair o certificado que nos permite se comunicar com o backend:

echo | openssl s_client -showcerts -servername painel-covid19.saude.ma.gov.br -connect painel-covid19.saude.ma.gov.br:443 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > chain.crt

Depois disso, basta mover o certificado para o local certo.

sudo mv chain.crt /usr/local/share/ca-certificates/

Um outro jeito de realizar a mesma coisa

Para ver onde você deve instalar o certificado, você também pode utilizar o certifi no python, o comando a seguir mostra o arquivo que é responsável por armazená-los:

>>> certifi.where()
'/venv/lib/python3.9/site-packages/certifi/cacert.pem'

Conclusão

Ao executar o programa, você verá diversos arquivos sendo gerados, um para cada município do Maranhão. Agora podemos armazenar os dados, analisá-los e exibí-los como quisermos! 9