Home Blog Movies Projects

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.

letterboxd.jpg

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)