Anuncios

Bienvenidos sean a este post, hoy veremos como trabajar con la parte de los scripts sobre nuestro proyecto.

Anuncios

Aqui comenzaremos el desarrollo del script que usaremos para extraer informacion (scrape) de una URL infomada y esta constara de tres partes:

  • Los modulos a importar
  • El analisis de los argumentos
  • La logica del mismo
Anuncios

Para ello primero vamos a crear un archivo que llamaremos scrape.py y lo almacenaremos en el directorio llamado ProyectoWeb, veamos la primera parte.

Anuncios

Importando modulos

Aqui hablaremos sobre los modulos que necesitaremos para poder trabajar, la mayoria ya estan preinstalados en nuestro lenguaje como librerias internas pero hay uno en particular que no, por ello primero procederemos a instalarlo mediante pip con el siguiente comando:

$ python3 -m pip install beautifulsoup4
Anuncios

Este nos instalara el modulo que hara realmente la magia porque es el que se dedica a extraer informacion de las paginas webs que informemos, con esto agreguemos las siguientes lineas en nuestro archivo:

scrape.py

import argparse
import base64
import json
import os
from bs4 import BeautifulSoup
import requests
Anuncios
Anuncios

El primero sera para la siguiente parte del script como es el analisis de los argumentos por medio de argparse, luego tenemos un modulo para almacenar los archivos descargados dentro de un json, para ello entra en accion base64, el siguiente es para crear el json donde trabajaremos, despues tenemos el que usaremos para guardar los archivos en el disco (os), el siguiente paso sera la importacion de la clase que como mencionamos anteriormente sera la encargada de hacer la verdadera magia y por ultimo tenemos a requests para poder trabajar con html, tal como vimos en este post, pero este sera el encargado de permitirnos recuperar la informacion desde una URL informada, con esto tenemos todos los elementos necesarios para nuestro script pasemos al sigueinte tema.

Anuncios

Analizando los argumentos

En la seccion anterior agregamos todos los elementos que necesitaremos en esta seccion nos centraremos en informar los argumentos que podemos recibir para trabajar, para ello agregaremos el siguiente bloque en el codigo:

if __name__ == "__main__":
	parser = argparse.ArgumentParser(
		description='Scrapear una pagina web')
	parser.add_argument(
		'-t',
		'--type',
		choices=['all','png','jpg'],
		default='all',
		help='El Tipo de imagen a extraer')
	parser.add_argument(
		'-f',
		'--format',
		choices=['img','json'],
		default='img',
		help='El formato de las imagenes a salvar.')
	parser.add_argument(
		'url',
		help='El URL que queremos investigar')
	args = parser.parse_args()
	scrape(args.url, args.format, args.type)
Anuncios
Anuncios

Aqui chequearemos si estamos en el modulo principal o main, en caso de ser verdadero crearemos un objeto llamado parser y que usara el ArgumentParser para crear el objeto que contendra toda la informacion necesaria para convertir los argumentos en objetos para python, observen que lo primero que creamos es una descripcion para informar lo que haremos, luego tomamos el parser y le agregamos un argumento, en este caso el -t y el –type para recibir el tipo de imagen que debemos buscar y extraer, tenemos tres posibilidades y la opcion predeterminada sera all, es decir todos, por ultimo tenemos un help para informar que hace el argumento, seguido de esto agregamos otro argumento pero esta vez para el formato de como guardaremos las imagenes, pasamos los dos identificadores en la linea de comando, -t y –type, y en este caso sera para guardarlos como imagenes o en un JSON, tambien tenemos un valor predeterminado y una descripcion de ayuda, por ultimo agregamos un argumento mas para indicar la direccion web o URL, con todo estos argumentos agregados creamos otro objeto llamado args donde usaremos parse_args para analizar todos los argumentos antes agregados, para finalmente pasar el nombre de la funcion en conjunto con todos los argumentos antes agregados y con esto finalizamos esta seccion, veamos que sucede cuando lo probamos con la opcion -h:

tinchicus@dbn001vrt:~/lenguajes/python/ProyectoWeb$ python3 scrape.py -h
usage: scrape.py [-h] [-t {all,png,jpg}] [-f {img,json}] url

Scrapear una pagina web

positional arguments:
  url                   El URL que queremos investigar

