Python Brasil
25.7K subscribers
587 photos
11 videos
15 files
3.53K links
Canal para compartilhamento de links, cursos vagas e eventos sobre Python.

Links, contato e freelas: https://linktr.ee/python.brasil

@laenderoliveira
Download Telegram
Django project optimization guide (part 1)
http://dizballanze.com/django-project-optimization-part-1/
RT @pythonbrasil: Não podia faltar ela...nossa querida pylady Paola Katherine( @pk_pacheco) também será keynote na #pybr13... https://t.co/
Forwarded from A P
Olá, pessoal!

A Myreks está aberta para quem quiser conhecer e fazer parte do time.


Estamos com a posição para Operations Engineer aberta, para ajudar a construir nossa cultura devops, colaboração entre as pessoas e evolução contínua do produto.


A descrição da vaga está aqui:

https://www.myreks.com/v3/vagas....­


Quem quiser conversar, pode me procurar direto, andre@myreks.com, skype:andre.pastore, telegram:apast, twitter:apast, ou pode escrever nos canais do site.
Criando um bot de notícias para o Telegram usando Scrapy e Firebase
http://feedproxy.google.com/~r/NullOnError/~3/tphk9yKZ8bE/

Problema

Eu costumo pegar com frequência a rodovia Régis Bittencourt (http://www.autopistaregis.com.br/) e o que acontece com frequência é o trânsito parar completamente no meio do nada e sem acesso à internet, então eu fico sem a mínima noção do que está acontecendo e em quanto tempo conseguirei chegar ao meu destino.

Pensando nisso, decidi escrever um pequeno bot para o Telegram que publica num canal as notícias da estrada! Como de costume no NULL on error, vou explicar como fiz.

Web scraping
O primeiro passo é extrair as informações do site. Eu optei por utilizar o framework Scrapy (https://scrapy.org/), por alguns motivos que ficarão bem claros abaixo e por ter bastante experiência escrevendo web crawlers com o Scrapy, eu não pretendo escrever um tutorial a respeito neste artigo, isso ficará para uma próxima oportunidade.

Antes de tudo eu preciso definir o que eu quero extrair; isso é feito definindo uma class com N campos (https://doc.scrapy.org/en/latest/topics/items.html#scrapy.item.Field) herdando de scrapy.Item (https://doc.scrapy.org/en/latest/topics/items.html)

class Entry(Item):
uid = Field()
spider = Field()
timestamp = Field()
content = Field()



Como é possível notar a aranha, ou crawler ficou bem simples, mas como pode ser visto abaixo, vou explicar cada parte a seguir.

class RegisSpider(CrawlSpider):
name = 'regis'
allowed_domains = ['autopistaregis.com.br']
start_urls = ['http://www.autopistaregis.com.br/?link=noticias.todas']
rules = (
Rule(LinkExtractor(allow=r'\?link=noticias.?ver*'), callback='parse_news'),
)

def parse_news(self, response):
loader = EntryLoader(item=Entry(), response=response)
loader.add_xpath('content', '//*[@id="noticia"]/p[not(position() > last() -3)]//text()')
return loader.load_item()



A propriedade start_urls indica onde a aranha vai iniciar a varredura de páginas

Após isso, definimos algumas regras. Vou usar um LinkExtractor (https://doc.scrapy.org/en/latest/topics/link-extractors.html), que, como o próprio nome diz é um componente para extrair links seguindo uma regra das páginas encontradas. Nesse caso eu usei uma expressão regular que bate com todas as URLS de notícias do site, e defino um callback que será chamado para cada página chamado parse_news

LinkExtractor(allow=r'\?link=noticias.?ver*'), callback='parse_news')



Então é aqui que a mágica toda acontece: passei algum tempo analisando o código fonte da página e usando o inspetor web para poder gerar um xpath que bata com notícia, excluindo as informações extras na página.

XPath

O XPath (https://www.w3schools.com/xml/xml_xpath.asp) é uma forma de atravessar o html e extrair alguma informação específica. É uma linguagem bem poderosa, neste caso eu usei a expressão [not(position() > last() -3)] para excluir os últimos 3 parágrafos marcados pela tag , que o site sempre coloca como uma forma de rodapé. Infelizmente, nem sempre os sites seguem boas práticas, o que me facilitaria e muito a extração dos dados!

loader.add_xpath('content', '//*[@id="noticia"]/p[not(position() > last() -3)]//text()')



Os outros campos, como ID da noticía e timestamp são “extraídos” usando um middleware chamado scrapy-magicfields (https://github.com/scrapy-plugins/scrapy-magicfields), desta maneira:

MAGIC_FIELDS = {
'uid': "$response:url,r'id=(\d+)'",
'spider': '$spider:name',
'timestamp': "$time",
}



O próximo passo é rodar o web crawler periodicamente. Eu usei o sistema de cloud do scrapinghub, que é a empresa que desenvolve o Scrapy e outras ferramentas de scraping, nele eu posso configurar para rodar de tempos em tempos o crawler. No meu caso eu configurei para rodar a cada 30 minutos,

Mesmo que possível, eu não posso publicar diretamente, apenas as novas notícias, caso contrário toda vez que o crawler rodar eu estaria poluindo o canal com as notícias repetidas. Então eu decidi salvar num banco de dados intermediário para conseguir distinguir o que é novo do que já foi indexado.

Persistência

Eis que entra o Firebase (https://firebase.google.com/), e sua nova funcionalidade chamada de functions (https://firebase.google.com/docs/functions), com o qual, eu posso escrever uma função que reage a determinados eventos no banco de dados, como por exemplo, quando um novo dado é inserido.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const request = require('request-promise');
const buildUrl = require('build-url');

admin.initializeApp(functions.config().firebase);


exports.notifyChannel = functions.database.ref('/news/{what}/{uid}')
.onCreate(event => {
const config = functions.config().telegram;
const url = buildUrl('https://api.telegram.org', {
path: `bot${config.bot.token}/sendMessage`,
queryParams: {
chat_id: config.channel.chat_id,
}
});

return request({
method: 'POST',
uri: url,
resolveWithFullResponse: true,
body: {
text: event.data.val().content
},
json: true
}).then(response => {
if (response.statusCode === 200) {
return response.body.id;
}
throw response.body;
}
);
});



Essa função é bem simples; basicamente, em qualquer evento de onCreate ela é chamada, eu faço uma chamada POST na API do Telegram com o nome do canal, token do bot e conteúdo, que no caso é o texto da notícia.