okfn-brasil / querido-diario

📰 Diários oficiais brasileiros acessíveis a todos | 📰 Brazilian government gazettes, accessible to everyone.
https://queridodiario.ok.org.br/
MIT License
1.12k stars 411 forks source link

Adiciona spider base para o sistema replicável Atende, e spiders derivados para as cidades com D.O.s nesse sistema. #1046

Open AlexJBSilva opened 12 months ago

AlexJBSilva commented 12 months ago

Checklist - Novo spider

Descrição

Adiciona o spider base para o sistema replicável Atende, e os spiders derivados para as cidades com D.O.s nesse sistema. Através do mapeador ATENDE (PR #1043), foram identificadas 34 cidades com Diários Oficiais disponíveis.

Esse sistema apresenta 2 layouts de página: layout Tipo 1 e layout Tipo 2.

id name state_code observação
4303103 Cachoeirinha RS Spider ainda não foi testado.
4303509 Camaquã RS Spider ainda não foi testado. Resolve #1038.
4311304 Lagoa Vermelha RS Realizando ajustes na Classe Spider Base T1 devido à falta de padrão nos links do site dessa cidade.
3552403 Sumaré SP Não precisa de um novo spider.

Sumaré já possui um spider funcional e não precisará de um novo spider, pois os diários no sistema Atende (layout Tipo 1) não possuem todos os metadados (inclusive data de publicação).

id name state_code observação
4101408 Apucarana PR Spider ainda não foi testado.
4201307 Araquari SC Spider ainda não foi testado.
4101804 Araucária PR Log: pr_araucaria.log CSV: pr_araucaria.csv
4302105 Bento Gonçalves RS Spider ainda não foi testado.
4104204 Campo Largo PR Spider ainda não foi testado.
4104303 Campo Mourão PR Log: pr_campo_mourao.log CSV: pr_campo_mourao.csv
4304200 Candelária RS Spider ainda não foi testado.
3114501 Carmópolis de Minas MG Spider ainda não foi testado.
4104907 Castro PR Spider ainda não foi testado.
4105706 Clevelândia PR Spider ainda não foi testado.
4106308 Corbélia PR Spider ainda não foi testado.
4306403 Dois Irmãos RS Spider ainda não foi testado.
4307807 Estrela RS Spider ainda não foi testado.
4309209 Gravataí RS Log: rs_gravatai.log CSV: rs_gravatai.csv
4109302 Guaraniaçu PR Spider ainda não foi testado.
4309605 Horizontina RS Spider ainda não foi testado.
4112959 Juranda PR Spider ainda não foi testado.
4209508 Laurentino SC Spider ainda não foi testado.
4114005 Mamborê PR Spider ainda não foi testado.
3145604 Oliveira MG Spider ainda não foi testado.
4117453 Ouro Verde do Oeste PR Spider ainda não foi testado.
4313904 Panambi RS Spider ainda não foi testado.
4119152 Pinhais PR Spider ainda não foi testado.
4122206 Rio Branco do Sul PR Spider ainda não foi testado.
4317202 Santa Rosa RS Spider ainda não foi testado.
4124103 Santo Antônio da Platina PR Spider ainda não foi testado.
4318432 São João do Polêsine RS Spider ainda não foi testado.
4320701 Sobradinho RS Spider ainda não foi testado.
4127957 Tupãssi PR Spider ainda não foi testado.
4118451 Pato Bragado PR Spider ainda não foi testado.

Dos testes realizados com Araucária, Campo Mourão e Gravataí:

Extra: Automatizando a criação de Spiders derivados.

Seguindo a dica da @trevineju, fiz o script abaixo para criar os spiders utilizando o arquivo cidades_atende_t2.csv com as infomações de configuração:

# script.py
import csv
from datetime import datetime
from pathlib import Path

from unidecode import unidecode

class SpiderBuilder:
    def read_csv(self, file):
        rows = []
        with open(file, "r", encoding="utf-8") as csvfile:
            reader = csv.DictReader(csvfile)
            for row in reader:
                rows.append(row)
        csvfile.close()
        return rows

    def format_str(self, name, state_code):
        name = unidecode(name).strip().lower().replace("-", " ").replace("'", "")
        state_code = unidecode(state_code).strip().lower()
        return name, state_code

    def blankspaces_to_underline(self, name):
        return name.replace(" ", "_")

    def to_pascal_case(self, name):
        return "".join(word for word in name.title() if not word.isspace())

    def get_spider_config(
        self, id, spider_name, class_name, start_date, city_subdomain, start_edition
    ):
        return f"""from datetime import date

from gazette.spiders.base.atende import BaseAtendeT2Spider

class {class_name}Spider(BaseAtendeT2Spider):
    TERRITORY_ID = "{id}"
    name = "{spider_name}"
    start_date = date({start_date.year}, {start_date.month}, {start_date.day})  # Edição {start_edition}
    city_subdomain = "{city_subdomain}"
"""

    def write_file(self, spider_name, spider_content):
        spiders_folder = Path("../gazette/spiders/")
        spider_path = spiders_folder.joinpath(
            spider_name.split("_", 1)[0], spider_name
        ).with_suffix(".py")
        # print (spider_path.resolve().absolute())
        # print (spider_content)
        with open(spider_path, "w", encoding="utf-8") as f:
            f.write(spider_content)

    def spider_from_cfg(self, config):
        name, state_code = self.format_str(config["name"], config["state_code"])
        state_plus_name = f"{state_code} {name}"
        spider_name = self.blankspaces_to_underline(state_plus_name)
        class_name = self.to_pascal_case(state_plus_name)
        start_date = datetime.strptime(config["start_date"], "%d/%m/%Y")
        content = self.get_spider_config(
            config["id"],
            spider_name,
            class_name,
            start_date,
            config["city_subdomain"],
            config["start_edition"],
        )
        self.write_file(spider_name, content)

if __name__ == "__main__":
    csv_file = "cidades_atende_t2.csv"
    sb = SpiderBuilder()
    spiders_cfg = sb.read_csv(csv_file)

    for config in spiders_cfg:
        sb.spider_from_cfg(config)

Para usar:

  1. Salve o script e o arquivo cidades_atende_t2.csv na pasta data: querido-diario/data_collection/data;
  2. Navegue até a pasta cd querido-diario/data_collection/data e execute o script python script.py.
trevineju commented 6 months ago

@AlexJBSilva muito obrigada pela PR!

Sei que ainda estava em rascunho, porém, tendo em vista as enchentes no Rio Grande do Sul, fizemos um esforço de priorizar a adição de municípios de lá e esta PR tem vários. Por isso, tomei a liberdade de seguir a partir de onde você parou.

Aqui você adiciona duas novas classes base e, como o "Layout 2" estava mais desenvolvido (você até anexou testes, enquanto o "Layout 1" não) e tem uma cobertura maior de municípios do RS, foquei nele.

Fiz uma PR (#1145) que puxa suas contribuições daqui e as finaliza lá. Optei por fazer isso e não revisar aqui pois, como disse acima, queria reduzir o escopo pra um sistema só por vez sem jogar fora o que você já tinha começado aqui (não queria perder o "Layout 1" e os demais municípios que já estão aqui).

Como acabei revisando vou deixar alguns feedbacks, mas mesmo eu não tenho certeza se precisa (visto que ainda era rascunho, pode ser só que você ia ajustar depois)

  1. Não precisa deixar um município por commit quando for essa situação de municípios padronizados. Pode adicionar vários em um só.

  2. Sei que não tínhamos outro caso para você se espelhar, mas não deixamos duas classes em um único arquivo. Adotei atende_layoutdois.py na minha revisão. Confesso que não sei se é um bom nome pro arquivo em si, porém ficou separado em dois (deixando sugestivo que o outro vai ser atende_layoutum.py), que é o que precisa.

  3. Você estava deixando parâmetros de requisição hardcoded na URL, como em /atende.php?rot=54015&aca=101&ajax=t&processo=loadPluginDiarioOficial e &parametro=%7B%22codigoPlugin%22%3A1,%22filtroPlugin%22%3A%7B%22pagina%22%3A%22{page}%22%7D%7D" Costumamos a usar o parâmetro formdata + requisição scrapy.FormRequest nesses casos.

  4. (e mais importante) Estava caminhando muito bem ❤️ Fora alguns detalhes de organização (tanto é que meus 2 dos 3 feedbacks acima eram mais questão de organização mesmo), as ordem das requisições e as lógicas de coleta dos metadados tavam ótimas.

Muito obrigada pela contribuição