How do I get movies's section?
Well, the first version of that section was totally manual. I used a tool supplied by LetterBoxd, as you can see on the image below. I got the CSV, then some tricky process to get the org table, uploaded it and finish.
This was a nice way to get what I wanted in first instance, but being honest with you, I didn't like it. This had to be an automatic process. It was clear.
The new version that I present here was written with some IA help (ChatGPT). It's a python's script, that I call with a cronjob located in /etc/cron.d/web_page
. The content of cron job's file is the next one, the task run every 3 days at midnight (00:00):
SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 0 0 */3 * * root update_movies
I will now explain the script.
First, we are doing web scrapping, so need some libraries to do this work easier.
import requests import time import re import os from bs4 import BeautifulSoup from datetime import datetime
base_url = "https://letterboxd.com/rhyloo/films/by/date/page/" output_file = "/tmp/movies_list.txt"
We define some functions
def clean_name(name): # Remover caracteres no deseados excepto los dos puntos return re.sub(r'[^\w\s:]', '', name).strip() # Función para obtener nombres de películas def get_movie_names(page_url): try: response = requests.get(page_url) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') # Buscar contenedores de pósters poster_containers = soup.find_all('li', class_='poster-container') movie_names = [] for container in poster_containers: img_tag = container.find('img') if img_tag and 'alt' in img_tag.attrs: raw_name = img_tag['alt'] clean_movie_name = clean_name(raw_name) movie_names.append(clean_movie_name) return movie_names except Exception as e: return [] # Escritor de resultados def write_to_file(file_path, movie_names): # Si el archivo ya existe, lo borramos if os.path.exists(file_path): os.remove(file_path) # Crear el archivo y escribir las películas with open(file_path, 'w', encoding='utf-8') as file: # Usar 'w' para escribir desde el principio for name in movie_names: file.write(name + '\n') # Función para dividir las películas en filas de 3 def dividir_en_filas(peliculas): filas = [] while peliculas: fila = peliculas[:3] filas.append(fila) peliculas = peliculas[3:] return filas
page_number = 1 all_movie_names = [] while True: page_url = base_url + str(page_number) + "/" movie_names = get_movie_names(page_url) if not movie_names: # Si no hay películas, detenemos el bucle break all_movie_names.extend(movie_names) # Añadir las películas a la lista total page_number += 1 # Pausa para evitar ser bloqueado por el servidor time.sleep(10) # Escribir todas las películas al archivo write_to_file(output_file, all_movie_names)
# Definir los caracteres que se deben eliminar caracteres_a_eliminar = ' ' # Abrir el archivo .txt with open(output_file, 'r') as archivo: contenido = archivo.read() # Filtrar los caracteres no deseados contenido_filtrado = ''.join(c for c in contenido if c not in caracteres_a_eliminar) # Guardar el contenido filtrado en un nuevo archivo with open(output_file, 'w') as archivo_filtrado: archivo_filtrado.write(contenido_filtrado) # Obtener la fecha actual en el formato adecuado fecha_actual = datetime.now().strftime("%Y-%m-%d %a") # Leer las películas desde el archivo with open(output_file, 'r', encoding='utf-8') as archivo: peliculas = [linea.strip() for linea in archivo.readlines()] # Calcular el número de dígitos del número más grande en las películas max_pelicula = len(peliculas) # Asumimos que el número de películas es el número más grande num_digitos = len(str(max_pelicula)) # Número de dígitos del número más grande # Ajustar el tamaño de la columna dependiendo de los dígitos movies_size_column = f"{num_digitos}em" # Aumentamos 1em por cada dígito extra # Crear las filas de la tabla filas = dividir_en_filas(peliculas) # Generar el formato Org Mode con la fecha actual org_mode = f"""#+TITLE: MOVIES #+author: J. L. Benavides #+date: <{fecha_actual}> #+last_modified: [{fecha_actual}] #+OPTIONS: toc:nil num:nil ^:nil I am a film lover, I enjoy watching films very much. It’s a hobby I can do alone or with others. Actually I have watched more than 500 films, below are some of them. Usually I update this content, but it is a manual process, so if you want to know the exact number at this time or confirm that I have watched some movie check my account in [[https://letterboxd.com/][letterboxd]], my user is [[https://letterboxd.com/rhyloo/films/by/date/][Rhyloo]]. @@html:<div class="last-update">Last update: {fecha_actual}</div>@@ #+ATTR_HTML: :class table-movies :style "grid-template-columns: {movies_size_column} 1fr;" | <c> | <c> | <c> | <c> | <c> | <c> | | No. | Movie Name | No. | Movie Name | No. | Movie Name | |-----+--------------------------------------------------------+-----+----------------------------------------------------------------------+-----+-------------------------------------------------------------------------------------| """ # Añadir las filas de películas a la tabla numero = 1 for fila in filas: # Asegurarse de que la fila tenga al menos 3 elementos fila.extend([''] * (3 - len(fila))) # Generar las filas para la tabla en Org Mode org_mode += f"| {numero:<3} | {fila[0]:<50} |" if fila[1]: # Solo añadir la tercera columna si no está vacía org_mode += f" {numero+1:<3} | {fila[1]:<68} |" else: org_mode += f" | |" if fila[2]: # Solo añadir la tercera columna si no está vacía org_mode += f" {numero+2:<3} | {fila[2]:<80} |\n" else: org_mode += f" | |\n" # Incrementar el número para las siguientes filas numero += 3 # Guardar el resultado en un archivo .org with open(output_org, 'w', encoding='utf-8') as archivo_org: archivo_org.write(org_mode)