optional arguments:
  -h, --help            show this help message and exit
  -t {all,png,jpg}, --type {all,png,jpg}
                        El Tipo de imagen a extraer
  -f {img,json}, --format {img,json}
                        El formato de las imagenes a salvar.
tinchicus@dbn001vrt:~/lenguajes/python/ProyectoWeb$
Anuncios

Vean como automaticamente nos separo cuales son los argumentos obligatorios (url) y los opcionales dado que tiene valores predeterminados que nos permite pasarlos de largo, tambien nos agrego la opcion -h y –help para ver esta ayuda, nos muestra como es su sintaxis sin necesidad de tener que crear todo esto por nosotros, con esto solo nos falta el siguiente tema.

Anuncios

La logica de todo

Estas seran las distintas funciones que se encargaran de procesar todo por lo tanto debemos agregarlo antes del condicional anterior, agreguemos la primera funcion:

def scrape(url, formato, tipo):
	try:
		pagina = requests.get(url)
	except requests.RequestException as err:
		print(str(err))
	else:
		sopa = BeautifulSoup(pagina.content, 'html.parser')
		imagenes = buscar_imagenes(sopa, url)
		imagenes = filtrar_imagenes(imagenes, tipo)
		guardar(imagenes, formato)
Anuncios
Anuncios

Este es la funcion principal la cual recibira los tres argumentos (url, formato, tipo) despues tenemos un bloque try donde tenemos una variable que almacenara el contenido de la pagina obtenido a traves de requests y get gracias al url informado, despues tenemos un except para una excepcion de request y en caso de haberlo sera mostrado en pantalla, por ultimo tenemos un else que hara la magia porque primero tenemos un objeto de tipo BeautifulSoup que sera el encargado de extraer toda la informacion de la pagina pasada por content y el identificador del parser, con este objeto creado y definido, pasamos a crear otro objeto llamado imagenes donde la buscaremos por medio de una funcion que definiremos luego llamado buscar_imagenes, una vez realizado esto volvemos a almacenar en imagenes el resultado de otra funcion que aun no existe llamada filtrar_imagenes que como su nombre lo indica sera para obtener solo los dos formatos elegidos, por ultimo tenemos una funcion mas para guardar las imagenes obtenidas, con esto comentado podemos pasar a agregar la siguiente funcion:

def buscar_imagenes(sopa, url_base):
	imagenes = []
	for img in sopa.findAll('img'):
		fnt = img.get('src')
		url_img = f'{url_base}/{fnt}'
		nombre = url_img.split('/')[-1]
		imagenes.append(dict(nombre=nombre, url=url_img))
	return imagenes
Anuncios
Anuncios

Esta es la encargada de buscar todas las imagenes, definiremos un diccionario vacio llamado imagenes, por lo tanto recibira todo lo definido en el objeto llamado sopa, y la url que sera de base o mejor dicha la original, luego usaremos un bucle for donde pasara por todo el contenido pero le diremos que solamente encuentre el contenido img, con esto tendremos cada tag del tipo img en la pagina, luego crearemos una variable llamada fnt que guardara el contenido de src de cada tag img, luego crearemos otra variable que almacenara el url completo de la imagen, dado que pasaremos la url base y el almacenado en fnt, luego obtendremos el nombre a traves de un split de la variable anterior que comenzaremos del ultimo caracter hasta el primero, por ultimo agregaremos en imagenes los ultimos dos datos obtenidos, uno sera la clave y el otro el valor, por ultimo devolvemos el diccionario final de imagenes, pasemos a agregar la siguiente funcion:

def filtrar_imagenes(imagenes, tipo):
	if tipo == 'all':
		return imagenes
	map_ext = {
		'png':['.png'],
		'jpg':['.jpg','.jpeg'],
	}
	return [
		img for img in imagenes
		if coincidir_ext(img['nombre'],map_ext[tipo])
	]
Anuncios

Esta es la funcion encargada de filtrar las imagenes, si el tipo es igual a ‘all‘ procede a devolver todas las imagenes almacenadas en imagenes y saliendo de la funcion, de lo contrario crea un nuevo mapa donde tenemos los dos tipos posibles como claves y sus extensiones como valores, despues tenemos un return donde pasaremos por cada elemento en imagenes y si la funcion coincidir_ext devuelve un valor gracias a la combinacion del nombre del archivo y la extension informada procede a devolverlo, ahora pasaremos a hablar sobre esta funcion agregandola:

