Resguardo de 257 trabajos de la Fundación para la Investigación Estratégica en Bolivia (PIEB) publicados entre 1998 y 2020.
Con el anuncio del cierre del PIEB, decidí archivar sus publicaciones digitales antes que desaparezcan. Lo más cercano en su sitio web a un catálogo con metadatos y enlaces a publicaciones es esta página. Sin embargo, explorando el sitio es obvio que existen trabajos no mencionados, como por ejemplo su última publicación. Una opción sería archivar todo lo que parezca una publicación, pero la ausencia de metadatos y un catálogo navegable implica arriesgar que nadie pueda encontrar estos trabajos oportunamente en el futuro. Así que archivo, al menos por ahora, todos los trabajos mencionados en esa página. El código que escribí recorre la página de cada año, construye un listado estructurado, descarga cada publicación y la almacena en el Internet Archive. El Internet Archive es la única institución con la capacidad operativa, los incentivos correctos y la accesibilidad (archivar no cuesta nada, donate!) para ser un repositorio serio y confiable.
import requests
from bs4 import BeautifulSoup
import pandas as pd
import re
from IPython.display import display, Markdown
import internetarchive as ia
import unicodedata
from itables import init_notebook_mode
init_notebook_mode()
from itables import show
PDF_DIRECTORY = '/a/place/to/store/books/'
INTERNETARCHIVE_ACCESS = 'xxxxxxxxxxxxxxx'
INTERNETARCHIVE_SECRET = 'xxxxxxxxxxxxxxx'
"""
DESCARGA
"""
def extract_title(metadata):
"""
Devuelve el título de la publicación
"""
return ': '.join([c.string.strip() for c in metadata.select('i')[0].children if c.string != None])
def extract_metadata(metadata, year):
"""
Devuelve un diccionario pre-definido de atributos
para cada publicación.
"""
meta = {}
m = str(metadata).split('<span')
for row in m[1:]:
attr = re.findall('(?<=\">)(.*)(?=\:)', row)
if len(attr) > 0:
attr = attr[0].lower()
if 'auto' in attr:
attr = 'autor'
elif 'fecha' in attr:
attr = 'fecha'
elif 'ginas' in attr:
attr = 'páginas'
elif 'isbn' in attr:
attr = 'isbn'
else:
attr = None
if attr is not None:
value = re.findall('(?<=\<\/span\>)(.*)(?=(\<\/br|\<br|\<\/div|$))', row)
value = value[0][0].split('<')[0].strip()
meta[attr] = value
if 'autor' not in meta.keys():
meta['autor'] = 'PIEB'
if 'fecha' not in meta.keys():
meta['fecha'] = year
return meta
def extract_bookurl(pdf):
"""
Devuelve el enlace absoluto al documento
de la publicación.
"""
return 'http://www.pieb.com.bo/' + pdf.select('a')[0]['href']
def get_entries(html):
"""
Devuelve la lista de nodos de publicaciones
en el objeto BeautifulSoup.
"""
return [tr for tr in html.select('tr') if 'Título:' in tr.select('td')[0].get_text()]
def get_yearhtml(year):
"""
Descarga la página de listado de publicaciones
para un año. Devuelve un objeto BeautifulSoup.
"""
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'X-Requested-With': 'XMLHttpRequest',
'Connection': 'keep-alive',
'Referer': 'http://www.pieb.com.bo/biblioPIEB.php',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
}
url = 'http://www.pieb.com.bo/biblioPIEBa.php?ano={}'
response = requests.get(url.format(year), headers=headers)
return BeautifulSoup(response.text, 'html.parser')
def valid_filename(string):
"""
Construye una cadena de texto sanitizada para ser
usada como nombre de file o identificador.
"""
filename = 'pieb_' + unicodedata.normalize('NFKD', string).encode('ascii', 'ignore').decode('ascii').lower()
filename = re.sub(r'[^\w\s-]', '', filename.lower())
filename = re.sub(r'[-\s]+', '_', filename).strip('-_')
return filename
# Construye una tabla de todas las publicaciones en páginas anuales
# entre 1998 y 2016.
pieb = []
for year in range(1998,2017):
html = get_yearhtml(year)
entries = get_entries(html)
for entry in entries:
ble, metadata, pdf = entry.select('td')
title = extract_title(metadata)
meta = extract_metadata(metadata, year)
book = extract_bookurl(pdf)
pieb.append({**{'title':title, 'book': book}, **meta})
df = pd.DataFrame(pieb)
df['fecha'] = df.fecha.astype(str).apply(lambda x: re.findall('[0-9]+', x)[-1])
df['autor'] = df['autor'].apply(lambda x: re.sub("Editado.+", "", x).replace(':', '').strip())
df['title'] = df['title'].apply(lambda x: re.sub('\:\s$', '', x))
df['archive'] = df.title.apply(lambda x: 'https://archive.org/details/{}'.format(valid_filename(x)[:100]))
"""
SUBIDA
"""
def download(i, row):
"""
Consume una fila de la tabla de publicaciones,
descarga y almacena localmente la publicación.
"""
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'X-Requested-With': 'XMLHttpRequest',
'Connection': 'keep-alive',
'Referer': 'http://www.pieb.com.bo/biblioPIEB.php',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
}
filename = '{}{}.pdf'.format(PDF_DIRECTORY, valid_filename(row['title']))
response = requests.get(row['book'], headers=headers)
print('{} {}: {}'.format(i, response.status_code, filename))
if response.status_code == 200:
with open(filename, 'wb') as f:
f.write(response.content)
def upload(i, row):
"""
Sube publicaciones de una fila de la tabla con
sus respectivos metadatos.
"""
identifier = valid_filename(row['title'])
filename = '{}{}.pdf'.format(PDF_DIRECTORY, identifier)
metadata = {
'title': row['title'],
'mediatype': 'texts',
'creator': row['autor'],
'source': row['book'],
'date': row['fecha']
}
if row['isbn'] != None:
metadata['isbn'] = row['isbn']
up = ia.upload(identifier[:100], filename, metadata = metadata, access_key = INTERNETARCHIVE_ACCESS, secret_key = INTERNETARCHIVE_SECRET)
print('{} {}: {}'.format(i, up[0].status_code, row['title']))
def update(i, row):
"""
Actualiza metadatos en un objeto ya creado en el
Internet Archive.
"""
identifier = valid_filename(row['title'])
metadata = {
'title': row['title'],
'mediatype': 'texts',
'creator': row['autor'],
'source': row['book'],
'date': row['fecha']
}
up = ia.modify_metadata(identifier[:100], metadata, access_key = INTERNETARCHIVE_ACCESS, secret_key = INTERNETARCHIVE_SECRET)
print('{} {}: {}'.format(i, up.status_code, identifier[:100]))
# Descarga todas las publicaciones
for i, row in df.iterrows():
download(i, row)
# Sube todas las publicaciones
for i, row in df.iterrows():
upload(i, row)
En total, archivé 257 trabajos publicados entre 1998 y 2000 con un peso total de 5.2 GB, listados en esta tabla:
def draw_table(df):
dfi = df[['fecha', 'autor', 'title', 'archive']].rename(columns={'fecha': 'Año', 'title': 'Título', 'autor': 'Autor', 'archive': '🔗'})
dfi['🔗'] = dfi['🔗'].apply(lambda link: '<a href="{}">🔗</a>'.format(link))
dfi['Título'] = dfi['Título'].apply(lambda link: '<i>{}</i>'.format(link))
show(dfi,
order = [],
scrollY="900px",
scrollCollapse=True,
paging=False,
language={
'search': '🔎︎',
'processing': 'creando tabla ...',
'info': '',
'infoEmpty': '',
'infoFiltered':'_TOTAL_ publicaciones'
},
columnDefs=[
{"width": "50px", "targets": 1},
{"width": "100px", "targets": 2},
{'width': '5px', 'targets': [0,3]}
])
draw_table(df)
Año | Autor | Título | 🔗 |
---|
Loading... (need help?) |
El Internet Archive también habilitó una colección para navegar estas publicaciones más visualmente y con filtros.
def todo(tareas):
display(Markdown('<div class="todo"><p>Direcciones posibles</p><ul>{}</ul></div>'.format('\n'.join(["<li>{}</li>".format(tarea) for tarea in tareas]))))
todo([
'Extender el catálogo con publicaciones fuera de esa página. Quizás alguien mantiene un catálogo más completo de uso interno.',
'Realizar ejercicios de procesamiento de lenguaje natural con el texto de publicaciones no escaneadas. ¿Cómo cambia la forma en que se comprenden y articulan problemas de desarrollo en Bolivia durante 20 años?',
'¿Puedo construir un mejor catálogo con información dentro de las publicaciones? Digamos, exponiendo índices, citaciones y figuras donde sea posible, o construyendo un buscador para el corpus completo de texto?'
])
Direcciones posibles