8.3. Scraper para o site da Hemeroteca Digital de Lisboa#
Esse notebook busca coletar os dados dos periódicos digitalizados e disponíveis no site da Hemeroteca Digital de Lisboa. Os dados serão organizados em um dataframe e exportados para um arquivo csv.
Os dados coletados são:
Título: Título do periódico
Autoria: Autoria do periódico
Período: Período disponível do periódico
Link: Link para o periódico
Fichas Históricas: Link para as fichas históricas do periódico
Quantidade de PDF: Quantidade de PDFs disponíveis do periódico
Links dos PDFs: Lista com links para os PDFs disponíveis do periódico
# importar bibliotecas
from bs4 import BeautifulSoup as bs
import requests
import re
import pandas as pd
# definir lista de letras que foram o índice
letras = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J','L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U','V', 'X', 'Z']
# definir url base
url_base = 'https://hemerotecadigital.cm-lisboa.pt/Indice/Indice'
# final da url
url_final = '.htm'
# definir lista de urls completa
urls = [url_base + letra + url_final for letra in letras]
# função para criar objeto soup
def criar_soup(url):
html = requests.get(url)
soup = bs(html.content, 'html.parser')
return soup
# find all a href that ends with .htm and start with ../Periodicos or ../OBRAS
def encontrar_links(soup):
links = soup.find_all('a', href=True)
links = [link['href'] for link in links if link['href'].endswith('.htm') and (link['href'].startswith('../Periodicos') or link['href'].startswith('../OBRAS'))]
# replace ../ with https://hemerotecadigital.cm-lisboa.pt/
links = [link.replace('../', 'https://hemerotecadigital.cm-lisboa.pt/') for link in links]
return links
# loop com as urls
links_geral = []
for url in urls:
soup = criar_soup(url)
links = encontrar_links(soup)
links_geral.extend(links)
# função para pegar o título e a data de publicação
def pegar_titulo_data(soup):
titulo_data = soup.find('title').text
# encontrar data com regex: tudo entre [ e ]
# se não encontrar [ no título, retorna None
pattern = r'\['
if re.search(pattern, titulo_data) == None:
data = "Sem data"
titulo = titulo_data
else:
data = re.findall(r'\[(.*?)\]', titulo_data)
# encontrar título com regex: tudo antes de [
titulo = re.findall(r'(.*?)\[', titulo_data)
return titulo, data
# Encontrar todos os links que terminam em ".pdf"
def encontrar_pdf(soup, url):
pdf_links = soup.find_all('a', href=True)
pdf_links = [link['href'] for link in pdf_links if link['href'].endswith('.pdf') or link['href'].endswith('.PDF')]
# excluir string após última "/" em url
url = url.rsplit('/', 1)[0]
url = url + '/'
# add url to pdf_links
pdf_links = [url + link for link in pdf_links]
return pdf_links
# criar lista geral para os dados
dados_geral = []
# loop nos links
for link in links_geral:
soup = criar_soup(link)
titulo, data = pegar_titulo_data(soup)
pdf_links = encontrar_pdf(soup, link)
# se FichasHistoricas estiver no link, adicionar à lista de dados na variável fichas
fichas = "Sem Ficha Histórica"
if any("FichasHistoricas" in pdf for pdf in pdf_links):
fichas = [pdf for pdf in pdf_links if "FichasHistoricas" in pdf]
# correção do link para o pdf
fichas = fichas[0].replace(link, 'https://hemerotecadigital.cm-lisboa.pt/')
# contar o número de pdfs
num_pdfs = len(pdf_links)
# verificar se algum pdf possui a string"FichasHistoricas"
# se sim, excluir da lista e subtrair 1 do número de pdfs
if any("FichasHistoricas" in pdf for pdf in pdf_links):
pdf_links = [pdf for pdf in pdf_links if "FichasHistoricas" not in pdf]
num_pdfs = num_pdfs - 1
# criar lista com os dados
dados = [titulo, data, num_pdfs, pdf_links, fichas]
# adicionar dados à lista de dados
dados_geral.append(dados)
# criar dataframe com headers
df = pd.DataFrame(dados_geral, columns=['Título', 'Data', 'Quantidade de PDF', 'Links dos PDFs', 'Fichas Históricas'])
# transformar itens da coluna 'Título' em string
df['Título'] = df['Título'].astype(str)
# remover colchetes e aspas da coluna 'Título'
df['Título'] = df['Título'].str.replace('[', '')
df['Título'] = df['Título'].str.replace(']', '')
df['Título'] = df['Título'].str.replace("'", '')
df['Título'] = df['Título'].str.replace('"', '')
/tmp/ipykernel_36193/591487736.py:4: FutureWarning: The default value of regex will change from True to False in a future version. In addition, single character regular expressions will *not* be treated as literal strings when regex=True.
df['Título'] = df['Título'].str.replace('[', '')
/tmp/ipykernel_36193/591487736.py:5: FutureWarning: The default value of regex will change from True to False in a future version. In addition, single character regular expressions will *not* be treated as literal strings when regex=True.
df['Título'] = df['Título'].str.replace(']', '')
# transformar itens da coluna 'Data' em string
df['Data'] = df['Data'].astype(str)
# remover colchetes e aspas da coluna 'Data'
df['Data'] = df['Data'].str.replace('[', '')
df['Data'] = df['Data'].str.replace(']', '')
df['Data'] = df['Data'].str.replace("'", '')
df['Data'] = df['Data'].str.replace('"', '')
/tmp/ipykernel_36193/3931564129.py:4: FutureWarning: The default value of regex will change from True to False in a future version. In addition, single character regular expressions will *not* be treated as literal strings when regex=True.
df['Data'] = df['Data'].str.replace('[', '')
/tmp/ipykernel_36193/3931564129.py:5: FutureWarning: The default value of regex will change from True to False in a future version. In addition, single character regular expressions will *not* be treated as literal strings when regex=True.
df['Data'] = df['Data'].str.replace(']', '')
# se coluna data for vazia, preencher com "Sem data"
df['Data'] = df['Data'].replace('', 'Sem data')
Corrigindo títulos vazios
# se links_pdf não for vazio, pegar o primeiro link
if df['Links dos PDFs'].empty == False:
# se coluna 'Título' for vazia, preencher com item da coluna 'Links dos PDFs'
df.loc[df['Título'] == '', 'Título'] = df['Links dos PDFs'].str[0]
# transforma itens da coluna 'Título' em string
df['Título'] = df['Título'].astype(str)
# excluir string até "Periodicos/" ou "OBRAS/" na coluna título usando regex
df['Título'] = df['Título'].str.replace(r'.*Periodicos/', '')
df['Título'] = df['Título'].str.replace(r'.*OBRAS/', '')
# substitui tudo após o primeiro "/" por vazio
df['Título'] = df['Título'].str.replace(r'/.*', '')
/tmp/ipykernel_36193/3298843525.py:2: FutureWarning: The default value of regex will change from True to False in a future version.
df['Título'] = df['Título'].str.replace(r'.*Periodicos/', '')
/tmp/ipykernel_36193/3298843525.py:3: FutureWarning: The default value of regex will change from True to False in a future version.
df['Título'] = df['Título'].str.replace(r'.*OBRAS/', '')
/tmp/ipykernel_36193/3298843525.py:5: FutureWarning: The default value of regex will change from True to False in a future version.
df['Título'] = df['Título'].str.replace(r'/.*', '')
Excluir itens duplicados
# se valor na coluna 'Título' for repetido, exclui linha
df = df.drop_duplicates(subset=['Título'], keep='first')
Limpando as datas
# tente encontrar '\d{4}-\d{4}' na coluna 'Data' senão encontrar busca por '\d{4}'
df['Anos'] = df['Data'].str.findall(r'\d{4}-\d{4}|\d{4}')
# deletar '\d{4}-\d{4}|\d{4}' da coluna 'Data'
df['Data'] = df['Data'].str.replace(r'\d{4}-\d{4}|\d{4}', '')
# deletar ';', '?', da coluna 'Data'
df['Data'] = df['Data'].str.replace(r';|\?', '')
# deletar 'Sem data' da coluna 'Data'
df['Data'] = df['Data'].str.replace(r'Sem data', '')
# deletar espaços em branco da coluna 'Data'
df['Data'] = df['Data'].str.strip()
# adicionar 'Sem local' à coluna 'Data' se estiver vazia
df['Data'] = df['Data'].replace('', 'Sem local')
# renomear coluna 'Data' para 'Local'
df = df.rename(columns={'Data': 'Local'})
/tmp/ipykernel_36193/3049931291.py:2: FutureWarning: The default value of regex will change from True to False in a future version.
df['Data'] = df['Data'].str.replace(r'\d{4}-\d{4}|\d{4}', '')
/tmp/ipykernel_36193/3049931291.py:4: FutureWarning: The default value of regex will change from True to False in a future version.
df['Data'] = df['Data'].str.replace(r';|\?', '')
# reorganizar colunas
df = df[['Título', 'Anos', 'Local', 'Fichas Históricas', 'Quantidade de PDF', 'Links dos PDFs']]
# exportar locais únicos para csv
df['Local'].value_counts().sort_values(ascending=False).to_csv('data/locais_hemeroteca_lisboa_raw.csv')
# manualmente corri os locais e criei o csv com os locais corretos
# leia o csv com os locais
locais = pd.read_csv('data/locais_hemeroteca_lisboa.csv', header=None)
# print o datafram locais
print(locais)
0 1
0 Sem local Sem local
1 Lisboa, Lisboa
2 Coimbra, Coimbra
3 Rio de Janeiro, Rio de Janeiro
4 Porto, Porto
5 , Sem local
6 Indice Sem local
7 Série 2 e 3, Lisboa, Lisboa
8 I e II Séries, Lisboa, Lisboa
9 Novembro Sem local
10 Lisboa e Rio de Janeiro, Lisboa e Rio de Janeiro
11 Porto, ) Porto
12 Praia, Praia
13 25 de Abril a 2 de Maio de Sem local
14 N.º 629 ao n.º 714 Sem local
15 Lourenço Marques, Lourenço Marques
16 Nova Goa, Nova Goa
17 29 Dez. Sem local
18 Outubro Sem local
19 N.º 1 ao n.º 52 | Sem local
20 Paris, Paris
21 Rio de Janeiro, 13 Maio Rio de Janeiro
22 Lisboa, - Lisboa
23 N.º 1 | 2 de Dezembro de Sem local
24 9 Dezembro Sem local
25 I e II Séries | Sem local
26 Díli, Díli
27 1 Junho Sem local
28 Rio de Janeiro, 11 a 18 de Maio de Rio de Janeiro
29 Washington, October Washington
30 Paris, 11-18 Mai Paris
31 Paris, 17-23 Avril Paris
32 Luanda, 15 de Agosto de Luanda
33 Abril | N.º ao n.º Sem local
34 N.º 1 ao n.º 35 | Sem local
35 21 Novembro Sem local
36 30 Maio Sem local
37 | N.º 1 ao n.º 28 Sem local
38 Bolama, Guiné, Nov. Bolama
39 Macau, Macau
40 Coimbra, Junho Coimbra
41 Madrid, 11 Mayo 8 Junio Madrid
42 Lisboa, - Lisboa
43 1.º Ano, Sem local
# se o valor da coluna 0 no dataframe locais for igual ao valor da coluna 'Local' no dataframe df, substitua o valor da coluna 'Local' no dataframe df pelo valor da coluna 1 no dataframe locais
for i in range(len(locais)):
df.loc[df['Local'] == locais[0][i], 'Local'] = locais[1][i]
df.head(20)
Título | Anos | Local | Fichas Históricas | Quantidade de PDF | Links dos PDFs | |
---|---|---|---|---|---|---|
0 | Academia Portuguesa | [1933] | Sem local | Sem Ficha Histórica | 11 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
1 | O Académico: publicação bi-semanal redigida po... | [] | Sem local | Sem Ficha Histórica | 1 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
2 | O academico: revista quinzenal litteraria | [1878] | Sem local | https://hemerotecadigital.cm-lisboa.pt/Periodi... | 6 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
4 | O Académico: semanário ilustrado | [1902-1903] | Sem local | https://hemerotecadigital.cm-lisboa.pt/Periodi... | 8 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
5 | AccaoColonial_NComemorativo | [1934] | Sem local | Sem Ficha Histórica | 1 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
6 | Acção realista | [1924-1926] | Sem local | Sem Ficha Histórica | 29 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
7 | La Acción, Ano 1, n.º 1 (1 de Febrero de 1908) | [] | Sem local | Sem Ficha Histórica | 1 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
8 | Actas da Câmara Municipal de Lisboa | [1974] | Sem local | Sem Ficha Histórica | 3 | [https://hemerotecadigital.cm-lisboa.pt/OBRAS/... |
9 | Actas das sessões da Câmara Municipal de Lisboa | [] | Sem local | Sem Ficha Histórica | 8 | [https://hemerotecadigital.cm-lisboa.pt/OBRAS/... |
11 | Água lustral : arte e crítica | [1913] | Coimbra | Sem Ficha Histórica | 1 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
12 | costumes | [1903-1904] | Sem local | https://hemerotecadigital.cm-lisboa.pt/Periodi... | 12 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
13 | Álbum das Glórias | [1880-1902] | Lisboa | Sem Ficha Histórica | 39 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
14 | Album dos artistas | [1956-1959] | Lisboa | Sem Ficha Histórica | 48 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
15 | Álbum dos vencidos | [1913-1914] | Lisboa | Sem Ficha Histórica | 12 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
16 | O aldrubia | [1931-1932] | Lisboa | Sem Ficha Histórica | 4 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
17 | Alma de marialvas : órgão dos Marialvas de S. ... | [1957] | Sem local | https://hemerotecadigital.cm-lisboa.pt/Periodi... | 1 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
18 | Alma Nova: quinzenário académico | [1931] | Sem local | https://hemerotecadigital.cm-lisboa.pt/Periodi... | 2 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
20 | Alma Nova | [1915-1918, 1922-1925, 1927-1929] | Sem local | https://hemerotecadigital.cm-lisboa.pt/Periodi... | 39 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
21 | Altura: Cadernos de Poesia | [1945] | Sem local | https://hemerotecadigital.cm-lisboa.pt/Periodi... | 2 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
22 | Almanach das Artes e Letras | [1874-1876] | Lisboa | Sem Ficha Histórica | 3 | [https://hemerotecadigital.cm-lisboa.pt/Period... |
# salvar dataframe em csv
df.to_csv('data/hemeroteca_lisboa.csv', index=False)