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

Mg Uberlândia [WIP] #37

Closed
wants to merge 10 commits into from

Conversation

juniorcarvalho
Copy link

Raspagem inicial para Uberlândia-MG

Estou com dificuldade de pegar a data do diário:
https://pt.stackoverflow.com/questions/296567/scrapy-xpath-href-ou-span-dentro-da-div

Pagina inicial do diário: http://www.uberlandia.mg.gov.br/?pagina=Conteudo&id=39

(PS: Este PR corrige o anterior onde fiz junto com mg_belohorizonte)

@cuducos
Copy link
Contributor

cuducos commented May 8, 2018

Atualizando o papo que começou em outro PR (mas sobre Uberlândia), a Prefeitura disse que está de olho! Aguardemos resposta então : )

@stefersonferreira
Copy link

Pode fazer dessa forma abaixo, to preferindo usar o BeautifulSoup é bem mais simples e resolve perfeitamente.

from bs4 import BeautifulSoup
import scrapy

class MgUberlandia(scrapy.Spider):
name = 'mg_uberlandia'
start_urls = ['http://www.uberlandia.mg.gov.br/?pagina=Conteudo&id=3077']

def parse(self, response):
	soup = BeautifulSoup(response.body_as_unicode())
	a = soup.find_all('a')

	for link in a:
		print(link.get('href'))

@juniorcarvalho
Copy link
Author

juniorcarvalho commented May 8, 2018

@stefersonferreira consigo o mesmo resultado com o scrapy. O problema que não estou conseguindo pegar a 'data' que fica logo após a tag html (/a)

@anapaulagomes
Copy link
Collaborator

@juniorcarvalho , dá uma olhada na minha resposta lá no SO.

@juniorcarvalho
Copy link
Author

Obrigado @anapaulagomes . Depois da uma olhada no código e me de seu feedback. :-)
Agora vou focar na extração dos dados do PDF.

urls = [
'http://www.uberlandia.mg.gov.br/?pagina=Conteudo&id=2649',
'http://www.uberlandia.mg.gov.br/?pagina=Conteudo&id=2779',
'http://www.uberlandia.mg.gov.br/?pagina=Conteudo&id=3035',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Até agora não consigo acessar o site da prefeitura de Uberlândia. :( Consegue me dizer o que cada página significa?

Copy link
Author

Choose a reason for hiding this comment

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

Cada uma é um ano.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nesse caso, acho que seria interessante pegar os anos e os links dinamicamente ao invés de deixá-los hardcoded. Se não, quando chegar em 2019, você terá que mexer no código pra atualizar com o novo link. Isso acaba virando muito trabalho para os mantenedores.

Copy link
Author

Choose a reason for hiding this comment

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

Então...a falta de padronização da página que está matando. Por isto optei pelo hardcoded. Uma pena você não está conseguindo acesso para ver. Uma outra possibilidade seria utilizar o 'caminho xpath' tipo '//*[@id="home"]/table/tbody/tr/td[2]/table/tbody/tr[2]/td[1]/p[2]/span/span/a', mas ficaria hardcoded da mesma forma. Vou testar mais opções e ver o que consigo melhorar.

Copy link
Member

Choose a reason for hiding this comment

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

Por que vocês criou essa lista urls ao invés de utilizar apenas o start_urls?
Você faz uma requisição à URL que está dentro do start_urls e simplesmente ignora o seu conteúdo para então mandar as requisições da sua lista urls .

Use apenas o start_urls e faça o que precisa ser feito no parse, assim você economiza requisições.

Copy link
Member

Choose a reason for hiding this comment

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

Essa lista hard-coded de anos não é uma boa ideia, já que como a @anapaulagomes comentou, em 2019 você vai precisar fazer uma alteração novamente no código (considerando que eles apenas vão incluir um novo item na seção de Edições Anteriores).

Nessa URL você consegue obter todos os links necessários de todos os anos:

    start_urls = ['http://www.uberlandia.mg.gov.br/?pagina=Conteudo&id=39', ]

Copy link
Member

Choose a reason for hiding this comment

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

@juniorcarvalho , usar um seletor ('//*[@id="home"]/table/tbody/tr/td[2]/table/tbody/tr[2]/td[1]/p[2]/span/span/a') não é tornar o código hard-coded, já que se eles incluirem mais links, isso não iria mudar. Mas nesse seu caso, você realmente está deixando tudo específico demais, por isso parece ser algo hard-coded.

try:
date = dt.datetime.strptime(dates[i], '%d/%m/%Y')
except:
date = None
Copy link
Collaborator

Choose a reason for hiding this comment

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

Acho que a data é super relevante e não podemos correr o risco dela ser nula. Confere @Irio e @cuducos ?

for v in variants:
url = response.xpath(v).extract()
if len(url) > 0:
for u in url:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seria legal nomear as variáveis com nomes mais expressivos. v e u deixam mais difícil ler o código. :)

Copy link
Author

Choose a reason for hiding this comment

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

v de variants e u de url não ? me de uma dica!

Copy link
Member

Choose a reason for hiding this comment

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

O extract() retorna uma lista vazia caso não encontre nada, então não é necessário fazer a checagem do tamanho da lista.

    for variant in variants:
        for url in response.xpath(variant).extract():
            pass

Seja explícito no nome das suas variáveis. Nesse caso, como as utilizações de v estão a uma, duas linhas de distância do for, é fácil inferir o que elas significam, mas se o seu bloco tivesse algumas dezenas de linhas, ia ser ruim para entender o que aquela variável quer dizer.

Não tenha medo de digitar um pouco mais e ter uma variável com nome longo mas que seja explicito :-)

urls.append(u)
return urls

def list_dates(self, response):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ficou bacana a implementação! 👍🏽

variants = ['//p/span/text()',
'//p/span/span/text()',
'//p/text()'
]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seria bom dar uma passada no black pra deixar o código bonitinho. Acho que aqui, por exemplo, ficaria assim:

variants = [
    '//p/span/text()',
    '//p/span/span/text()',
    '//p/text()',
]

@rennerocha
Copy link
Member

@juniorcarvalho , na página http://www.uberlandia.mg.gov.br/?pagina=Conteudo&id=39 você consegue obter todos os links que você precisa.

Abra ela com o Developer Tools do Firefox (ou Inspector do Chrome):

developer_tools_uberlandia

Você vai perceber que todo o conteúdo (os links) que você precisa estão contidos dentro da <div id="home" class="colunaConteudo"></div>

No Scrapy você consegue buscar informações usando selectores em CSS (https://doc.scrapy.org/en/latest/topics/selectors.html#selectors)

Nesse caso, se você quiser obter o selector dessa div, você pode fazer:
selector = response.css('#home')

Dentro do Developer Tools você pode ver que os links que você quer, estão todos dentro de alguma <table>, então para obter todos os <a> que estão dentro de uma <table> dentro do <div id="home">, você pode fazer:
selector = response.css('#home table a')

Como te interessa apenas os hrefs:
urls = response.css('#home table a::attr(href)').extract()

Agora você tem a lista das URLs desta página. Algumas são links para download e outras são links para as outras páginas dos anos anteriores. Agora só fazer os requests das páginas de ano e retornar os items das páginas de download.

@juniorcarvalho
Copy link
Author

@rennerocha @anapaulagomes obrigado pelas dicas! fiz as alterações.


def parse(self, response):
urls = response.css(
"#home table[align*=right] td[style*=vertical-align] a::attr(href)"
Copy link
Member

Choose a reason for hiding this comment

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

Eu não gosto de definir seletores usando informações de alinhamento. Caso eles mudem levemente a página e comecem a usar uma classe para alinhamento ao invés do align (o que seria o certo, por sinal :-) ), seu spider quebra. Sempre prefira usar o id do elemento (que é menos provável que mude com o tempo) ou uma classe que tenha algum sentido semântico (nesse caso não existe).

Ao invés, você pode pegar todos os links dessa página e filtrar só os que te interessam:
urls = response.css('#home table a::attr(href)').extract()

Nesse caso você só quer as URLs do seguinte formato:
http://www.uberlandia.mg.gov.br/?pagina=Conteudo&id={ALGUM_NUMERO}

Porém existem URLs desse formato:
http://www.uberlandia.mg.gov.br/uploads/cms_b_arquivos/19147.pdf

Como o padrão é bem claro, você pode filtrar nessa lista só as que estão no formato que você quer. Uma ideia seria usar uma regex para filtrar isso (https://doc.scrapy.org/en/latest/topics/selectors.html#using-selectors-with-regular-expressions):
urls = response.css('#home table a::attr(href)').re('.*Conteudo.*')

Aqui você tem a lista de todas as URLs que você realmente quer trabalhar nesta página.

urls = response.css(
"#home table[align*=right] td[style*=vertical-align] a::attr(href)"
).extract()
urls = self.last_four_years(urls)
Copy link
Member

Choose a reason for hiding this comment

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

Eu não tentaria filtrar por data nesse momento. Você está pegando as 4 primeiras URLs, mas quem garante que elas realmente são dos últimos 4 anos?
Estamos vendo de fazer a filtragem por data de outra maneira (veja #39 e #23), então acho que não faz nenhum mal deixar o spider pegar tudo o que encontrar por enquanto.

)
return items

def links_months(self, response):
Copy link
Member

Choose a reason for hiding this comment

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

Seletores muito específicos dentro de um HTML são ruins, já que qualquer pequena mudança na tela (por exemplo, um ser removido) faz com que ele pare de funcionar.

Abra o Developer Tools na página e dê uma analisada no HTML. Nesse caso por exemplo, dentro da div com id="home", os únicos links são aqueles que você precisa. Então ao invés de fazer essa função toda, você pode simplesmente pegar todas as tags a dentro da div:

response.css('#home a::attr(href)').extract()

Assim você pode excluir essa função inteira e você diminui a necessidade de manutenção futura.

urls_return.append(url)
return urls_return

def list_dates(self, response):
Copy link
Member

Choose a reason for hiding this comment

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

O mesmo comentário que fiz no seu método links_months, vale para esse. Não use seletores tão específicos.

Assim você obtém os blocos de cada uma das edições:
edicoes = response.css('#home div')

Que seria isso por exemplo:

<div>
	<strong><a href="http://www.uberlandia.mg.gov.br/uploads/cms_b_arquivos/13792.pdf" target="_blank">Edição 4754</a></strong> - 21/10/2015</div>

Aqui você tem o link do download E a data da edição.

Você pode iterar nesses seletores e obter as informações que precisa:

for edicao in edicoes:
    url = edicao.css('a::attr(href)').extract_first()
    data = re.findall("\d{2}/\d{2}/\d{4}", edicao.extract())  # Ou edicao.re("\d{2}/\d{2}/\d{4}")

@alfakini alfakini mentioned this pull request May 24, 2018
@giovanisleite
Copy link
Contributor

Don't forget to update the cities.md

@jvanz
Copy link
Member

jvanz commented Dec 6, 2019

@juniorcarvalho are you working on this? :)

@rafaelhfreitas
Copy link

@juniorcarvalho precisa de alguma ajuda com o trabalho ? Esse final de semana, fiz o fork para começar a implementação e fiz a parte inicial de buscar os links dos arquivos. A sua implementação já esta bem adiantada.

@rafaelhfreitas
Copy link

rafaelhfreitas commented Jul 8, 2020

Boa tarde pessoal. estou retomando essa atividade.

Já fiz o levantamento das paginas que o spider deverá utilizar para fazer a raspagem.

https://www.uberlandia.mg.gov.br/prefeitura/orgaos-municipais/procuradoria-geral-do-municipio/diario-oficial-uberlandia/diario-oficial-anos-anteriores/

https://www.uberlandia.mg.gov.br/2020/07/?post_type=diariooficial

Mas estou em dúvida em como deixar a execução de forma que ele sempre busque o que foi incrementado no portal e não baixe todos os diários novamente.
Usando os demais como exemplo, eu percebi que os outros diários o método parse gera um objeto do tipo Gazette.
Preciso me preocupar com isso ? O Spider poder rodar qts vezes quizer ? Os dados armazenados serão apagados e recriados a cada nova execução ?

Dúvida já sanada.

http://jvanz.com/como-funciona-o-robozinho-do-serenata-que-baixa-os-diarios-oficiais.html#como-funciona-o-robozinho-do-serenata-que-baixa-os-diarios-oficiais

@rafaelhfreitas
Copy link

rafaelhfreitas commented Jul 9, 2020

Boa tarde pessoal.

Precisando de ajuda na tarefa.

Seguinte estou usando o shell do scrapy para testar meus seletores que irei utilizar no spider.
Mas estou tendo problemas para pegar somente os elementos solicitados.
Pelo meu entendimento o response.css do scrapy é um seletor que trabalha em cima da classe de css dos elementos.
Mas quando utilizo ele, estou recebendo uma lista vazia.

Com xpath, consegui filtrar apenas os elementos a da pagina, mas havia outros elementos que não eram do diário e não consegui filtrar eles.

Screenshot from 2020-07-09 17-19-48
Screenshot from 2020-07-09 17-19-25
Screenshot from 2020-07-09 17-19-05

Vi que é possível aplicar uma expressão regular ao css.
Estou tentando utilizar response.css('li.a::href').re(r'https://www.uberlandia\w*')
mas estou recebendo um erro:

Screenshot from 2020-07-09 17-33-47

Screenshot from 2020-07-09 17-38-15

@cuducos
Copy link
Contributor

cuducos commented Jul 9, 2020

//div/ul/li/a no xpath e li.a no seletor CSS querem dizer coisas diferentes. O primeiro diz todo a dentro de um nó li dentro de um nó ul dentro de um nó div. O segundo diz todo nó li com a classe a.

@rafaelhfreitas
Copy link

rafaelhfreitas commented Jul 9, 2020

Consegui uma forma. Tava fazendo trabalho de iniciante mesmo.

Screenshot from 2020-07-09 17-52-27

@rafaelhfreitas
Copy link

//div/ul/li/a no xpath e li.a no seletor CSS querem dizer coisas diferentes. O primeiro diz todo a dentro de um nó li dentro de um nó ul dentro de um nó div. O segundo diz todo nó li com a classe a.

Obrigado pelo retorno, agora que vi aqui sua resposta Eduardo. Mas era coisa de noob mesmo, vlw pela explicação.

@jvanz jvanz changed the base branch from master to main August 12, 2020 01:47
@jaswdr
Copy link
Contributor

jaswdr commented Jul 18, 2021

@rafaelhfreitas pensa em continuar essa PR ainda?

@rafaelhfreitas
Copy link

Bom dia.

Penso sim, até me inscrevi no curso que esta sendo oferecido agora em agosto para finalizar o spider.

@rafaelhfreitas
Copy link

Atividade retornada, já fiz um novo fork e estou vendo as aulas do @giuliocc na escola de dados para entregar a primeira contruibuição com o projeto !

@rennerocha
Copy link
Member

Closing stale PR.

@rennerocha rennerocha closed this Jun 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants