
Harvard Artifacts Etl Analytics
- 1.7k installs
- 4 repo stars
- Updated July 18, 2026
- aradotso/data-skills
harvard-artifacts-etl-analytics builds Harvard Art Museums ETL pipelines with MySQL storage and Streamlit Plotly dashboards.
About
The harvard-artifacts-etl-analytics skill demonstrates end-to-end data engineering and analytics for Harvard Art Museums API data with ETL, SQL, and interactive Streamlit dashboards. Extract pulls artifact JSON via API with pagination and rate limiting using HARVARD_API_KEY environment variable. Transform flattens nested JSON into relational tables for artifactmetadata, media, and colors. Load inserts batches into MySQL or TiDB Cloud via mysql-connector-python. Analyze runs twenty plus predefined SQL queries for collection insights. Visualize renders Streamlit dashboards with Plotly charts for distributions, timelines, and color analysis. Installation clones the GitHub repository and pip installs streamlit pandas requests mysql-connector-python plotly. Database schema defines artifactmetadata primary keys, foreign keys to media and color tables, and indexes for query performance. Streamlit app sections cover overview metrics, classification breakdowns, temporal trends, and geographic origin maps. API extraction handles page offsets, retry on rate limits, and incremental load patterns. Configuration uses DB_HOST, DB_USER, DB_PASSWORD, and DB_NAME env vars. Skill by ara.so Data Skil.
- Extracts Harvard Art Museums API with pagination and rate limiting.
- Transforms nested JSON into artifactmetadata, media, and color tables.
- Loads batches into MySQL or TiDB Cloud relational schema.
- Twenty plus predefined SQL analytical queries included.
- Streamlit plus Plotly interactive dashboards for collection visualization.
Harvard Artifacts 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)
harvard-artifacts-etl-analytics capabilities & compatibility
- Capabilities
- api extraction with pagination · json to relational transform · mysql batch load · predefined sql analytics · streamlit plotly dashboards
- Use cases
- research · planning
npx skills add https://github.com/aradotso/data-skills --skill harvard-artifacts-etl-analyticsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.7k |
|---|---|
| repo stars | ★ 4 |
| Security audit | 1 / 3 scanners passed |
| Last updated | July 18, 2026 |
| Repository | aradotso/data-skills ↗ |
How do I extract Harvard museum artifacts, load them into SQL, and visualize with Streamlit?
Build Harvard Art Museums API ETL pipelines with MySQL storage and Streamlit Plotly analytics dashboards.
Who is it for?
Data engineers learning museum API ETL with SQL analytics and Streamlit visualization.
Skip if: Skip when the data source is not Harvard Art Museums API.
When should I use this skill?
User builds Harvard museum ETL, Streamlit analytics dashboard, or artifacts SQL pipeline.
What you get
Populated relational schema, analytical SQL results, and interactive Streamlit dashboard.
- ETL extraction scripts
- SQL database schema
- Streamlit analytics app
By the numbers
- Defines 8 trigger phrases for Harvard API ETL and Streamlit analytics
- Covers extraction, SQL loading, analytics, and Streamlit dashboard delivery
Files
Harvard Artifacts ETL Analytics
Skill by ara.so — Data Skills collection.
This project provides an end-to-end data engineering and analytics application for the Harvard Art Museums API. It demonstrates ETL pipelines, SQL database design, analytical queries, and interactive Streamlit dashboards with Plotly visualizations.
What It Does
- Extracts artifact data from Harvard Art Museums API with pagination and rate limiting
- Transforms nested JSON into relational database tables (metadata, media, colors)
- Loads data into MySQL/TiDB Cloud with batch inserts
- Analyzes using 20+ predefined SQL queries
- Visualizes results through interactive Streamlit dashboards
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 plotlyConfiguration
API Key Setup
Get your API key from Harvard Art Museums API:
# Set environment variable
export HARVARD_API_KEY="your_api_key_here"Database Configuration
Configure MySQL/TiDB connection:
import os
import mysql.connector
db_config = {
'host': os.getenv('DB_HOST', 'localhost'),
'user': os.getenv('DB_USER', 'root'),
'password': os.getenv('DB_PASSWORD'),
'database': os.getenv('DB_NAME', 'harvard_artifacts'),
'port': int(os.getenv('DB_PORT', 3306))
}
connection = mysql.connector.connect(**db_config)Database Schema
Create Tables
CREATE TABLE artifactmetadata (
id INT PRIMARY KEY,
title VARCHAR(500),
culture VARCHAR(255),
century VARCHAR(100),
dated VARCHAR(255),
department VARCHAR(255),
classification VARCHAR(255),
medium VARCHAR(500),
dimensions VARCHAR(500),
creditline TEXT,
description TEXT,
provenance TEXT,
people TEXT,
url VARCHAR(500),
lastupdate DATETIME
);
CREATE TABLE artifactmedia (
media_id INT AUTO_INCREMENT PRIMARY KEY,
artifact_id INT,
iiifbaseuri VARCHAR(500),
baseimageurl VARCHAR(500),
renditionnumber VARCHAR(50),
format VARCHAR(50),
FOREIGN KEY (artifact_id) REFERENCES artifactmetadata(id)
);
CREATE TABLE artifactcolors (
color_id INT AUTO_INCREMENT PRIMARY KEY,
artifact_id INT,
color VARCHAR(50),
spectrum VARCHAR(50),
hue VARCHAR(50),
percent FLOAT,
FOREIGN KEY (artifact_id) REFERENCES artifactmetadata(id)
);ETL Pipeline
Extract: Fetch Data from API
import requests
import os
def fetch_artifacts(api_key, page=1, size=100):
"""
Fetch artifacts from Harvard Art Museums API with pagination
"""
base_url = "https://api.harvardartmuseums.org/object"
params = {
'apikey': api_key,
'page': page,
'size': size
}
response = requests.get(base_url, params=params)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"API request failed: {response.status_code}")
def collect_all_artifacts(api_key, max_records=1000):
"""
Collect artifacts with pagination handling
"""
all_artifacts = []
page = 1
size = 100
while len(all_artifacts) < max_records:
data = fetch_artifacts(api_key, page=page, size=size)
records = data.get('records', [])
if not records:
break
all_artifacts.extend(records)
page += 1
# Rate limiting
import time
time.sleep(0.5)
return all_artifacts[:max_records]Transform: Process JSON to DataFrames
import pandas as pd
def transform_metadata(artifacts):
"""
Transform artifact metadata into structured format
"""
metadata_list = []
for artifact in artifacts:
metadata = {
'id': artifact.get('id'),
'title': artifact.get('title'),
'culture': artifact.get('culture'),
'century': artifact.get('century'),
'dated': artifact.get('dated'),
'department': artifact.get('department'),
'classification': artifact.get('classification'),
'medium': artifact.get('medium'),
'dimensions': artifact.get('dimensions'),
'creditline': artifact.get('creditline'),
'description': artifact.get('description'),
'provenance': artifact.get('provenance'),
'people': str(artifact.get('people', [])),
'url': artifact.get('url'),
'lastupdate': artifact.get('lastupdate')
}
metadata_list.append(metadata)
return pd.DataFrame(metadata_list)
def transform_media(artifacts):
"""
Extract media information from artifacts
"""
media_list = []
for artifact in artifacts:
artifact_id = artifact.get('id')
images = artifact.get('images', [])
for image in images:
media = {
'artifact_id': artifact_id,
'iiifbaseuri': image.get('iiifbaseuri'),
'baseimageurl': image.get('baseimageurl'),
'renditionnumber': image.get('renditionnumber'),
'format': image.get('format')
}
media_list.append(media)
return pd.DataFrame(media_list)
def transform_colors(artifacts):
"""
Extract color data from artifacts
"""
color_list = []
for artifact in artifacts:
artifact_id = artifact.get('id')
colors = artifact.get('colors', [])
for color in colors:
color_data = {
'artifact_id': artifact_id,
'color': color.get('color'),
'spectrum': color.get('spectrum'),
'hue': color.get('hue'),
'percent': color.get('percent')
}
color_list.append(color_data)
return pd.DataFrame(color_list)Load: Insert into Database
def load_metadata(df, connection):
"""
Batch insert metadata into database
"""
cursor = connection.cursor()
insert_query = """
INSERT INTO artifactmetadata
(id, title, culture, century, dated, department, classification,
medium, dimensions, creditline, description, provenance, people, url, lastupdate)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE title=VALUES(title)
"""
data = df.values.tolist()
cursor.executemany(insert_query, data)
connection.commit()
cursor.close()
def load_media(df, connection):
"""
Batch insert media data
"""
cursor = connection.cursor()
insert_query = """
INSERT INTO artifactmedia
(artifact_id, iiifbaseuri, baseimageurl, renditionnumber, format)
VALUES (%s, %s, %s, %s, %s)
"""
data = df.values.tolist()
cursor.executemany(insert_query, data)
connection.commit()
cursor.close()
def load_colors(df, connection):
"""
Batch insert color data
"""
cursor = connection.cursor()
insert_query = """
INSERT INTO artifactcolors
(artifact_id, color, spectrum, hue, percent)
VALUES (%s, %s, %s, %s, %s)
"""
data = df.values.tolist()
cursor.executemany(insert_query, data)
connection.commit()
cursor.close()Analytical SQL Queries
Sample Analytics Queries
# Query 1: Artifact count by culture
culture_query = """
SELECT culture, COUNT(*) as artifact_count
FROM artifactmetadata
WHERE culture IS NOT NULL
GROUP BY culture
ORDER BY artifact_count DESC
LIMIT 10
"""
# Query 2: Artifacts with images
media_query = """
SELECT m.department, COUNT(DISTINCT am.artifact_id) as artifacts_with_images
FROM artifactmedia am
JOIN artifactmetadata m ON am.artifact_id = m.id
GROUP BY m.department
ORDER BY artifacts_with_images DESC
"""
# Query 3: Color distribution
color_query = """
SELECT color, COUNT(*) as frequency, AVG(percent) as avg_percentage
FROM artifactcolors
GROUP BY color
ORDER BY frequency DESC
LIMIT 15
"""
# Query 4: Artifacts by century
century_query = """
SELECT century, COUNT(*) as count
FROM artifactmetadata
WHERE century IS NOT NULL
GROUP BY century
ORDER BY count DESC
LIMIT 20
"""
# Query 5: Classification analysis
classification_query = """
SELECT classification, COUNT(*) as total,
SUM(CASE WHEN medium IS NOT NULL THEN 1 ELSE 0 END) as with_medium
FROM artifactmetadata
GROUP BY classification
ORDER BY total DESC
LIMIT 10
"""Streamlit Dashboard
Basic App Structure
import streamlit as st
import pandas as pd
import plotly.express as px
import mysql.connector
import os
st.set_page_config(page_title="Harvard Artifacts Analytics", layout="wide")
st.title("🏛️ Harvard Art Museums ETL & Analytics Dashboard")
# Sidebar for configuration
with st.sidebar:
st.header("Configuration")
api_key = st.text_input("Harvard API Key", type="password",
value=os.getenv('HARVARD_API_KEY', ''))
if st.button("Run ETL Pipeline"):
with st.spinner("Extracting data from API..."):
artifacts = collect_all_artifacts(api_key, max_records=500)
with st.spinner("Transforming data..."):
df_metadata = transform_metadata(artifacts)
df_media = transform_media(artifacts)
df_colors = transform_colors(artifacts)
with st.spinner("Loading into database..."):
connection = mysql.connector.connect(**db_config)
load_metadata(df_metadata, connection)
load_media(df_media, connection)
load_colors(df_colors, connection)
connection.close()
st.success("ETL Pipeline completed successfully!")
# Analytics Section
st.header("📊 Analytics Dashboard")
# Query selector
query_options = {
"Artifacts by Culture": culture_query,
"Media Availability": media_query,
"Color Distribution": color_query,
"Century Analysis": century_query,
"Classification Breakdown": classification_query
}
selected_query = st.selectbox("Select Analysis", list(query_options.keys()))
if st.button("Run Analysis"):
connection = mysql.connector.connect(**db_config)
df_result = pd.read_sql(query_options[selected_query], connection)
connection.close()
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("Query Results")
st.dataframe(df_result)
with col2:
st.subheader("Visualization")
if len(df_result.columns) >= 2:
fig = px.bar(df_result,
x=df_result.columns[0],
y=df_result.columns[1],
title=selected_query)
st.plotly_chart(fig, use_container_width=True)Advanced Visualization
def create_interactive_chart(df, chart_type='bar'):
"""
Create interactive Plotly visualizations
"""
if chart_type == 'bar':
fig = px.bar(df, x=df.columns[0], y=df.columns[1],
color=df.columns[1],
color_continuous_scale='Viridis')
elif chart_type == 'pie':
fig = px.pie(df, names=df.columns[0], values=df.columns[1])
elif chart_type == 'scatter':
fig = px.scatter(df, x=df.columns[0], y=df.columns[1],
size=df.columns[1], hover_data=df.columns)
fig.update_layout(
hovermode='x unified',
showlegend=True,
height=500
)
return fig
# Usage in Streamlit
chart_type = st.radio("Chart Type", ['bar', 'pie', 'scatter'])
fig = create_interactive_chart(df_result, chart_type)
st.plotly_chart(fig, use_container_width=True)Common Patterns
Complete ETL Workflow
def run_complete_etl(api_key, db_config, max_records=1000):
"""
Execute full ETL pipeline
"""
# Extract
print("Extracting data from API...")
artifacts = collect_all_artifacts(api_key, max_records)
# Transform
print("Transforming data...")
df_metadata = transform_metadata(artifacts)
df_media = transform_media(artifacts)
df_colors = transform_colors(artifacts)
# Load
print("Loading into database...")
connection = mysql.connector.connect(**db_config)
try:
load_metadata(df_metadata, connection)
load_media(df_media, connection)
load_colors(df_colors, connection)
print(f"Successfully loaded {len(df_metadata)} artifacts")
finally:
connection.close()
return {
'metadata_count': len(df_metadata),
'media_count': len(df_media),
'color_count': len(df_colors)
}Error Handling
def safe_api_call(api_key, page, retries=3):
"""
API call with retry logic
"""
import time
for attempt in range(retries):
try:
return fetch_artifacts(api_key, page=page)
except Exception as e:
if attempt < retries - 1:
wait_time = 2 ** attempt
print(f"Retry {attempt + 1}/{retries} after {wait_time}s")
time.sleep(wait_time)
else:
raise eTroubleshooting
API Rate Limiting
# Add delays between requests
import time
time.sleep(1) # Wait 1 second between API callsDatabase Connection Issues
# Test connection
try:
connection = mysql.connector.connect(**db_config)
print("Database connected successfully")
connection.close()
except mysql.connector.Error as err:
print(f"Connection failed: {err}")Memory Management for Large Datasets
# Process in chunks
chunk_size = 100
for i in range(0, len(all_artifacts), chunk_size):
chunk = all_artifacts[i:i+chunk_size]
df_chunk = transform_metadata(chunk)
load_metadata(df_chunk, connection)Missing Data Handling
# Handle None values in transformation
metadata = {
'title': artifact.get('title', 'Unknown'),
'culture': artifact.get('culture') or 'Not Specified',
'century': artifact.get('century', 'Unknown Period')
}Related skills
How it compares
Use this Harvard API portfolio ETL when you need SQL plus Streamlit analytics instead of enterprise SSIS or Kafka streaming stacks.
FAQ
What API key is needed?
HARVARD_API_KEY from harvardartmuseums.org collections API registration.
Which database is supported?
MySQL or TiDB Cloud via mysql-connector-python configuration.
How is data visualized?
Streamlit app with Plotly charts for metrics and distributions.
Is Harvard Artifacts Etl Analytics safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.