Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cria raspador para Maragogi-AL #1176 #1186

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions data_collection/gazette/spiders/al/al_maragogi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import re
from datetime import date, datetime

import scrapy
from dateutil.rrule import YEARLY, rrule

from gazette.items import Gazette
from gazette.spiders.base import BaseGazetteSpider


class AlMaragogiSpider(BaseGazetteSpider):
name = "al_maragogi"
TERRITORY_ID = "2704500"
allowed_domains = ["maragogi.al.gov.br"]
base_url = "https://maragogi.al.gov.br/diarios-oficiais/diario-oficial-"
start_urls = ["https://maragogi.al.gov.br/diarios-oficiais/"]
start_date = date(2020, 1, 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vi que somente a partir de 17/04/2024 começaram a publicar os diários individuais de Maragogi, sendo antes disso todos da Associação dos Municípios Alagoanos, diários já cobertos pelo raspador al_associacao_municipios.

Seria importante modificar essa data inicial pra gente não pegar esses diários da associação como sendo apenas do município de Maragogi, e talvez, como uma medida extra de segurança, colocar uma verificação para não baixar arquivos que contém "AMA" no título, pois esse é o padrão que estão usando, aparentemente.

O que acha?

end_date = date.today()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Este atributo já é inserido automaticamente como date.today() na BaseGazetteSpider, então pode omitir aqui.

Suggested change
end_date = date.today()

stop_crawling = False

def extrair_numero(self, arquivo):
match = re.search(r"no-(\d+)-", arquivo)
if match:
return match.group(1)
return None
Comment on lines +21 to +25
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Geralmente, a não ser que o parsing para um campo seja muito complexo, não compensa desviar o foco da lógica de parsing para outro método, prejudicando o fluxo de leitura. Nesse caso, acho que não precisamos desse método.


def start_requests(self):
for date_of_interest in rrule(
freq=YEARLY, dtstart=self.start_date, until=self.end_date
):
base_url = f"{self.base_url}{date_of_interest.year}/"
yield scrapy.Request(url=base_url, callback=self.parse)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pode omitir pois self.parse é o callback padrão dos objetos Request

Suggested change
yield scrapy.Request(url=base_url, callback=self.parse)
yield scrapy.Request(url=base_url)


def parse(self, response):
if self.stop_crawling:
return

titles = response.css(".arq-list-item-content h1::text").getall()
dates = response.css(".data::text").getall()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abrindo os arquivos vemos que essa data não é a data de publicação do diário. A data de publicação está no título do documento :/

O que é chato, pois o título do documento não está totalmente padronizado e o padrão de data vai precisar ser extraído com regex. É necessário mudar e fazer uma boa validação, pra ver se o regex está pegando todos os documentos desejados que conhecemos.


for title, data_str in zip(titles, dates):
edition_number = self.extrair_numero(title)
data_str = data_str.strip()

try:
item_date = datetime.strptime(data_str, "%d/%m/%Y").date()
except ValueError:
continue

if item_date < self.start_date:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outra coisa importante a se considerar com extração de datas a partir do título. Duas páginas em 2024:
image
image
As datas podem ficar muito fora de ordem, inclusive entre páginas... Pra não dispensarmos totalmente a verificação de data e fazermos a busca no ano inteiro (mesmo quando quiséssemos só o último dia, por exemplo, que seria muito mal otimizado) poderíamos considerar até dois meses antes do mês do start_date como o limite para executar o return e até então só executar continue. O que acha?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, outra coisa para ser adicionada aqui é uma verificação para não coletar itens depois posteriores a end_date

if item_date > self.end_date:
    continue

self.stop_crawling = True
return
Comment on lines +50 to +52
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pela natureza assíncrona do Scrapy, o uso de self.stop_crawling vai gerar comportamentos inesperados e é necessário remover essa lógica.

Um exemplo:

Se os anos a pesquisar fossem 2024, 2023, 2022, 2021 e 2020 e a start_date inserida por usuário fosse 2023-12-22 (finalzinho do ano).

2 requisições iniciais seriam enviadas (2024 e 2023), porém, não é garantido que a primeira resposta a ser processada seria do ano mais recente, seria a que voltasse primeiro. Ou seja, seria possível que a primeira resposta a ser processada fosse de 2023, onde alguns itens seriam raspados, mas a self.stop_crawling logo seria colocada para True e nenhum item de 2024 seria raspado.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ogecece Talvez aqui seria o uso do continue não?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ogecece analisando o site aqui tem uma request que achei aqui que podemos usar pra sanar melhor essa questão de data inicial + data final, utilizando o menu de busca com a opção de busca avançada ele gera uma request onde podemos trazer as publicações de diários com as datas já configurada, talvez tendo cuidado de fazer uma iteração nos anos das datas como fiz no SAE

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ogecece Talvez aqui seria o uso do continue não?

Continuando com o problema das datas fora de ordem, sim, perfeito, se a data fosse menor que self.start_date usaria continue. E sugeri que se a data identificada fosse dois meses anterior a self.start_date, poderia encerrar, aí poderia dar o return.

@ogecece analisando o site aqui tem uma request que achei aqui que podemos usar pra sanar melhor essa questão de data inicial + data final

Bicho, o sistema mudou mesmo. Pode ver que no print que tinha tirado antes era totalmente diferente. Vale testar até pra ver se algo do código do raspador ainda é aproveitável. Mas que bom que pelo menos a parte das datas parece estar resolvida.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consegui achar uma request aqui, estou fazendo o raspador do zero, pois com uma request consigo retornar os raspadores filtrado pela data e ordem que eu preciso


if title.endswith("."):
title = title[:-1]

if not title.endswith(".pdf"):
title += ".pdf"
Comment on lines +54 to +58
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isso poderia ser mais fácil se no começo da função não extraísse diretamente os h1::text mas sim os a externos ao h1 onde ainda teria acesso à URL. O que acha?

Ou até ficar no nível de article mesmo e pegar .arq-list-item-content h1::text pra extrair title e .arq-list-item-content a::attr(href) pra pegar a URL.


file_url = f"https://maragogi.al.gov.br/wp-content/uploads/{item_date.year}/{item_date.month:02d}/{title}"

yield Gazette(
date=item_date,
edition_number=edition_number,
is_extra_edition=False,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existem arquivos com extra no título indicando que são edições extra. É necessário implementar essa verificação.

image

file_urls=[file_url],
power="executive",
)

next_page = response.css("a.next.page-numbers::attr(href)").get()
if next_page:
yield scrapy.Request(url=next_page, callback=self.parse)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pode omitir pois self.parse é o callback padrão dos objetos Request

Suggested change
yield scrapy.Request(url=next_page, callback=self.parse)
yield scrapy.Request(url=next_page)

Loading