Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
aradotso avatar

Harvard Art Museums Etl Analytics

  • 1.7k installs
  • 4 repo stars
  • Updated July 18, 2026
  • aradotso/data-skills

harvard-art-museums-etl-analytics builds ETL and Streamlit analytics on Harvard Art Museums API data.

About

The harvard-art-museums-etl-analytics skill demonstrates end-to-end data engineering from Harvard Art Museums API through SQL storage to Streamlit visualization. It fetches artifact metadata, media, and color information, transforms nested JSON into normalized relational tables, loads MySQL or TiDB schemas, and runs twenty-plus predefined analytical queries with Plotly charts. Installation clones the Harvard Artifacts repository and installs streamlit, pandas, requests, mysql-connector-python, and plotly. Configuration uses HARVARD_API_KEY plus DB_HOST, DB_USER, DB_PASSWORD, and DB_NAME environment variables. Schema covers artifactmetadata, artifactmedia, and artifactcolors tables with foreign keys linking media and colors to artifact IDs. ETL scripts extract paginated API results, clean nested fields, and load relational rows. Streamlit dashboard surfaces collection insights interactively. Triggers include build ETL pipeline, analytics dashboard, query museum artifacts, and SQL analytics for art museum data. Skill by ara.so Data Skills collection for teaching complete pipeline plus dashboard patterns on public museum open data.

  • Harvard Art Museums API extraction with metadata, media, and colors.
  • ETL transforms nested JSON into normalized MySQL or TiDB tables.
  • Twenty-plus predefined SQL analytical queries included.
  • Streamlit plus Plotly interactive visualization dashboard.
  • Environment variables HARVARD_API_KEY and DB credentials configure access.

Harvard Art Museums Etl Analytics by the numbers

  • 1,706 all-time installs (skills.sh)
  • +2 installs in the week ending Jul 28, 2026 (Skillselion tracking)
  • Ranked #95 of 2,066 Data Science & ML skills by installs in the Skillselion catalog
  • Security screen: MEDIUM risk (skills.sh audit)
  • Data as of Jul 28, 2026 (Skillselion catalog sync)
At a glance

harvard-art-museums-etl-analytics capabilities & compatibility

Capabilities
harvard api integration · json to relational etl · sql analytics queries · streamlit dashboard · plotly visualization
Use cases
data analysis · orchestration
From the docs

What harvard-art-museums-etl-analytics says it does

Executes 20+ predefined analytical queries
SKILL.md
npx skills add https://github.com/aradotso/data-skills --skill harvard-art-museums-etl-analytics

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1.7k
repo stars4
Security audit0 / 3 scanners passed
Last updatedJuly 18, 2026
Repositoryaradotso/data-skills

How do I build an ETL pipeline and dashboard for Harvard Art Museums artifact data?

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

Who is it for?

Data engineers learning API-to-warehouse pipelines with public museum open data.

Skip if: Skip when the data source is not Harvard Art Museums API collections.

When should I use this skill?

User builds museum ETL, Streamlit artifact dashboard, or Harvard API analytics pipeline.

What you get

Normalized SQL tables, executed analytics queries, and interactive Streamlit charts.

  • Paginated API ETL scripts
  • SQL analytics schema
  • Streamlit dashboard

By the numbers

  • Defines 8 trigger phrases for Harvard API ETL and dashboard workflows
  • Spans Python extraction, SQL analytics, and Streamlit visualization end to end

Files

SKILL.mdMarkdownGitHub ↗

Harvard Art Museums ETL Analytics

Skill by ara.so — Data Skills collection.

This project demonstrates a complete data engineering workflow: extracting artifact data from the Harvard Art Museums API, transforming it into relational structures, loading into SQL databases, and visualizing insights through interactive Streamlit dashboards.

What It Does

  • API Integration: Fetches artifact metadata, media, and color information from Harvard Art Museums API
  • ETL Pipeline: Transforms nested JSON into normalized relational tables
  • SQL Storage: Stores data in MySQL/TiDB with proper schema design
  • Analytics: Executes 20+ predefined analytical queries
  • Visualization: Generates interactive charts using Plotly in Streamlit

Installation

# 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

# Required packages
pip install streamlit pandas requests mysql-connector-python plotly

Configuration

Environment Variables

Set up your credentials using environment variables:

export HARVARD_API_KEY="your_api_key_here"
export DB_HOST="your_database_host"
export DB_USER="your_database_user"
export DB_PASSWORD="your_database_password"
export DB_NAME="harvard_artifacts"

Database Setup

The project uses three main tables:

CREATE TABLE artifactmetadata (
    id INT PRIMARY KEY,
    title VARCHAR(500),
    culture VARCHAR(200),
    century VARCHAR(100),
    classification VARCHAR(200),
    department VARCHAR(200),
    dated VARCHAR(200),
    url TEXT
);

CREATE TABLE artifactmedia (
    id INT AUTO_INCREMENT PRIMARY KEY,
    artifact_id INT,
    media_type VARCHAR(100),
    baseimageurl TEXT,
    FOREIGN KEY (artifact_id) REFERENCES artifactmetadata(id)
);

CREATE TABLE artifactcolors (
    id INT AUTO_INCREMENT PRIMARY KEY,
    artifact_id INT,
    color VARCHAR(50),
    percentage FLOAT,
    FOREIGN KEY (artifact_id) REFERENCES artifactmetadata(id)
);

Key Components

1. API Data Collection

import requests
import os

def fetch_artifacts(api_key, page=1, size=100):
    """Fetch artifacts from Harvard Art Museums API"""
    url = f"https://api.harvardartmuseums.org/object"
    params = {
        'apikey': api_key,
        'page': page,
        'size': size
    }
    
    response = requests.get(url, params=params)
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"API Error: {response.status_code}")

# Usage
api_key = os.getenv('HARVARD_API_KEY')
artifacts_data = fetch_artifacts(api_key, page=1, size=50)

2. ETL Pipeline

import pandas as pd
import mysql.connector

def extract_metadata(artifacts):
    """Extract metadata from API response"""
    metadata_list = []
    
    for artifact in artifacts.get('records', []):
        metadata_list.append({
            'id': artifact.get('id'),
            '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'),
            'url': artifact.get('url', '')
        })
    
    return pd.DataFrame(metadata_list)

def extract_media(artifacts):
    """Extract media information"""
    media_list = []
    
    for artifact in artifacts.get('records', []):
        artifact_id = artifact.get('id')
        for media in artifact.get('images', []):
            media_list.append({
                'artifact_id': artifact_id,
                'media_type': 'image',
                'baseimageurl': media.get('baseimageurl', '')
            })
    
    return pd.DataFrame(media_list)

def extract_colors(artifacts):
    """Extract color information"""
    color_list = []
    
    for artifact in artifacts.get('records', []):
        artifact_id = artifact.get('id')
        for color in artifact.get('colors', []):
            color_list.append({
                'artifact_id': artifact_id,
                'color': color.get('color', 'Unknown'),
                'percentage': color.get('percent', 0.0)
            })
    
    return pd.DataFrame(color_list)

3. Loading to SQL

def load_to_sql(df, table_name, connection_config):
    """Load DataFrame to SQL database"""
    conn = mysql.connector.connect(**connection_config)
    cursor = conn.cursor()
    
    # Prepare batch insert
    columns = ', '.join(df.columns)
    placeholders = ', '.join(['%s'] * len(df.columns))
    sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
    
    # Execute batch insert
    data = [tuple(row) for row in df.values]
    cursor.executemany(sql, data)
    
    conn.commit()
    cursor.close()
    conn.close()
    
    return len(df)

# Usage
db_config = {
    'host': os.getenv('DB_HOST'),
    'user': os.getenv('DB_USER'),
    'password': os.getenv('DB_PASSWORD'),
    'database': os.getenv('DB_NAME')
}

metadata_df = extract_metadata(artifacts_data)
load_to_sql(metadata_df, 'artifactmetadata', db_config)

4. SQL Analytics Queries

def execute_query(query, connection_config):
    """Execute SQL query and return DataFrame"""
    conn = mysql.connector.connect(**connection_config)
    df = pd.read_sql(query, conn)
    conn.close()
    return df

# Example analytical queries
queries = {
    "Top 10 Cultures by Artifact Count": """
        SELECT culture, COUNT(*) as artifact_count
        FROM artifactmetadata
        WHERE culture != 'Unknown'
        GROUP BY culture
        ORDER BY artifact_count DESC
        LIMIT 10
    """,
    
    "Artifacts with Images": """
        SELECT 
            m.department,
            COUNT(DISTINCT m.id) as total_artifacts,
            COUNT(DISTINCT media.artifact_id) as with_images,
            ROUND(COUNT(DISTINCT media.artifact_id) * 100.0 / COUNT(DISTINCT m.id), 2) as image_percentage
        FROM artifactmetadata m
        LEFT JOIN artifactmedia media ON m.id = media.artifact_id
        GROUP BY m.department
        ORDER BY total_artifacts DESC
    """,
    
    "Most Common Colors": """
        SELECT color, COUNT(*) as usage_count, AVG(percentage) as avg_percentage
        FROM artifactcolors
        GROUP BY color
        ORDER BY usage_count DESC
        LIMIT 15
    """,
    
    "Artifacts by Century": """
        SELECT century, COUNT(*) as count
        FROM artifactmetadata
        WHERE century IS NOT NULL AND century != 'Unknown'
        GROUP BY century
        ORDER BY count DESC
    """
}

5. Streamlit Dashboard

import streamlit as st
import plotly.express as px

def create_dashboard():
    st.set_page_config(page_title="Harvard Art Analytics", layout="wide")
    
    st.title("🎨 Harvard Art Museums Analytics Dashboard")
    
    # Sidebar for query selection
    st.sidebar.header("Select Analysis")
    query_name = st.sidebar.selectbox("Choose Query", list(queries.keys()))
    
    # Execute query
    if st.button("Run Analysis"):
        with st.spinner("Fetching data..."):
            df = execute_query(queries[query_name], db_config)
            
            # Display results
            st.subheader(f"Results: {query_name}")
            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=query_name
                )
                st.plotly_chart(fig, use_container_width=True)

if __name__ == "__main__":
    create_dashboard()

Running the Application

# Start the Streamlit app
streamlit run app.py

# The app will open at http://localhost:8501

Common Patterns

Full ETL Workflow

def run_etl_pipeline(api_key, db_config, num_pages=5):
    """Complete ETL pipeline"""
    all_metadata = []
    all_media = []
    all_colors = []
    
    for page in range(1, num_pages + 1):
        print(f"Processing page {page}...")
        
        # Extract
        artifacts = fetch_artifacts(api_key, page=page)
        
        # Transform
        metadata = extract_metadata(artifacts)
        media = extract_media(artifacts)
        colors = extract_colors(artifacts)
        
        all_metadata.append(metadata)
        all_media.append(media)
        all_colors.append(colors)
    
    # Combine all pages
    metadata_df = pd.concat(all_metadata, ignore_index=True)
    media_df = pd.concat(all_media, ignore_index=True)
    colors_df = pd.concat(all_colors, ignore_index=True)
    
    # Load
    load_to_sql(metadata_df, 'artifactmetadata', db_config)
    load_to_sql(media_df, 'artifactmedia', db_config)
    load_to_sql(colors_df, 'artifactcolors', db_config)
    
    print(f"✅ Loaded {len(metadata_df)} artifacts")

Error Handling and Rate Limiting

import time

def fetch_with_retry(api_key, page, max_retries=3):
    """Fetch with exponential backoff"""
    for attempt in range(max_retries):
        try:
            response = fetch_artifacts(api_key, page)
            time.sleep(1)  # Rate limiting
            return response
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            wait_time = 2 ** attempt
            print(f"Retry {attempt + 1} after {wait_time}s...")
            time.sleep(wait_time)

Troubleshooting

API Rate Limits

If you encounter rate limiting errors:

# Add delay between requests
import time
time.sleep(2)  # Wait 2 seconds between API calls

Database Connection Issues

# Test database connection
try:
    conn = mysql.connector.connect(**db_config)
    print("✅ Database connected successfully")
    conn.close()
except mysql.connector.Error as err:
    print(f"❌ Database error: {err}")

Memory Issues with Large Datasets

# Process in smaller batches
def batch_process(api_key, db_config, batch_size=10):
    for i in range(0, 100, batch_size):
        # Process and load batch
        # Clear memory
        import gc
        gc.collect()

Missing Data Fields

# Use safe extraction with defaults
def safe_get(dictionary, key, default='Unknown'):
    return dictionary.get(key, default) or default

Advanced Usage

Custom Analytics Query

# Add custom query to dashboard
custom_query = st.text_area("Enter Custom SQL Query")
if st.button("Execute Custom Query"):
    df = execute_query(custom_query, db_config)
    st.dataframe(df)

Export Results

# Export query results to CSV
df = execute_query(queries[query_name], db_config)
csv = df.to_csv(index=False)
st.download_button("Download CSV", csv, "results.csv", "text/csv")

This skill enables AI agents to help developers build complete data engineering pipelines using the Harvard Art Museums API, from data collection through analytics and visualization.

Related skills

How it compares

Choose this when Harvard API pagination and schema correctness matter more than a generic public-API ETL outline.

FAQ

What API does it use?

Harvard Art Museums API with HARVARD_API_KEY environment variable.

What database is supported?

MySQL or TiDB with artifactmetadata, artifactmedia, artifactcolors tables.

How is data visualized?

Interactive Streamlit dashboard with Plotly charts.

Is Harvard Art Museums Etl Analytics safe to install?

skills.sh reports 0 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

Data Science & MLpipelinesanalytics

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.