
Compartir:
Ben es un desarrollador de segunda carrera que anteriormente pasó una década en los campos de la educación de adultos, la organización comunitaria y la gestión de organizaciones sin ánimo de lucro. Trabajó como defensor de los desarrolladores para Vonage. Escribe regularmente sobre la intersección entre el desarrollo comunitario y la tecnología. Originario del sur de California y residente durante mucho tiempo en Nueva York, Ben reside ahora cerca de Tel Aviv (Israel).
Rastrea la web en busca de actualizaciones y envía alertas SMS con Ruby
Tiempo de lectura: 18 minutos
A todos nos ha pasado. Te despiertas y es un nuevo día, y sólo necesitas saber la respuesta a la pregunta más candente. ¿Ya es fin de semana? La semana ha sido muy ajetreada con responsabilidades profesionales y personales. Lo único que quieres es tomarte un par de días para sentarte y relajarte.
Por supuesto, para responder a esta pregunta puedes abrir la aplicación de calendario de tu teléfono o preguntar a tu asistente personal digital favorito. Pero, ¿por qué hacer esas cosas cuando puedes crear una aplicación que te envíe un mensaje de texto?
Vamos a crear una aplicación Ruby on Rails que haga lo siguiente:
Permite tanto nuevos suscriptores como la posibilidad de darse de baja de la lista
Extrae la respuesta a nuestra pregunta de isittheweekend.com
Envía diariamente la respuesta de nuestros datos a todos los destinatarios suscritos.
Para envolver todo junto, también vamos a crear una tarea Rake que se ejecutará todas estas tareas a la vez, y la designación de su ejecución una vez cada 24 horas.
Si lo prefiere, también puede encontrar una versión totalmente operativa de esta aplicación en GitHub.
Empecemos.
Requisitos previos
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.
Generación de la aplicación Rails
Lo primero que tenemos que hacer es crear nuestra nueva aplicación Rails. Desde tu línea de comandos ejecuta lo siguiente:
Esto crea la estructura de archivos necesaria para nuestra aplicación Rails y establece la base de datos por defecto en PostgreSQL.
Una vez hecho esto, cd en el directorio que se ha creado. Antes de instalar nuestras dependencias, añadiremos las gemas adicionales que utilizará nuestra aplicación.
Abra el código en su editor de código preferido y vaya a la sección Gemfile. Dentro del archivo Gemfile añada las siguientes gemas:
gem 'nexmo'
gem 'watir'
gem 'webdrivers', '~> 4.0'
gem 'whenever', require: false
gem 'dotenv-rails'Estamos utilizando la gema nexmo para enviar las actualizaciones por SMS, las gemas watir y webdrivers para realizar la petición HTTP a un sitio con contenido JavaScript dinámico, la gema whenever para programar la tarea Rake, y la gema dotenv-rails para gestionar las variables de entorno.
Una vez guardado el Gemfileestará listo para ejecutar bundle install desde la línea de comandos.
El siguiente paso es crear el esquema y los modelos de nuestra base de datos.
Crear el esquema y los modelos de la base de datos
Ahora que nuestra aplicación Rails está creada y tiene sus dependencias instaladas, la siguiente tarea es crear el esquema de base de datos correcto para alojar los datos que necesitaremos para hacer funcionar nuestra aplicación. Necesitamos almacenar los siguientes tipos de información:
Destinatarios: La lista de abonados con sus números de teléfono.
DiffStorage: Copias de los datos del sitio web con los que comparar para determinar si ha habido algún cambio.
Utilizaremos la herramienta generadora de Rails para crear los archivos de migración y posteriormente editaremos cada uno de ellos.
Estos comandos crearán archivos de modelo en app/models y archivos de migración en db/migrate. Antes de confirmar esos cambios en su aplicación, una vez realizadas las acciones del generador, inspeccione los archivos creados en ambos directorios para asegurarse de que son correctos.
En concreto, en los archivos de migración, debe asegurarse de que cada migración incluya t.timestampsque añade un created_at y updated_at a la tabla. También debería ver las columnas number y subscribed en el archivo de migración Recipient con los tipos string y booleanrespectivamente. Del mismo modo, debería ver una columna en el archivo de migración DiffStorage para website_data con el tipo text.
Los archivos modelo dentro de app/models deben estar vacíos, aparte de las declaraciones de clase y su herencia de ApplicationRecord.
Cuando parezca satisfactorio, es el momento de ejecutar rake db:migrate desde la línea de comandos. El comando mostrará los resultados en tu consola, y si inspeccionas el archivo db/schema.rb podrás ver el esquema que has creado inicializado dentro de la aplicación.
Por último, también tenemos que crear Messenger y Scraper pero no necesitamos migrarlos. Para ello ejecutamos de nuevo el generador Rails y le añadimos una bandera --migration=false al generador:
Ahora ha llegado el momento de definir la lógica dentro de los modelos.
Definición de los modelos
Como ya se ha mencionado, tenemos cuatro modelos responsables de áreas únicas de la aplicación:
DiffStorage: Comprueba si hay diferencias en los datos del sitio web.Recipient: Gestiona las altas y bajas de abonadosMessenger: Gestiona el envío de mensajes SMSScraper: Responsable de la búsqueda de datos en el sitio web
Definición del modelo DiffStorage
El modelo DiffStorage contendrá dos modelos de clase. Uno contendrá la URL que estamos raspando. El segundo comprobará si se han producido cambios desde la última vez que se escaneó el sitio web e invocará los siguientes pasos de la aplicación cuando se cumplan las condiciones.
En primer lugar, vamos a definir la URL en su propio método para que creemos un único lugar donde exista y pueda ser fácilmente modificada si decidimos hacerlo más tarde:
def self.url
'http://isittheweekend.com'
endA continuación, la mayor parte de este modelo vivirá dentro del método #check_last_record de la clase:
def self.check_last_record
today_answer = Scraper.call(self.url)
if DiffStorage.any?
yesterday_answer = DiffStorage.last
else
yesterday_answer = ''
end
Messenger.send_update_message(Recipient.all, yesterday_answer, today_answer)
endEl método anterior llama primero al método de la clase Scraper que iniciará el rastreo del sitio web para obtener la instantánea más reciente y asigna esos datos a la clase today_answer. A continuación, envuelve el siguiente paso dentro de una sentencia if preguntando si hay algún registro en DiffStorage. Si hay registros anteriores almacenados allí, el método toma el más reciente y lo asigna a . yesterday_answer. Si no hay registros anteriores, se asigna una cadena vacía a . yesterday_answer. Por último, envía los destinatarios y las dos variables al modelo Messenger para procesar el envío del mensaje.
Definición del modelo de rascador
El modelo Scraper modelo se encargará de hacer el trabajo de recopilar los datos de isittheweekend.com para determinar si efectivamente es fin de semana o no. El modelo tendrá cuatro métodos de clase y vamos a definir cada uno aquí:
require 'nokogiri'
require 'webdrivers/chromedriver'
require 'watir'
class Scraper < ApplicationRecord
def self.call(url)
self.get_url(url)
end
def self.get_url(url)
doc = HTTParty.get(url)
browser = Watir::Browser.new :chrome, headless: true
browser.goto(url)
parsed_page ||= Nokogiri::HTML.parse(browser.html)
answer = parsed_page.css('h1#isit').text
self.check_text(answer)
end
def self.check_text(data)
if data == '' || data == nil
puts "There was no text received from the web scrape."
exit
else
puts "There was data in the text received from the web scrape."
self.store_text(data)
end
end
def self.store_text(text)
record = DiffStorage.new
record.website_data = text
if record.save
puts "Record Updated Successfully"
end
return record
end
endCada acción dentro del acto de raspado se define en su propio método pequeño con el fin de mantener nuestras preocupaciones separadas. El método #call es el punto de entrada de la clase. Es lo que es invocado por otros métodos fuera de sí mismo. El método #get_url realiza la petición HTTP simulando una petición del navegador Chrome utilizando la librería Watir y la analiza con Nokogiri. El método #check_text comprueba si se ha obtenido algún dato. El método #store_text guarda los datos en la base de datos.
Definir el modelo de mensajería
Dentro del Messenger estará todo el código responsable de enviar la actualización diaria de SMS a los abonados. Crearemos un método que enviará el mensaje, un método que compone el texto de respuesta de fin de semana, un método que junta todo el mensaje y un método que gestiona un mensaje de confirmación si un abonado envía una solicitud de baja.
En primer lugar, el método para enviar el mensaje de actualización:
def self.send_update_message(recipients, yesterday, today)
@client = Nexmo::Client.new(
api_key: ENV['NEXMO_API_KEY'],
api_secret: ENV['NEXMO_API_SECRET']
)
puts "Sending Message to Each Recipient"
recipients.each do |recipient|
if recipient.subscribed == true
client.sms.send(
from: ENV['FROM_NUMBER'],
to: recipient.number,
text: self.weekend_message(yesterday, today)
)
puts "Sent message to #{recipient.number}"
end
end
endEl valor del parámetro text se refiere a un método de clase llamado #weekend_message. Este método compondrá la cadena para la actualización del fin de semana comprobando si hoy es igual que ayer o no:
def self.weekend_message(yesterday, today)
if today == yesterday
response = "Today is the same as yesterday, and the answer is #{today}."
elsif today =! yesterday
response = "Today is not the same as yesterday, the answer for today is #{today}."
else
response = 'Today and yesterday are both neither affirmative or positive. Are we in an alternative dimension of time and space?'
end
self.compose_message(response)
endA continuación, el método que contiene la HEREDOC con el cuerpo del mensaje:
def self.compose_message(response)
<<~HEREDOC
Hello!
It is a new day, but is it a weekend day?
#{response}
To be removed from the list please respond with "1".
HEREDOC
endPor último, el método para enviar un mensaje de confirmación de eliminación:
def self.send_removal_message(to)
@client.sms.send(
from: ENV['FROM_NUMBER'],
to: to,
text: 'You have been successfully removed.'
)
endEl último modelo que necesitamos definir antes de continuar con el siguiente paso es el modelo Recipient modelo.
Definir el modelo de destinatario
Este modelo no contiene ninguno de los métodos de su clase. La única adición que haremos a este modelo es añadir dos validaciones a los datos de los destinatarios. Estas validaciones actuarán como salvaguarda a la hora de añadir nuevos números de teléfono a la base de datos. Comprobaremos que a) efectivamente se está proporcionando un número en los datos y b) el número no es un duplicado de un registro ya existente. Para realizar estas validaciones añadimos dos líneas bajo la definición de la clase:
class Recipient < ApplicationRecord
validates :number, presence: true
validates :number, uniqueness: true
end Crear el controlador y las rutas
¡Estamos cerca de terminar la construcción de nuestra aplicación! El siguiente paso es definir las acciones del controlador que dictarán el flujo de la aplicación. En primer lugar, vamos a generar el controlador utilizando el generador Rails desde la línea de comandos:
Esto creará un nuevo archivo controlador vacío en app/controllers llamado weekend_checker_controller.rb y archivos de vista complementarios en app/views/weekend_checker. En breve añadiremos una vista de índice. En este punto, nos centraremos en el controlador.
El controlador necesita tres acciones que correspondan a tres rutas: #index, #create y #event. La ruta #index será la única vista por defecto de nuestro sitio web. Ese será el lugar donde las personas podrán suscribirse a la lista. La ruta #create será donde se procesen los nuevos Numbers. Por último, la #event será donde la aplicación reciba los datos de webhook de la SMS API, incluidas las solicitudes de eliminación, y los procese.
class WeekendCheckerController < ApplicationController
def index
end
def create
@recipient = Recipient.new(recipient_params)
if @recipient.save
flash[:notice] = "Phone number saved successfully."
else
flash[:alert] = "Form did not save. Please fix and try again."
end
redirect_to '/'
end
def event
if params[:text] == '1'
recipient = Recipient.find_by(number: params[:msisdn])
if recipient
if recipient.update(subscribed: false)
Messenger.send_removal_message(params[:msisdn])
end
end
end
puts params
head :no_content
end
private
def recipient_params
params.permit(:number, :subscribed)
end
end
Estas tres acciones del controlador necesitan tres rutas correspondientes definidas en config/routes.rb:
Rails.application.routes.draw do
get '/', to: 'weekend_checker#index'
get '/webhooks/event', to: 'weekend_checker#event'
post '/recipient/new', to: 'weekend_checker#create'
endEl penúltimo elemento para la configuración del código de nuestra aplicación es la creación de una vista básica para la ruta / ruta.
Definir la vista
Para suscribirse a la lista de SMS, crearemos una vista accesible en el nivel superior de la URL que contendrá un formulario de suscripción.
Dentro de la carpeta app/views/weekend_checker añada un archivo index.html.erb archivo. Contendrá el siguiente código:
<h2>Is It The Weekend? Get a Daily Text to Find Out!</h2>
<p>
This is a free service that will analyze <a href="http://isittheweekend.com">isittheweekend.com</a> and check for any updates once a day. If there is an update it will send you a text message at the number you provide.
</p>
<p>
To remove yourself from the SMS list, reply to the text message you receive with the number "1".
</p>
<p>
SMS messages are sent using the <a href="/home">Nexmo SMS API</a>.
</p>
<% flash.each do |type, msg| %>
<div>
<%= msg %>
</div>
<% end %>
<%= form_with model: @recipient, url: "/recipient/new" do |f| %>
<%= f.telephone_field :number, :placeholder => '12122222222' %>
<%= f.hidden_field :subscribed, value: true %>
<%= f.submit "Add Number" %>
<% end %>La última tarea de codificación que tenemos que hacer es configurar nuestra nueva tarea Rake que ejecutará todo este código y configurar la gema whenever para ejecutar la tarea Rake una vez al día.
Crear la tarea Rastrillo y programarla
Una vez más, utilizaremos un generador Rails desde la línea de comandos para crear el fichero para nuestra tarea Rake. Desde la línea de comandos ejecuta lo siguiente:
La tarea anterior creará un archivo en lib/tasks llamado scraper.rake. Cuando lo abramos dentro de nuestro editor de código tendrá este aspecto:
namespace :scraper do
desc "TODO"
task :check_site_update => :environment do
end
end
Redefinamos el desc con una breve cadena de lo que hará esta tarea: desc "Check Website for Any Updates". A continuación, dentro del bloque task añadimos el método DiffStorage#check_last_record que es el punto de entrada para todo el trabajo que hemos creado anteriormente:
namespace :scraper do
desc "Check Website for Any Updates"
task :check_site_update => :environment do
DiffStorage.check_last_record
end
end
Ahora que nuestra tarea Rake se define, por último tenemos que inicializar el whenever y hacerle saber que queremos que esta tarea se ejecute una vez al día. Para ello, en primer lugar, se ejecuta el comando inicializador de la gema desde la línea de comandos:
El comando anterior crea un archivo schedule.rb dentro de la carpeta config/ carpeta. Añade el siguiente código a ese archivo para ejecutar la tarea scraper:check_site_update tarea diariamente:
every 1.day do
rake "scraper:check_site_update"
endAhora que la programación está creada, tenemos que actualizar el archivo crontab en nuestra máquina para que conozca este nuevo trabajo. Para ello, ejecutamos bundle exec whenever --update-crontab desde la línea de comandos. Una vez hecho esto, la tarea está completamente inicializada y configurada para ejecutarse una vez al día en nuestra máquina.
El código de nuestra aplicación está listo. Lo único que falta ahora es crear nuestra Account de Nexmo, obtener nuestras credenciales de la API de Nexmo y aprovisionar un número de teléfono virtual con el que enviar los mensajes de texto diarios. Una vez tengamos esta información la añadiremos a nuestra aplicación como variables de entorno.
Credenciales y número de teléfono de la API de Nexmo
Para crear una Account navegue hasta el Panel Nexmo y complete los pasos de registro. Una vez que haya terminado de registrarse, entrará en su panel de control.
Si no lo ha hecho antes, cree un archivo .env en el directorio de nivel superior de su aplicación y añada sus archivos NEXMO_API_KEY y NEXMO_API_SECRET al mismo. Los valores correspondientes se encuentran en la parte superior de la página del cuadro de mandos, bajo el encabezado Your API credentials encabezado.
NEXMO_API_KEY=
NEXMO_API_SECRET=La siguiente tarea que tenemos que hacer dentro del salpicadero es aprovisionar un número de teléfono. Después de hacer clic en el enlace Numbers en la barra lateral de navegación, aparecerá un menú desplegable. Una vez seleccionada la opción Buy numbers y haga clic en el botón Search verás una lista de posibles números a adquirir.
Cuando busque números por función, país y tipo, se recomienda seleccionar el país en el que se encuentran sus usuarios, SMS para las características y Mobile para el tipo.
Tras pulsar el botón naranja Buy del número que desea comprar, puede añadir ese número a su archivo .env como una nueva variable llamada FROM_NUMBER:
NEXMO_API_KEY=
NEXMO_API_SECRET=
FROM_NUMBER=El último elemento que tenemos que hacer dentro de nuestro panel de control es proporcionar una URL accesible externamente como el webhook de eventos para el número de teléfono. Para fines de desarrollo, ngrok es una buena herramienta, y puedes seguir esta guía sobre cómo poner en marcha con él.
En el panel Numbers desplegable de navegación de la barra lateral, una vez que seleccione Your numbers verá su número de teléfono recién aprovisionado en una presentación de lista. Después de hacer clic en el icono de engranaje para gestionar sus propiedades, se le mostrará un menú de diálogo de configuración.
En el ejemplo de la captura de pantalla anterior, sustituiría el campo de texto Inbound Webhook URL por su propia URL que termina en /webhooks/event.
Ya está. Nuestro código está finalizado y nuestras credenciales Nexmo están listas. En este punto, te enfrentas a una elección para ejecutar tu aplicación. Puedes ejecutarla localmente o puedes desplegarla en un proveedor de alojamiento externo, como Heroku. En el paso final, vamos a discutir cómo ejecutarlo localmente.
Si está interesado en desplegarlo para una solución a más largo plazo, puede visitar el repositorio de GitHub y hacer clic en el botón Deploy to Heroku en la parte superior del LÉAME para iniciar el proceso.
Ejecutar la aplicación
Ya estamos listos para ejecutar nuestra nueva aplicación. Para poder ejecutarla localmente, el webhook de eventos de Rails tiene que ser accesible al mundo exterior fuera de nuestro entorno local. Por ejemplo, si estamos utilizando ngrok después de seguir esta guía tanto la aplicación Rails como ngrok deben estar ejecutándose simultáneamente.
Para iniciar la aplicación Rails ejecute lo siguiente desde su línea de comandos:
A continuación, puede navegar en el navegador de su elección a localhost:3000. Verás el formulario de registro que has creado. Rellénalo con tu número de teléfono y envíalo. Ahora, una vez que la tarea Rake se ejecute, deberías esperar recibir un SMS informándote si es fin de semana y si hoy es diferente a ayer.
Próximos pasos
La aplicación que construimos, aunque caprichosa, demuestra el potencial de aprovechar el web scraping y los SMS para crear una aplicación que envíe actualizaciones a los suscriptores. Existen innumerables posibilidades para una aplicación como ésta. Tanto si estás interesado en replicar este escenario exacto como en portar el código para tu propio caso de uso, hay mucho más que explorar sobre este tema.
Para más información sobre otras posibilidades de SMS, consulta los siguientes recursos:
Compartir:
Ben es un desarrollador de segunda carrera que anteriormente pasó una década en los campos de la educación de adultos, la organización comunitaria y la gestión de organizaciones sin ánimo de lucro. Trabajó como defensor de los desarrolladores para Vonage. Escribe regularmente sobre la intersección entre el desarrollo comunitario y la tecnología. Originario del sur de California y residente durante mucho tiempo en Nueva York, Ben reside ahora cerca de Tel Aviv (Israel).