def coincidir_ext(archivo, lista_ext):
	nombre, extension = os.path.splitext(archivo.lower())
	return extension in lista_ext
Anuncios

Como dijimos esta funcion recibe dos argumentos que son el nombre del archivo y la extension de la lista informada, en este caso crearemos dos variables llamados nombre y extension y por medio de splitext dividiremos en dos al nombre del archivo donde tendremos el nombre del archivo en nombre y la extension en la otra variable y devolveremos este valor si extension esta contenido en la extension informada, con esto haremos el verdadero filtro, pasemos a agregar la siguiente funcion:

def guardar(imagenes, formato):
	if imagenes:
		if formato == 'img':
			guardar_imagenes(imagenes)
		else:
			guardar_json(imagenes)
		print('Hecho')
	else:
		print('Sin imagenes para guardar')
Anuncios

Esta sera la encargada de guardar las imagenes pero no sera la unica porque no solo recibe a las imagenes sino tambien el tipo de formato para almacenarlos, sean imagenes o json, si imagenes existe pasamos a chequear a formato si es igual a img procedemos a ejecutar guardar_imagenes pasando imagenes de lo contrario ejecutaremos guardar_json tambien pasando imagenes, una vez ejecutados y terminados notifcaremos con un mensaje, de lo contrario si imagenes no existe nos muestra un mensaje en pantalla notificando esto, pasemos a definir la primera funcion:

def guardar_imagenes(imagenes):
	for img in imagenes:
		dato_img = requests.get(img['url']).content
		with open(img['nombre'], 'wb') as f:
			f.write(dato_img)
Anuncios

Este es el mas simple porque pasara por cada objeto de imagenes y lo almacena en uno llamado img, despues creamos otro donde por medio de requests y get obtendremos todo el contenido de esta imagen y por medio de with y open lo escribiremos nuevamente con formato de bytes tal como vimos anteriormente, esto lo hara por cada imagen almacenada en imagenes, agreguemos la ultima funcion:

def guardar_json(imagenes):
	datos = {}
	for img in imagenes:
		dato_img = requests.get(img['url']).content
		dato_img_b64 = base64.b64encode(dato_img)
		dato_img_str = dato_img_b64.decode('utf-8')
		datos[img['nombre']] = dato_img_str
	with open('imagenes.json', 'w') as ijson:
		ijson.write(json.dumps(datos))
Anuncios
Anuncios

Este es un poco mas complejo pero nada que no hayamos visto hasta ahora, primero crearemos un diccionario llamado datos, nuevamente con un bucle for pasamos todos los elementos de imagenes, primero crearemos un objeto llamado dato_img y en este almacenaremos todo el contenido de la imagen, despues crearemos otra variable donde lo codificaremos con base64 el contenido antes obtenido, despues en otro objeto lo decodificaremos y lo almacenaremos con una codificacion de tipo utf-8, por ultimo definimos otro nuevo objeto donde asignaremos a la posicion nombre el dato antes decodificado, con esto realizado crearemos un nuevo archivo donde lo usaremos para escribir y en este escribiremos todo el contenido almancenado en datos gracias al dumps de json, con esto terminamos con el codigo, veamos como quedo el codigo final:

scrape.py

import argparse
import base64
import json
import os
from bs4 import BeautifulSoup
import requests

def scrape(url, formato, tipo):
	try:
		pagina = requests.get(url)
	except requests.RequestException as err:
		print(str(err))
	else:
		sopa = BeautifulSoup(pagina.content, 'html.parser')
		imagenes = buscar_imagenes(sopa, url)
		imagenes = filtrar_imagenes(imagenes, tipo)
		guardar(imagenes, formato)

def buscar_imagenes(sopa, url_base):
	imagenes = []
	for img in sopa.findAll('img'):
		fnt = img.get('src')
		url_img = f'{url_base}/{fnt}'
		nombre = url_img.split('/')[-1]
		imagenes.append(dict(nombre=nombre, url=url_img))
	return imagenes

def filtrar_imagenes(imagenes, tipo):
	if tipo == 'all':
		return imagenes
	map_ext = {
		'png':['.png'],
		'jpg':['.jpg','.jpeg'],
	}
	return [
		img for img in imagenes
		if coincidir_ext(img['nombre'],map_ext[tipo])
	]

def coincidir_ext(archivo, lista_ext):
	nombre, extension = os.path.splitext(archivo.lower())
	return extension in lista_ext

def guardar(imagenes, formato):
	if imagenes:
		if formato == 'img':
			guardar_imagenes(imagenes)
		else:
			guardar_json(imagenes)
		print('Hecho')
	else:
		print('Sin imagenes para guardar')

def guardar_imagenes(imagenes):
	for img in imagenes:
		dato_img = requests.get(img['url']).content
		with open(img['nombre'], 'wb') as f:
			f.write(dato_img)

def guardar_json(imagenes):
	datos = {}
	for img in imagenes:
		dato_img = requests.get(img['url']).content
		dato_img_b64 = base64.b64encode(dato_img)
		dato_img_str = dato_img_b64.decode('utf-8')
		datos[img['nombre']] = dato_img_str
	with open('imagenes.json', 'w') as ijson:
		ijson.write(json.dumps(datos)) 

if __name__ == "__main__":
	parser = argparse.ArgumentParser(
		description='Scrapear una pagina web')
	parser.add_argument(
		'-t',
		'--type',
		choices=['all','png','jpg'],
		default='all',
		help='El Tipo de imagen a extraer')
	parser.add_argument(
		'-f',
		'--format',
		choices=['img','json'],
		default='img',
		help='El formato de las imagenes a salvar.')
	parser.add_argument(
		'url',
		help='El URL que queremos investigar')
	args = parser.parse_args()
	scrape(args.url, args.format, args.type)
Anuncios

Repasemos para ver como quedo nuestra estructura hasta ahora:

.
├── scrape.py
└── servidor_simple
    ├── img
    │   ├── tux-alcohol.png
    │   ├── tux-argentina.png
    │   ├── tux-book.png
    │   ├── tux-ebook.png
    │   └── tux-luke.png
    ├── index.html
    └── server.sh
Anuncios

Nuestro siguiente paso sera probar el script y para ello primero deben entrar al directorio del servidor e iniciarlo por medio del archivo server.sh, una vez iniciado volvemos a la raiz del proyecto y vamos a ejecutar dos veces el script para ver como descarga las imagenes y como crea el json, para descargar las imagenes lo podemos hacer asi directamente:

$ python3 scrape.py http://localhost:8000/
Anuncios

Esto descargara todas las imagenes del servidor, el siguiente sera para el JSON:

$ python3 scrape.py --format json http://localhost:8000/
Anuncios

Tambien podriamos haber usado el -f pero me parecio mejor usar este formato, si lo verificamos tendremos este resultado:

tinchicus@dbn001vrt:~/lenguajes/python/ProyectoWeb$ ls -l
total 580
-rw-r--r-- 1 tinchicus tinchicus 327805 abr 29 23:33 imagenes.json
-rw-r--r-- 1 tinchicus tinchicus   2173 abr 29 22:57 scrape.py
drwxr-xr-x 3 tinchicus tinchicus   4096 abr 29 12:18 servidor_simple
-rw-r--r-- 1 tinchicus tinchicus  37213 abr 29 23:32 tux-alcohol.png
-rw-r--r-- 1 tinchicus tinchicus  44228 abr 29 23:32 tux-argentina.png
-rw-r--r-- 1 tinchicus tinchicus  53057 abr 29 23:32 tux-book.png
-rw-r--r-- 1 tinchicus tinchicus  39678 abr 29 23:32 tux-ebook.png
-rw-r--r-- 1 tinchicus tinchicus  71592 abr 29 23:32 tux-luke.png
tinchicus@dbn001vrt:~/lenguajes/python/ProyectoWeb$
Anuncios

Para finalizar les dejo un video para verlo completamente en accion desde que se levanta el server hasta que se genere el archivo JSON

Anuncios

En el video podemos ver como es el procedimiento para levantar el server y luego como nos notifica si no pasamos ningun parametro o el -h, y por ultimo como descarga los archivos o uno solo de tipo JSON, hasta aqui la segunda parte de nuestro proyecto.

Anuncios

En resumen, hoy hemos visto como crear un script que nos permite obtener informacion de una pagina web sin tener que trabajar sobre ella, en este caso lo usamos para las imagenes, vimos como poder crear una forma practica para manejar los argumentos del codigo cuando lo llamemos, despues vimos como es el nucleo del codigo donde se encargara de buscar las imagenes en la pagina y descargarlas o descargarla en un archivo JSON, espero les haya gustado sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Donación

Es para mantenimento del sitio, gracias!

$1.50