🚀 Automatize sua Revisão: Extração de BibTeX em massa do Springer com Python e ChatGPT
Realizar revisões sistemáticas ou construir um repositório bibliográfico de forma eficiente pode ser um desafio, especialmente quando os repositórios acadêmicos não oferecem exportação em lote das citações. Com base nessa dor real, desenvolvi — com apoio do ChatGPT — um script completo que automatiza a extração de referências do SpringerLink via RSS e as transforma em um arquivo .bib pronto para uso no Rayyan, Zotero, Overleaf, entre outros.
Abaixo explico como funciona e como você pode executar o código em poucos minutos no Google Colab ou localmente no seu terminal.
🧠 O que o script faz?
- Acessa o RSS de busca do Springer com os filtros definidos (tema, data, idioma, tipo de publicação).
- Percorre várias páginas automaticamente (você define quantas).
- Extrai os DOIs de cada artigo publicado.
- Consulta a API do CrossRef usando content negotiation para obter o BibTeX.
- Salva tudo em um único arquivo
.bib.
✅ Como rodar no Google Colab
- Acesse o Google Colab
- Crie um novo notebook e cole o código abaixo
- Rode célula por célula
- Faça o download do arquivo
.bibgerado ao final
💻 Código completo
import requests
import xml.etree.ElementTree as ET
from tqdm import tqdm
def get_dois_from_rss(page_limit=10):
base_url = "https://link.springer.com/search.rss"
query_params = {
"new-search": "true",
"query": '( "process management" OR "business process management" ) AND ( "artificial intelligence" OR "AI" OR "machine learning" OR "deep learning" OR "neural networks" OR "generative AI" )',
"content-type": ["Conference Paper", "Article", "Research"],
"language": "En",
"date": "custom",
"dateFrom": "2020",
"dateTo": "2025",
"sortBy": "relevance"
}
all_dois = []
for page in range(1, page_limit + 1):
query_params["page"] = str(page)
print(f"🔄 Buscando página {page}...")
try:
response = requests.get(base_url, params=query_params)
if response.status_code != 200:
print(f"⚠️ Falha ao acessar página {page}: {response.status_code}")
break
root = ET.fromstring(response.content)
items = root.findall(".//item")
if not items:
print("🚫 Nenhum item encontrado. Parando paginação.")
break
for item in items:
guid = item.find("guid")
if guid is not None and guid.text.startswith("10."):
print(f"✅ DOI encontrado: {guid.text}")
all_dois.append(guid.text)
except Exception as e:
print(f"❌ Erro na página {page}: {e}")
break
return all_dois
def fetch_bibtex(dois):
bibtex_entries = []
headers = {"Accept": "application/x-bibtex"}
for doi in tqdm(dois, desc="🔃 Baixando BibTeX via CrossRef"):
try:
r = requests.get(f"https://doi.org/{doi}", headers=headers, timeout=10)
if r.status_code == 200:
bibtex_entries.append(r.text)
except Exception as e:
print(f"Erro ao buscar BibTeX para DOI {doi}: {e}")
return bibtex_entries
# 🔧 Executar
dois_coletados = get_dois_from_rss(page_limit=20)
bibtex_data = fetch_bibtex(dois_coletados)
# 💾 Salvar
with open("springer_all_pages.bib", "w", encoding="utf-8") as f:
for entry in bibtex_data:
f.write(entry + "\n\n")
print(f"\n✅ Arquivo gerado: springer_all_pages.bib ({len(bibtex_data)} entradas)")
📌 Personalize conforme sua busca
Você pode alterar facilmente:
- O termo da
query(ex:"process mining"ou"knowledge graph") - O intervalo de tempo (
dateFrom,dateTo) - O número de páginas (
page_limit=20)
📎 Resultado
Ao final da execução, você terá um arquivo chamado:
springer_all_pages.bib
Pronto para subir no Rayyan, importar no Zotero ou usar em seu projeto em LaTeX.
🧠 Por que usar esse script?
- Evita o trabalho manual de baixar artigo por artigo.
- Garante alta confiabilidade (dados vindos direto da CrossRef).
- Ideal para quem faz revisão sistemática, mapeamento ou meta-análise.
Se quiser adaptar o script para outras bases com RSS, como IEEE Xplore ou PubMed, também é possível. E claro, o ChatGPT pode te ajudar nisso também. 😉
Nova versão que extrai com abstract
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import xml.etree.ElementTree as ET
from tqdm import tqdm
import json
import re
def get_rss_from_springer(page_limit=10):
"""Extrai RSS do Springer página por página"""
base_url = "https://link.springer.com/search.rss"
query_params = {
"new-search": "true",
"query": '''
("implicit dependencies" OR "hidden dependencies" OR "undocumented dependencies" OR "implicit relationships" OR "hidden relationships" OR "activity relationships"
OR "process relationships" OR "tacit dependencies" OR "emergent dependencies") AND ("business process*" OR "organizational process*" OR "process model*"
OR "process redesign" OR "process reengineering" OR "workflow*" OR "BPMN")
''',
"content-type": ["Conference Paper", "Article", "Research"],
"language": "En",
"date": "custom",
"dateFrom": "2020",
"dateTo": "2025",
"sortBy": "relevance"
}
all_items = []
for page in range(1, page_limit + 1):
query_params["page"] = str(page)
print(f"🔄 Página {page}...")
try:
response = requests.get(base_url, params=query_params, timeout=15)
if response.status_code != 200:
print(f"⚠️ Erro: {response.status_code}")
break
root = ET.fromstring(response.content)
items = root.findall(".//item")
if not items:
print("🚫 Sem mais itens. Parando.")
break
all_items.extend(items)
print(f"✅ {len(items)} artigos")
except Exception as e:
print(f"❌ Erro: {e}")
break
return all_items
def extract_article_data(item):
"""Extrai: título, DOI, abstract da tag <description>"""
data = {}
title = item.find("title")
data['title'] = title.text if title is not None else "N/A"
guid = item.find("guid")
data['doi'] = guid.text if guid is not None else "N/A"
desc = item.find("description")
if desc is not None:
parts = []
if desc.text:
parts.append(desc.text)
for child in desc:
if child.text:
parts.append(child.text)
if child.tail:
parts.append(child.tail)
abstract = ' '.join(parts)
abstract = re.sub(r'<[^>]+>', '', abstract)
abstract = re.sub(r'\s+', ' ', abstract).strip()
data['abstract'] = abstract
else:
data['abstract'] = ""
print(f"DEBUG - Abstract extraído para '{data['title']}...': {data['abstract']}...")
link = item.find("link")
data['link'] = link.text if link is not None else "N/A"
return data
def fetch_crossref_data(doi):
"""Busca dados do CrossRef para enriquecer o BibTeX"""
try:
url = f"https://api.crossref.org/works/{doi}"
response = requests.get(url, timeout=10)
if response.status_code == 200:
return response.json()
return None
except:
return None
def extract_from_crossref(metadata):
"""Extrai campos úteis do CrossRef"""
data = {
'authors': [],
'journal': '',
'volume': '',
'issue': '',
'pages': '',
'year': '',
'abstract': ''
}
if not metadata or 'message' not in metadata:
return data
msg = metadata['message']
try:
for author in msg.get('author', []):
name_parts = []
if 'given' in author:
name_parts.append(author['given'])
if 'family' in author:
name_parts.append(author['family'])
if name_parts:
data['authors'].append(' '.join(name_parts))
except:
pass
try:
container = msg.get('container-title', [])
data['journal'] = container[0] if isinstance(container, list) else container
except:
pass
data['volume'] = msg.get('volume', '')
data['issue'] = msg.get('issue', '')
data['pages'] = msg.get('page', '')
try:
if 'issued' in msg and 'date-parts' in msg['issued']:
date_parts = msg['issued']['date-parts'][0]
data['year'] = str(date_parts[0]) if date_parts else ''
except:
pass
data['abstract'] = msg.get('abstract', '')
return data
def generate_bibtex(article, crossref_data):
"""Gera entrada BibTeX completa"""
key = article['doi'].replace('/', '_').replace('.', '_')
bibtex = f"@article{{{key},\n"
bibtex += f" title = {{{article['title']}}},\n"
if crossref_data and crossref_data['authors']:
bibtex += f" author = {{{' and '.join(crossref_data['authors'])}}},\n"
if crossref_data and crossref_data['journal']:
bibtex += f" journal = {{{crossref_data['journal']}}},\n"
if crossref_data and crossref_data['volume']:
bibtex += f" volume = {{{crossref_data['volume']}}},\n"
if crossref_data and crossref_data['issue']:
bibtex += f" issue = {{{crossref_data['issue']}}},\n"
if crossref_data and crossref_data['pages']:
bibtex += f" pages = {{{crossref_data['pages']}}},\n"
if crossref_data and crossref_data['year']:
bibtex += f" year = {{{crossref_data['year']}}},\n"
bibtex += f" doi = {{{article['doi']}}},\n"
bibtex += f" url = {{{article['link']}}},\n"
abstract = article['abstract']
if not abstract and crossref_data:
abstract = crossref_data['abstract']
if abstract:
abstract_clean = abstract.replace('"', '\\"').replace('\n', ' ')
bibtex += f" abstract = {{{abstract_clean}}}\n"
else:
bibtex = bibtex.rstrip(',\n') + "\n"
bibtex += "}\n"
return bibtex
def main():
print("\n" + "="*80)
print("🔍 SPRINGER RSS → BIBTEX COM ABSTRACTS")
print("="*80 + "\n")
print("📋 Extraindo RSS...\n")
items = get_rss_from_springer(page_limit=65)
if not items:
print("❌ Nenhum artigo encontrado")
return
print(f"\n✅ Total: {len(items)} artigos\n")
print("🔍 Processando artigos...\n")
articles = [extract_article_data(item) for item in items]
print("📡 Buscando CrossRef e gerando BibTeX...\n")
bibtex_list = []
all_data = []
for article in tqdm(articles, desc=""):
metadata = fetch_crossref_data(article['doi'])
crossref_data = extract_from_crossref(metadata) if metadata else {}
bibtex = generate_bibtex(article, crossref_data)
bibtex_list.append(bibtex)
article['crossref'] = crossref_data
article['bibtex'] = bibtex
all_data.append(article)
print("\n💾 Salvando...\n")
with open("springer_articles.bib", "w", encoding="utf-8") as f:
f.write("\n\n".join(bibtex_list))
print(f"✅ springer_articles.bib ({len(bibtex_list)} entradas)")
with open("springer_articles.json", "w", encoding="utf-8") as f:
json.dump(all_data, f, indent=2, ensure_ascii=False)
print(f"✅ springer_articles.json")
print("\n" + "="*80)
print("📊 RESUMO")
print("="*80 + "\n")
for i, article in enumerate(all_data[:5], 1):
print(f"{i}. {article['title'][:60]}...")
print(f" DOI: {article['doi']}")
if article['abstract']:
print(f" Abstract: {article['abstract'][:80]}...")
print()
if len(all_data) > 5:
print(f"... +{len(all_data)-5} artigos\n")
print("="*80)
print(f"✅ Processados: {len(all_data)}")
print(f"✅ Com abstract: {sum(1 for a in all_data if a['abstract'])}")
print("="*80)
if __name__ == "__main__":
main()
Top comments (0)