Skip to main content

harvard-artifacts-etl-streamlit-app

Build ETL pipelines and analytics dashboards using Harvard Art Museums API with Python, SQL, and Streamlit

Ir para a instalação

Informações da origem

Repositório
reason-machines/data-skills
Última atividade na origem
25 de junho de 2026 às 15:32
Idioma detectado do SKILL.md
inglês
Estrelas
5
Forks
1

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
harvard-artifacts-etl-streamlit-app
description
Build ETL pipelines and analytics dashboards using Harvard Art Museums API with Python, SQL, and Streamlit
triggers
["how do I fetch data from Harvard Art Museums API","build an ETL pipeline for museum artifacts","create a Streamlit dashboard for Harvard artifacts","set up SQL database for artifact metadata","query Harvard museum collection data","visualize art museum data with Plotly","implement pagination for Harvard API","design schema for artifact data"]
# Harvard Artifacts ETL & Analytics App > Skill by [ara.so](https://ara.so) — Data Skills collection. This project is an end-to-end data engineering application that demonstrates real-world ETL pipelines using the Harvard Art Museums API. It extracts artifact data, transforms it into relational structures, loads it into SQL databases, and provides interactive analytics through a Streamlit dashboard. ## What This Project Does - **API Integration**: Fetches artifact data from Harvard Art Museums API with pagination and rate limiting - **ETL Pipeline**: Transforms nested JSON into normalized relational tables - **SQL Storage**: Stores data in MySQL/TiDB with proper schema design - **Analytics**: Executes 20+ predefined SQL queries for insights - **Visualization**: Creates interactive dashboards with Plotly charts ## Installation ```bash # Clone the repository git clone https://github.com/Manali0711/Harvard-Artifacts-Collection-Data-Engineering-Analytics-App.git cd Harvard-Artifacts-Collection-Data-Engineering-Analytics-App # Install dependencies pip install -r requirements.txt # Set up environment variables export HARVARD_API_KEY="your_api_key_here" export DB_HOST="your_database_host" export DB_USER="your_db_user" export DB_PASSWORD="your_db_password" export DB_NAME="harvard_artifacts" ``` ## Configuration ### API Key Setup Get your free API key from [Harvard Art Museums API](https://harvardartmuseums.org/collections/api): ```python import os # In your code, always reference the environment variable API_KEY = os.getenv('HARVARD_API_KEY') BASE_URL = 'https://api.harvardartmuseums.org/object' ``` ### Database Configuration ```python import mysql.connector import os db_config = { 'host': os.getenv('DB_HOST'), 'user': os.getenv('DB_USER'), 'password': os.getenv('DB_PASSWORD'), 'database': os.getenv('DB_NAME') } conn = mysql.connector.connect(**db_config) ``` ## Database Schema The application uses three main tables: ```sql CREATE TABLE artifactmetadata ( objectid INT PRIMARY KEY, title VARCHAR(500), culture VARCHAR(200), century VARCHAR(100), classification VARCHAR(200), department VARCHAR(200), dated VARCHAR(200), accessionyear INT, technique VARCHAR(500), medium VARCHAR(500), verificationlevel INT, totalpageviews INT, totaluniquepageviews INT ); CREATE TABLE artifactmedia ( mediaid INT AUTO_INCREMENT PRIMARY KEY, objectid INT, baseimageurl VARCHAR(500), primaryimageurl VARCHAR(500), iiifbaseuri VARCHAR(500), FOREIGN KEY (objectid) REFERENCES artifactmetadata(objectid) ); CREATE TABLE artifactcolors ( colorid INT AUTO_INCREMENT PRIMARY KEY, objectid INT, color VARCHAR(50), spectrum VARCHAR(50), percentage FLOAT, FOREIGN KEY (objectid) REFERENCES artifactmetadata(objectid) ); ``` ## Core ETL Pipeline ### Extract: Fetch Data from API ```python import requests import os def fetch_artifacts(num_records=100, page_size=100): """ Fetch artifacts from Harvard Art Museums API with pagination """ API_KEY = os.getenv('HARVARD_API_KEY') BASE_URL = 'https://api.harvardartmuseums.org/object' all_records = [] page = 1 while len(all_records) < num_records: params = { 'apikey': API_KEY, 'size': page_size, 'page': page, 'hasimage': 1 # Only get artifacts with images } response = requests.get(BASE_URL, params=params) if response.status_code == 200: data = response.json() records = data.get('records', []) if not records: break all_records.extend(records) page += 1 else: print(f"Error: {response.status_code}") break return all_records[:num_records] ``` ### Transform: Process JSON Data ```python import pandas as pd def transform_metadata(artifacts): """ Transform artifact JSON into metadata dataframe """ metadata_list = [] for artifact in artifacts: metadata = { 'objectid': artifact.get('objectid'), 'title': artifact.get('title', 'Unknown'), 'culture': artifact.get('culture', 'Unknown'), 'century': artifact.get('century', 'Unknown'), 'classification': artifact.get('classification', 'Unknown'), 'department': artifact.get('department', 'Unknown'), 'dated': artifact.get('dated', 'Unknown'), 'accessionyear': artifact.get('accessionyear'), 'technique': artifact.get('technique', 'Unknown'), 'medium': artifact.get('medium', 'Unknown'), 'verificationlevel': artifact.get('verificationlevel', 0), 'totalpageviews': artifact.get('totalpageviews', 0), 'totaluniquepageviews': artifact.get('totaluniquepageviews', 0) } metadata_list.append(metadata) return pd.DataFrame(metadata_list) def transform_media(artifacts): """ Extract media/image information """ media_list = [] for artifact in artifacts: objectid = artifact.get('objectid') media = { 'objectid': objectid, 'baseimageurl': artifact.get('baseimageurl'), 'primaryimageurl': artifact.get('primaryimageurl'), 'iiifbaseuri': artifact.get('iiifbaseuri') } media_list.append(media) return pd.DataFrame(media_list) def transform_colors(artifacts): """ Extract color information from nested structure """ colors_list = [] for artifact in artifacts: objectid = artifact.get('objectid') colors = artifact.get('colors', []) for color_data in colors: color_record = { 'objectid': objectid, 'color': color_data.get('color'), 'spectrum': color_data.get('spectrum'), 'percentage': color_data.get('percent') } colors_list.append(color_record) return pd.DataFrame(colors_list) ``` ### Load: Insert into SQL Database ```python def load_to_database(metadata_df, media_df, colors_df, db_config): """ Batch load dataframes into SQL database """ import mysql.connector conn = mysql.connector.connect(**db_config) cursor = conn.cursor() # Load metadata metadata_tuples = [tuple(x) for x in metadata_df.to_numpy()] metadata_sql = """ INSERT INTO artifactmetadata (objectid, title, culture, century, classification, department, dated, accessionyear, technique, medium, verificationlevel, totalpageviews, totaluniquepageviews) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE objectid=objectid """ cursor.executemany(metadata_sql, metadata_tuples) # Load media media_tuples = [tuple(x) for x in media_df.to_numpy()] media_sql = """ INSERT INTO artifactmedia (objectid, baseimageurl, primaryimageurl, iiifbaseuri) VALUES (%s, %s, %s, %s) """ cursor.executemany(media_sql, media_tuples) # Load colors colors_tuples = [tuple(x) for x in colors_df.to_numpy()] colors_sql = """ INSERT INTO artifactcolors (objectid, color, spectrum, percentage) VALUES (%s, %s, %s, %s) """ cursor.executemany(colors_sql, colors_tuples) conn.commit() cursor.close() conn.close() ``` ## Streamlit Application ### Main App Structure ```python import streamlit as st import pandas as pd import plotly.express as px import os st.set_page_config(page_title="Harvard Artifacts Analytics", layout="wide") st.title("🏛️ Harvard Art Museums Analytics Dashboard") # Sidebar for configuration with st.sidebar: st.header("Configuration") num_records = st.number_input("Number of artifacts to fetch", min_value=10, max_value=1000, value=100) if st.button("Run ETL Pipeline"): with st.spinner("Fetching data..."): artifacts = fetch_artifacts(num_records) with st.spinner("Transforming data..."): metadata_df = transform_metadata(artifacts) media_df = transform_media(artifacts) colors_df = transform_colors(artifacts) with st.spinner("Loading to database..."): db_config = { 'host': os.getenv('DB_HOST'), 'user': os.getenv('DB_USER'), 'password': os.getenv('DB_PASSWORD'), 'database': os.getenv('DB_NAME') } load_to_database(metadata_df, media_df, colors_df, db_config) st.success(f"✅ Successfully loaded {len(metadata_df)} artifacts!") # Analytics Section st.header("📊 Analytics Queries") queries = { "Top 10 Cultures by Artifact Count": """ SELECT culture, COUNT(*) as count FROM artifactmetadata WHERE culture != 'Unknown' GROUP BY culture ORDER BY count DESC LIMIT 10 """, "Artifacts by Century": """ SELECT century, COUNT(*) as count FROM artifactmetadata WHERE century != 'Unknown' GROUP BY century ORDER BY count DESC """, "Most Common Color Spectrum": """ SELECT spectrum, COUNT(*) as count, AVG(percentage) as avg_percentage FROM artifactcolors WHERE spectrum IS NOT NULL GROUP BY spectrum ORDER BY count DESC """, "Department Distribution": """ SELECT department, COUNT(*) as count FROM artifactmetadata WHERE department != 'Unknown' GROUP BY department ORDER BY count DESC """, "Most Viewed Artifacts": """ SELECT title, culture, totalpageviews FROM artifactmetadata WHERE totalpageviews > 0 ORDER BY totalpageviews DESC LIMIT 10 """ } selected_query = st.selectbox("Select Analysis", list(queries.keys())) if st.button("Execute Query"): import mysql.connector db_config = { 'host': os.getenv('DB_HOST'), 'user': os.getenv('DB_USER'), 'password': os.getenv('DB_PASSWORD'), 'database': os.getenv('DB_NAME') } conn = mysql.connector.connect(**db_config) df = pd.read_sql(queries[selected_query], conn) conn.close() st.dataframe(df, use_container_width=True) # Auto-generate visualization if len(df.columns) >= 2: fig = px.bar(df, x=df.columns[0], y=df.columns[1], title=selected_query) st.plotly_chart(fig, use_container_width=True) ``` ## Common Patterns ### Rate Limiting and Error Handling ```python import time def fetch_with_retry(url, params, max_retries=3): """ Fetch with exponential backoff retry logic """ for attempt in range(max_retries): try: response = requests.get(url, params=params, timeout=10) if response.status_code == 429: # Rate limit wait_time = 2 ** attempt time.sleep(wait_time) continue response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: if attempt == max_retries - 1: raise time.sleep(2 ** attempt) return None ``` ### Incremental ETL ```python def get_last_processed_id(conn): """ Get the last processed object ID for incremental loads """ cursor = conn.cursor() cursor.execute("SELECT MAX(objectid) FROM artifactmetadata") result = cursor.fetchone() cursor.close() return result[0] if result[0] else 0 def fetch_new_artifacts(last_id): """ Fetch only artifacts after the last processed ID """ params = { 'apikey': os.getenv('HARVARD_API_KEY'), 'size': 100, 'sort': 'objectid', 'sortorder': 'asc', 'objectid': f'>{last_id}' } response = requests.get(BASE_URL, params=params) return response.json().get('records', []) ``` ## Running the Application ```bash # Set environment variables export HARVARD_API_KEY="your_api_key" export DB_HOST="localhost" export DB_USER="root" export DB_PASSWORD="your_password" export DB_NAME="harvard_artifacts" # Run Streamlit app streamlit run app.py ``` ## Troubleshooting
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub