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

Raspador de Suzano-SP #983

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

carlosfrodrigues
Copy link

Segue o commit com o raspador e o log.
Fecha a issue #973

@trevineju trevineju added the hacktoberfest-accepted Pull Requests aprovados na Hacktoberfest label Oct 21, 2023
@trevineju trevineju linked an issue Oct 26, 2023 that may be closed by this pull request
1 task
Copy link
Member

@ogecece ogecece left a comment

Choose a reason for hiding this comment

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

Oi @carlosfrodrigues ! Esse site deve ter dado dor de cabeça, deu em mim também. Fizemos a revisão do código no GT de Raspadores dessa última terça 21/11/23 então está bem completo. Testamos ao vivo e até conseguimos coletar mais arquivos do que originalmente pois não coleta apenas PDF. Deixo aqui várias dicas e proposta de melhorias. Fica a vontade pra tirar dúvida ou questionar qualquer coisa!


Por documentação:

Algumas datas não são confiáveis como na edição 049 onde o título apresenta publicação em 14/02/2018 mas deveria ser de 14/03/2018 e nesse caso daria pra corrigir pela URL. Porém em outras edições o título está correto e a URL não como na edição 102 de 01/06/2019.

Copy link
Member

Choose a reason for hiding this comment

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

Esse arquivo pode ser removido do PR. Não precisava subir ele no conteúdo do PR, bastava linkar na descrição mesmo hehehe

name = "sp_suzano"
TERRITORY_ID = "3552502"
start_date = datetime.date(2017, 8, 1)
start_urls = ["https://suzano.sp.gov.br/web/transparencia/imprensa-oficial/"]
Copy link
Member

Choose a reason for hiding this comment

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

Foi percebido que no início da raspagem dois redirects aconteciam:

    2023-11-21 19:34:34 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (301) to <GET https://suzano.sp.gov.br/transparencia/imprensa-oficial> from <GET https://suzano.sp.gov.br/web/transparencia/imprensa-oficial/>
    2023-11-21 19:34:35 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (301) to <GET https://suzano.sp.gov.br/transparencia/imprensa-oficial/> from <GET https://suzano.sp.gov.br/transparencia/imprensa-oficial>
    2023-11-21 19:34:35 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://suzano.sp.gov.br/transparencia/imprensa-oficial/> (referer: None)

Pra evitar essas requisições extras toda vez que formos raspar o dia anterior, é melhor substituir o start_urls já com o endereço final:

Suggested change
start_urls = ["https://suzano.sp.gov.br/web/transparencia/imprensa-oficial/"]
start_urls = ["https://suzano.sp.gov.br/transparencia/imprensa-oficial/"]

Comment on lines +15 to +18
info = response.xpath("/html/body/div[2]/div/div/div[2]/div/div/*/*/span[2]")
data = response.xpath(
"/html/body/div[2]/div/div/div[2]/div/div/*/div[@class='desc']"
)
Copy link
Member

Choose a reason for hiding this comment

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

Sugiro não usar o xpath completo nos seletores pois são muito rígidos, ou seja, propensos a erros por mudanças pequenas na estrutura do html. Temos uma aula no curso de Python p/ Inovação Cívica da Escola de Dados sobre seletores. Lá cobrimos um pouco de xpath. Vale dar uma conferida.

Mas sugiro uso de css aqui pois nesse caso as âncoras de seleção que temos no html usam bastante o atributo class e css lida melhor com ele do que xpath:

Suggested change
info = response.xpath("/html/body/div[2]/div/div/div[2]/div/div/*/*/span[2]")
data = response.xpath(
"/html/body/div[2]/div/div/div[2]/div/div/*/div[@class='desc']"
)
titles = response.css("div.box-editais-licitacoes-2015 div.g-el2 > p > span.b::text")
#
# sugiro usar um nome diferente para a variável (titles > info) para ficar um pouco mais descritivo
url_boxes = response.css(
"div.box-editais-licitacoes-2015 div.g-el2 > p + div"
) # também não usamos xpath completo, passamos a usar css e sugerir url_boxes > data

Além disso, sugiro mudar os nomes das variáveis pra representarem um pouco melhor o que estamos coletando, info e data ficaram um pouco genéricos.

Copy link
Member

Choose a reason for hiding this comment

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

As próximas sugestões de modificação vão usar essa nomenclatura de titles e url_boxes.

)
file_pattern = r'href="(https://[^"]+\.pdf)"'
date_pattern = r"(\d{1,2}[./]\d{1,2}[./]\d{4})"
for index, element in enumerate(data):
Copy link
Member

Choose a reason for hiding this comment

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

Não é necessário usar índices ao lidar com duas lista de mesmo tamanho. Podemos usar zip no lugar

Suggested change
for index, element in enumerate(data):
for title_raw, url_box in zip(titles, url_boxes):

Comment on lines +22 to +26
title = info[index].get()
matches_url = re.findall(file_pattern, element.get())
matches_date = re.findall(date_pattern, title)
if matches_url and matches_date:
url = matches_url[0]
Copy link
Member

Choose a reason for hiding this comment

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

Aqui seria bom mudar algumas coisas:

  1. Estamos usando re.findall para buscar a url, quando poderíamos usar um seletor pro atributo href (isso até faz com que possamos coletar arquivos não-PDF como .docx na edição 20.2 de 2021-01-28)
    1.1. Imagino que você já tenha detectado isso, mas agora seria necessário verificar se há ou não urls (casos onde não aparecem urls ou aparece apenas uma ao invés do comum que seria a url da imagem primeiro e a url do arquivo por último)
  2. Outra coisa também é que estávamos usando o re.findall pra pegar apenas a primeira ocorrência. Nesse caso é melhor usar re.search. Usar o método .re_first de uma resposta de seletor também é uma boa alternativa pois às vezes nem precisaríamos importar a biblioteca re.
  3. Achei que valia adicionar um log de debug para linhas sem data
  4. Ao invés de verificar if matches_url and matches_date: e continuar com o código dentro da verificação é interessante fazer esses curto-circuitos em Python pro código não expandir demais horizontalmente (nesse caso fazia sentido separar em 2 verificações distintas, mas também poderia verificar a negação not matches_url or not matches_date)
  5. Apesar de o pipeline estar dando conta nesse caso, temos dado preferência a manter isso no código dos raspadores por padrão
Suggested change
title = info[index].get()
matches_url = re.findall(file_pattern, element.get())
matches_date = re.findall(date_pattern, title)
if matches_url and matches_date:
url = matches_url[0]
urls = url_box.css("a::attr(href)").getall()
if not urls:
continue
url = urls[-1]
date_raw = title_raw.re_first(r"\d{1,2}[./]\d{1,2}[./]\d{4}")
if not date_raw:
self.logger.debug(f"Não é possível encontrar a data do elemento com título '{title_raw.get()}'. URL de referência: {url}")
continue
if self.start_date > date > self.end_date:
continue

Comment on lines +27 to +34
if "/" in matches_date[0]:
date = datetime.datetime.strptime(
matches_date[0], "%d/%m/%Y"
).date()
else:
date = datetime.datetime.strptime(
matches_date[0], "%d.%m.%Y"
).date()
Copy link
Member

Choose a reason for hiding this comment

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

Não é necessário usar dois casos de formato de data, podemos substituir um para o outro e apenas considerar um dos casos.

Suggested change
if "/" in matches_date[0]:
date = datetime.datetime.strptime(
matches_date[0], "%d/%m/%Y"
).date()
else:
date = datetime.datetime.strptime(
matches_date[0], "%d.%m.%Y"
).date()
date = datetime.datetime.strptime(
date_raw.replace(".", "/"), "%d/%m/%Y"
).date()

date = datetime.datetime.strptime(
matches_date[0], "%d.%m.%Y"
).date()
is_extra = True if (title.find("Extra") != -1) else False
Copy link
Member

Choose a reason for hiding this comment

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

Seria bom usar lowercase pra poder pegar outras possíveis formas de escrita e também fazer verificação de substring usando o in me parece um pouco mais legível. Além disso, não é necessário a ternária True if ... else False pois o retorno já é booleano (tanto no código original quanto na sugestão).

Suggested change
is_extra = True if (title.find("Extra") != -1) else False
is_extra = "extra" in title_raw.get().lower()

Comment on lines +36 to +42

yield Gazette(
date=date,
file_urls=[url],
is_extra_edition=is_extra,
power="executive",
)
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 tínhamos as edições disponíveis pra coleta então quis fazer esse agrado. Me arrependi amargamente pois existem muitos casos de fronteira uawheauwehauweuhaweuawe

Suggested change
yield Gazette(
date=date,
file_urls=[url],
is_extra_edition=is_extra,
power="executive",
)
edition_regex = re.compile(r"""
(edição|edital) # o padrão inicia com 'edição' mas há caso de 'edital'
(\s+extra)? # pode ser uma edição 'extra'
(\s+n°)? # pode ou não ter um 'nº'
[\s–-]* # às vezes vezes o número de edição não é separado do que vem antes ou depois, antes geralmente pode vir espaço, traço ou travessão
([.\d]+) # número de edição
.+? # qualquer coisa entre o número de edição e a data de publicação, caso contrário a data pode ser capturada como número de edição
\d{1,2}[./]\d{1,2}[./]\d{4} # data de publicação
""", flags=re.IGNORECASE | re.VERBOSE) # dá pra pegar edition_number e isso é um inferno pois escrevem manualmente e é cheio de casos de fronteira
edition_number = re.search(edition_regex, title_raw.get())
yield Gazette(
date=date,
file_urls=[url],
is_extra_edition=is_extra,
edition_number=edition_number.group(4) if edition_number else '',
power="executive",
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hacktoberfest-accepted Pull Requests aprovados na Hacktoberfest
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Novo spider]: Suzano-SP
3 participants