Diario de Python | #8. Issuu PDF Downloader
Tags: python, spanish, devjournal
Published: 2023-02-15 00:38:08

Estaba buscando ideas para seguir mejorando Color Choice, hasta que me enteré que la Replay seguía viva. Para los que no la conozcan, se trata de una revista de videojuegos retro con un contenido de calidad muy interesante, la cual está disponible en una plataforma de lectura llamada [Issuu](https://issuu.com/).

## ¡Las quiero todas!
Al día de la publicación de este artículo, tienen unos 35 o 40 números publicados. Decidí que quería bajarlas a mi Ipad, pero digamos que (salvo que el mismo publisher lo permita) no se pueden descargar desde Issuu.

Por lo cual, busqué algo como "Issuu PDF Downloader Online" y encontré [este sitio](https://issuu.pdf-downloader.com/) para bajarlas, que trae todas las imágenes y las convierte a PDF.

## El problema
Simplemente tenía que copiar **cada URL** de cada revista, lo cual era un proceso repetitivo que constaba de 40 iteraciones. Todo esto, para las revistas de Replay. 

> Imaginen si buscaba descargarme 100 números de algún otro publisher.

Luego recordé que soy desarrollador, y mi obligación moral consiste en automatizar cosas.

## Hora de copiarse de la gente honesta
No quería trabajar a sudor y lágrima en una solución desarrollada de cero, así que decidí copiarme de la gente.

Quería hacerlo en Python para seguir fortaleciendo mis skills, así que busqué un repositorio que cumpliera mis expectativas. Encontré esto:

{% embed https://github.com/jpyamamoto/Issuu-PDF-Downloader %}

Pero como todo problema de desarrollo, nunca se encuentra una solución mágica. A veces parece que alguien ya pensó en tus inconvenientes, pero quizá estés en un terreno más personalizado (y ambicioso) del que pensabas.

## Hora de contribuir a la sociedad
En primer lugar, hice un fork del repo, porque sabía que podía mejorar muchas cosas:

{% embed https://github.com/maxwellnewage/Issuu-PDF-Downloader %}

Luego evalué que me ofrecía el mismo:
- Se pueden descargar revistas, pero la librería está rota.
- Tampoco tenía un requirements.txt, así que no podías importar automáticamente todas las dependencias necesarias.
- Pedía una URL por consola, lo mismo que en la web, pero esta vez tenía que ejecutar el programa por cada enlace, un desastre.
- La nomenclatura no era muy standard, y había muchas variables con nombres raros.

La verdad lo tenía difícil: el programa no compilaba por dependencias. Hice un par de búsquedas, las reparé, pero luego las librerías tenían métodos distintos o con parámetros nuevos. Se me estaba rompiendo por todos lados. Estuve varios días dándole vueltas, pero logré hacerlo andar.

## He domado a la bestia
En primer lugar, yo quería algo automatizado, así que armé una lista de URLs, en vez de una sola por un input de consola. Pero, para hacerlo más dinámico, decidí armar un json que se pueda llenar de esos enlaces y luego se convierta en una lista de diccionarios en Python:

```python
def get_issuu_json_as_dict():
    issuu_url_json = os.path.join('./json/', 'issuu_urls.json')
    issuu_url_file = open(issuu_url_json, "r").read()
    return json.loads(issuu_url_file)
```

Entonces, la persona que quisiera utilizar mi aplicación, simplemente podría crear issuu_urls.json en la carpeta json y poner su lista de revistas en este formato:

```json
[
    {
        "name": "name of the pdf",
        "url": "issuu url"
    }
]
```

Por otro lado, lo primero que hacía el algoritmo viejo era bajar todas las imágenes y luego convertirlas en PDF (como el sitio que encontré más arriba). El problema era que las imágenes se almacenaban en la raíz del proyecto, y no se eliminaban nunca.

Lo solucioné en downloader.py:

```python
def downloader(url):
    print('Started download process...')
    # List to create the PDF
    files = []
    formatter = url.replace('page_1.jpg', '')
    page_counter = 1
    while True:
        url_page = formatter + 'page_' + str(page_counter) + '.jpg'
        filename = str(page_counter) + '.jpg'
        if exists(url_page):
            # Save image directory
            filename_with_folder = os.path.join(IMAGE_DIR, filename)
            # Download images
            opener = open(filename_with_folder, 'wb')
            opener.write(urllib.request.urlopen(url_page).read())
            # Save images
            opener.close()
            print(f'Correctly saved file {filename} From URI: {url_page}')
            # Add filename to list, so we make the pdf later
            files.append(filename_with_folder)
            # Go for the next one
            page_counter += 1
        else:
            # No more images
            break

    print('Completed download process...')
    # Time to create the pdf
    return files
```

la constante _IMAGE_DIR_ contiene el valor "./images/", osea una carpeta de imágenes. Luego de que cada imagen se guarde ahí y se convierta en pdf, se corre un método llamado _delete_old_images()_, el cual limpia todos los archivos al final de la ejecución.

```python
def delete_old_images():
    for image in os.listdir(IMAGE_DIR):
        if image.endswith(".jpg"):
            os.remove(os.path.join(IMAGE_DIR, image))
```

Tuve que hacerlo de esa manera porque si intentaba limpiar por cada PDF creado, me arrojaba una excepción que indicaba que el archivo que quería eliminar estaba siendo utilizado. Muy raro.

Por cierto, para crear los PDFs utilicé una librería llamada [PyFPDF](https://pyfpdf.readthedocs.io/en/latest/), la cual permite crearlos de cero; incluso leer un documento y extraer información del mismo.

## Detalles, de los que no te gustan
Mi fork es plenamente funcional. Le di mucho amor, e incluso dejé una documentación para que puedas clonarlo y probarlo sin dramas en tu hermoso IDE PyCharm.

Sin embargo, me encontré con unos detalles que pueden hacer fallar su ejecución: en primer lugar, si intentas bajar los 40 PDFs (podés generar mi json corriendo el script dict_to_json_replay.py), a mediados de la iteración número 15 o 20, te va a dar un error estilo Bad Request. Esto es porque a Issuu no le agrada que estés bajando tantas imágenes de su servidor y piensa que estás intentando hacer un [ataque DDoS](https://latam.kaspersky.com/resource-center/threats/ddos-attacks). Para evitar esto, te recomiendo partir tu json en 10 enlaces por cada ejecución.

Por otro lado, esto no resuelve el bloqueo de las revistas premium: si el publisher decidió que la mitad de las páginas no estén disponibles, vas a descargar imágenes muy desenfocadas en esa parte de tu documento. Pensá que esto no es un hack, sino más bien un web crawler que busca cada página y la recompone en un PDF.

## Conclusiones
Me siento satisfecho con los resultados. Logré mi objetivo de automatizar la descarga masiva de revistas en Issuu de manera completamente legal. Los animo, simples mortales, a hacerle un fork a mi repositorio e intentar mejorar este proyecto. Estoy seguro de que pueden convertirlo en mucho más.

En las próximas entregas volveré sobre Color Choice, ese experimento social que me gustaría combinar con probabilidad y estadística.