Crear un parser de datos de Cercanias (Renfe) en python


Automatismo en python para procesar los datos abiertos de los horarios de trenes de Cercanías (Renfe), són de caracter público, los ofrecen en versión texto plano y formato CSV.

Los Datos de Renfe

A finales de 2018, Renfe puso en marcha su portal de datos abiertos, un espacio donde compartir información e indicadores clave con la ciudadanía. El lanzamiento de este portal formaba parte de su estrategia de transformación digital para, en sus propias palabras, “poner al cliente en el centro de todos sus servicios”.  

Descargar en Horarios cercanías (fomento_transit.zip)
Opcional, listado de estaciones disponibles

Dentro del zip se encuentran los datos en tipo de archivo de texto plano y estructurados con CSV (Coma Separate Value), vaya los datos separados por un delimitador, en este caso la coma simple ,

En total hay 6 archivos

  • agency.txt nada a destacar
  • calendar.txt datos para generar un calendario de horarios
  • routes.txt datos sobre las rutas
  • stop_times horario de llegada de los trenes en cada estación
  • stops estaciones, pero mejor descargar los datos de estaciones disponibles
  • trips inteniarios de los trenes

Es importante sanear los archivos previamente, la fila de encabezados, el último identificador eliminar todos los espacios extras que tiene, que si no python se quejará de que no encuentra el header.

Diseñar y crear Base de datos

Antes de parsear los datos es apropiado saber donde deberán ir los datos, formato y que relación tienen algunos en ellos, para eso es ir abriendo los archivos y leer la primera línea donde hay los nombres de los campos, luego mirar la segunda línea para averiguar que formato tienen los datos, texto, enteros, fechas etc.

Resaltar los identificadores y mirar entre los datos si son únicos o no, para definir los índices primarios, etc.

Script SQL para generar las tablas

Conjunto de scripts SQL para generar las tablas.

CREATE TABLE `calendar` (
	`service_id` VARCHAR(10) NOT NULL COLLATE 'utf8mb4_general_ci',
	`monday` BIT(1) NOT NULL,
	`tuesday` BIT(1) NOT NULL,
	`wednesday` BIT(1) NOT NULL,
	`thursday` BIT(1) NOT NULL,
	`friday` BIT(1) NOT NULL,
	`saturday` BIT(1) NOT NULL,
	`sunday` BIT(1) NOT NULL,
	`start_date` DATE NOT NULL,
	`end_date` DATE NOT NULL,
	PRIMARY KEY (`service_id`) USING BTREE
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB;

CREATE TABLE `routes` (
	`route_id` VARCHAR(10) NOT NULL COLLATE 'utf8mb4_general_ci',
	`route_short_name` VARCHAR(8) NOT NULL COLLATE 'utf8mb4_general_ci',
	`route_long_name` VARCHAR(80) NOT NULL COLLATE 'utf8mb4_general_ci',
	`route_type` TINYINT(4) NOT NULL DEFAULT '0',
	`route_color` VARCHAR(8) NOT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci',
	`route_text_color` VARCHAR(8) NOT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci',
	PRIMARY KEY (`route_id`) USING BTREE
)
COMMENT='route_id\r\nroute_short_name\r\nroute_long_name\r\nroute_type\r\nroute_color\r\nroute_text_color                                                                     \n'
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB;

CREATE TABLE `stops` (
	`stop_id` VARCHAR(10) NOT NULL DEFAULT '' COLLATE 'utf8mb4_general_ci',
	`stop_name` VARCHAR(80) NOT NULL COLLATE 'utf8mb4_general_ci',
	`stop_lat` DECIMAL(11,7) NOT NULL DEFAULT '0.0000000',
	`stop_lon` DECIMAL(11,7) NOT NULL DEFAULT '0.0000000',
	PRIMARY KEY (`stop_id`) USING BTREE
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB;

CREATE TABLE `stop_times` (
	`trip_id` VARCHAR(20) NOT NULL COLLATE 'utf8mb4_general_ci',
	`arrival_time` TIME NOT NULL,
	`departure_time` TIME NOT NULL,
	`stop_id` VARCHAR(10) NOT NULL COLLATE 'utf8mb4_general_ci',
	`stop_sequence` SMALLINT(6) NOT NULL DEFAULT '0'
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB;

CREATE TABLE `trips` (
	`route_id` VARCHAR(10) NOT NULL COLLATE 'utf8mb4_general_ci',
	`service_id` VARCHAR(10) NOT NULL COLLATE 'utf8mb4_general_ci',
	`trip_id` VARCHAR(20) NOT NULL COLLATE 'utf8mb4_general_ci',
	`trip_headsign` VARCHAR(40) NULL DEFAULT NULL COLLATE 'utf8mb4_general_ci',
	PRIMARY KEY (`trip_id`) USING BTREE
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB;

Parsear datos de calendar.txt

Analizando el contenido del texto solo deberemos comprobar que los campos start_date y end_date que son de tipos Date se procesan correctamente (YYYYMMDD) a (YYYY-MM-DD).

def calendarParser(cnx):
	cursor = cnx.cursor()
	add_item = ("INSERT INTO calendar "
               "(service_id, monday, tuesday, wednesday, thursday, friday, saturday, sunday, start_date, end_date) "
               "VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)")

	with open('calendar.txt', newline='', encoding="utf-8") as csvfile:
		reader = csv.DictReader(csvfile)
		for row in reader:
			print(reader.line_num)
			print(row)
			data_item = (
				row['service_id'],
				row['monday'] == '1',
				row['tuesday'] == '1',
				row['wednesday'] == '1',
				row['thursday'] == '1',
				row['friday'] == '1',
				row['saturday'] == '1',
				row['sunday'] == '1',
				row['start_date'],
				row['end_date'])
			cursor.execute(add_item, data_item)
			cnx.commit()

	cursor.close()

Parsear datos de trips.txt

Se puede parsear sin tener nada a considerar.

def tripsParser(cnx):
	cursor = cnx.cursor()
	add_item = ("INSERT INTO trips "
               "(route_id, service_id, trip_id, trip_headsign) "
               "VALUES (%s, %s, %s, %s)")

	with open('trips.txt', newline='', encoding="utf-8") as csvfile:
		reader = csv.DictReader(csvfile)
		for row in reader:
			print(reader.line_num)
			data_item = (
				row['route_id'],
				row['service_id'],
				row['trip_id'],
				None)
			cursor.execute(add_item, data_item)
			cnx.commit()

	cursor.close()

Parsear datos de routes.txt

Al parsear los datos en el campo route_long_name, se deberá limpiar los doble espacio, rectificar la Ñ por ñ, y comprobar que los acentos y caracteres especiales se procesan correctamente.

def routesParser(cnx):
	cursor = cnx.cursor()
	add_item = ("INSERT INTO routes "
               "(route_id, route_short_name, route_long_name, route_type, route_color, route_text_color) "
               "VALUES (%s, %s, %s, %s, %s, %s)")

	with open('routes.txt', newline='', encoding="utf-8") as csvfile:
		reader = csv.DictReader(csvfile)
		for row in reader:
			print(reader.line_num)
			print(row)
			data_item = (
				row['route_id'],
				row['route_short_name'],
				cleanStr(row['route_long_name']),
				row['route_type'],
				row['route_color'],
				row['route_text_color'])
			cursor.execute(add_item, data_item)
			cnx.commit()

	cursor.close()

Parsear datos de stops.txt

Al parsear los datos en el campo stop_name, se deberá limpiar los doble espacios, rectificar la Ñ por ñ y comprobar que los acentos y caracteres especiales se procesan correctamente.

Para complementar datos, se puede extraer la dirección de las estaciones de listado-estaciones-completo.csv

def stopsParser(cnx):
	cursor = cnx.cursor()
	add_item = ("INSERT INTO stops "
               "(stop_id, stop_name, stop_lat, stop_lon) "
               "VALUES (%s, %s, %s, %s)")

	with open('stops.txt', newline='', encoding="utf-8") as csvfile:
		reader = csv.DictReader(csvfile)
		for row in reader:
			print(reader.line_num)
			data_item = (
				row['stop_id'],
				cleanStr(row['stop_name']),
				cleanStr(row['stop_lat']),
				cleanStr(row['stop_lon']))
			cursor.execute(add_item, data_item)
			cnx.commit()

	cursor.close()

Parsear datos de stop_times.txt

Ese es él más datos alberga en su interior, su parseo puede tardar unos cuantos minutos, ya que puede haber casi 2 millones de entradas a procesar, el campo stop_sequence, se debe limpiar que a veces hay espacios en blanco hasta llegar el salto de línea.

def stopTimesParser(cnx):
	cursor = cnx.cursor()

	add_item = ("INSERT INTO stop_times "
               "(trip_id, arrival_time, departure_time, stop_id, stop_sequence) "
               "VALUES (%s, %s, %s, %s, %s)")

	with open('stop_times.txt', newline='', encoding="utf-8") as csvfile:
		reader = csv.DictReader(csvfile)
		for row in reader:
			print(reader.line_num)
			data_item = (
				row['trip_id'],
				row['arrival_time'],
				row['departure_time'],
				row['stop_id'],
				cleanStr(row['stop_sequence']))
			cursor.execute(add_item, data_item)
			cnx.commit()

	cursor.close()

MainScript

import mysql.connector
import csv
import re

print("Cercanias file parser v1.0.")
DB_HOST = 'localhost'
DB_USER = 'root'
DB_PASSWORD = ''
DB_NAME = 'cercanias'

def cleanStr(s) :
	return re.sub("\s\s+" , " ", s).replace("Ñ", "ñ")

#Add all parser functions here

# Main script

cnx = mysql.connector.connect(user=DB_USER, password=DB_PASSWORD,
                              host=DB_HOST,
                              database=DB_NAME)

calendarParser(cnx)
routesParser(cnx)
stopsParser(cnx)
tripsParser(cnx)
stopTimesParser(cnx)

cnx.close()

Código fuente

Código fuente del procesador de datos

Anuncio publicitario

Publicado por Codelaby

Mobile DevDesigner

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

A %d blogueros les gusta esto: