The Etsy API provides access to rich data about your shop’s performance. By extracting, analyzing, and visualizing this data, you can make informed decisions about pricing, product development, and marketing strategies.
# etsy_client/analytics.py
"""Analytics and reporting for Etsy shops."""
from typing import Dict, Any, List, Optional, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass
from collections import defaultdict
import statistics
from .client import EtsyClient
from .listings import ListingOperations
from .orders import OrderOperations
@dataclass
class ListingMetrics:
"""Metrics for a single listing."""
listing_id: int
title: str
views: int
favorites: int
sales: int
revenue: float
price: float
created_date: datetime
@property
def conversion_rate(self) -> float:
return self.sales / self.views if self.views > 0 else 0
@property
def favorite_rate(self) -> float:
return self.favorites / self.views if self.views > 0 else 0
@property
def revenue_per_view(self) -> float:
return self.revenue / self.views if self.views > 0 else 0
@dataclass
class ShopMetrics:
"""Aggregate metrics for a shop."""
total_listings: int
active_listings: int
total_views: int
total_favorites: int
total_sales: int
total_revenue: float
avg_price: float
avg_conversion_rate: float
period_start: datetime
period_end: datetime
class ShopAnalytics:
"""
Analytics engine for Etsy shop data.
Provides methods to analyze listings, sales, and performance.
"""
def __init__(self, client: EtsyClient, shop_id: int):
self.client = client
self.shop_id = shop_id
self.listing_ops = ListingOperations(client)
self.order_ops = OrderOperations(client)
def get_listing_metrics(
self,
listing_id: int,
days: int = 30
) -> ListingMetrics:
"""
Get metrics for a single listing.
Args:
listing_id: The listing ID
days: Period to analyze (for sales)
Returns:
ListingMetrics object
"""
listing = self.listing_ops.get_listing(listing_id)
# Get sales
transactions = self.order_ops.get_listing_transactions(
self.shop_id, listing_id, limit=100
)
# Filter by date
min_date = datetime.now() - timedelta(days=days)
recent_sales = []
for txn in transactions.get('results', []):
txn_date = datetime.fromtimestamp(txn.get('created_timestamp', 0))
if txn_date >= min_date:
recent_sales.append(txn)
price = listing['price']['amount'] / listing['price']['divisor']
revenue = sum(
(t['price']['amount'] / t['price']['divisor']) * t.get('quantity', 1)
for t in recent_sales
)
return ListingMetrics(
listing_id=listing_id,
title=listing['title'],
views=listing.get('views', 0),
favorites=listing.get('num_favorers', 0),
sales=len(recent_sales),
revenue=revenue,
price=price,
created_date=datetime.fromtimestamp(listing.get('created_timestamp', 0))
)
def get_all_listing_metrics(
self,
state: str = "active"
) -> List[ListingMetrics]:
"""
Get metrics for all listings.
Args:
state: Listing state filter
Returns:
List of ListingMetrics
"""
listings = self.listing_ops.get_all_listings(self.shop_id, state)
metrics = []
for listing in listings:
price = listing['price']['amount'] / listing['price']['divisor']
metrics.append(ListingMetrics(
listing_id=listing['listing_id'],
title=listing['title'],
views=listing.get('views', 0),
favorites=listing.get('num_favorers', 0),
sales=0, # Would need transactions for accurate count
revenue=0,
price=price,
created_date=datetime.fromtimestamp(
listing.get('created_timestamp', 0)
)
))
return metrics
def get_shop_metrics(
self,
days: int = 30
) -> ShopMetrics:
"""
Get aggregate shop metrics.
Args:
days: Period to analyze
Returns:
ShopMetrics object
"""
# Get listings
active_listings = self.listing_ops.get_all_listings(self.shop_id, "active")
# Calculate totals
total_views = sum(l.get('views', 0) for l in active_listings)
total_favorites = sum(l.get('num_favorers', 0) for l in active_listings)
prices = [
l['price']['amount'] / l['price']['divisor']
for l in active_listings
]
avg_price = statistics.mean(prices) if prices else 0
# Get recent orders
min_date = datetime.now() - timedelta(days=days)
receipts = self.order_ops.get_all_receipts(
self.shop_id, min_created=min_date
)
total_revenue = sum(
r.get('grandtotal', {}).get('amount', 0) /
r.get('grandtotal', {}).get('divisor', 100)
for r in receipts
)
total_sales = len(receipts)
# Estimate conversion rate
avg_conversion = total_sales / total_views if total_views > 0 else 0
return ShopMetrics(
total_listings=len(active_listings),
active_listings=len(active_listings),
total_views=total_views,
total_favorites=total_favorites,
total_sales=total_sales,
total_revenue=total_revenue,
avg_price=avg_price,
avg_conversion_rate=avg_conversion,
period_start=min_date,
period_end=datetime.now()
)
def top_performers(
self,
metric: str = "revenue",
limit: int = 10
) -> List[ListingMetrics]:
"""
Get top performing listings by a metric.
Args:
metric: Metric to sort by (views, favorites, sales, revenue)
limit: Number of results
Returns:
Sorted list of ListingMetrics
"""
all_metrics = self.get_all_listing_metrics()
sorted_metrics = sorted(
all_metrics,
key=lambda x: getattr(x, metric, 0),
reverse=True
)
return sorted_metrics[:limit]
def underperformers(
self,
min_age_days: int = 30,
min_views: int = 100,
max_conversion: float = 0.005
) -> List[ListingMetrics]:
"""
Find underperforming listings.
Args:
min_age_days: Minimum listing age to consider
min_views: Minimum views required
max_conversion: Maximum conversion to be "underperforming"
Returns:
List of underperforming ListingMetrics
"""
all_metrics = self.get_all_listing_metrics()
cutoff_date = datetime.now() - timedelta(days=min_age_days)
underperformers = []
for m in all_metrics:
if m.created_date > cutoff_date:
continue # Too new
if m.views < min_views:
continue # Not enough data
if m.conversion_rate <= max_conversion:
underperformers.append(m)
return sorted(underperformers, key=lambda x: x.conversion_rate)
def tag_performance(self) -> Dict[str, Dict]:
"""
Analyze performance by tag.
Returns:
Dictionary of tag performance metrics
"""
listings = self.listing_ops.get_all_listings(self.shop_id, "active")
tag_stats = defaultdict(lambda: {
'listings': 0,
'total_views': 0,
'total_favorites': 0,
'avg_price': []
})
for listing in listings:
views = listing.get('views', 0)
favorites = listing.get('num_favorers', 0)
price = listing['price']['amount'] / listing['price']['divisor']
for tag in listing.get('tags', []):
tag_stats[tag]['listings'] += 1
tag_stats[tag]['total_views'] += views
tag_stats[tag]['total_favorites'] += favorites
tag_stats[tag]['avg_price'].append(price)
# Calculate averages
results = {}
for tag, stats in tag_stats.items():
results[tag] = {
'listings': stats['listings'],
'total_views': stats['total_views'],
'total_favorites': stats['total_favorites'],
'avg_views': stats['total_views'] / stats['listings'],
'avg_price': statistics.mean(stats['avg_price'])
}
return dict(sorted(
results.items(),
key=lambda x: x[1]['total_views'],
reverse=True
))
class ReportGenerator:
"""Generate formatted reports from analytics data."""
def __init__(self, analytics: ShopAnalytics):
self.analytics = analytics
def daily_summary(self) -> str:
"""Generate a daily summary report."""
metrics = self.analytics.get_shop_metrics(days=1)
report = []
report.append("=" * 60)
report.append("DAILY SHOP SUMMARY")
report.append(f"Date: {datetime.now().strftime('%Y-%m-%d')}")
report.append("=" * 60)
report.append("")
report.append(f"Active Listings: {metrics.active_listings}")
report.append(f"Total Views (all time): {metrics.total_views:,}")
report.append(f"Total Favorites (all time): {metrics.total_favorites:,}")
report.append("")
report.append(f"Today's Sales: {metrics.total_sales}")
report.append(f"Today's Revenue: ${metrics.total_revenue:.2f}")
report.append(f"Average Order Value: ${metrics.total_revenue / metrics.total_sales:.2f}"
if metrics.total_sales > 0 else "Average Order Value: N/A")
return "\n".join(report)
def weekly_report(self) -> str:
"""Generate a weekly performance report."""
current = self.analytics.get_shop_metrics(days=7)
previous = self.analytics.get_shop_metrics(days=14)
# Calculate changes
revenue_change = (
(current.total_revenue / (previous.total_revenue / 2) - 1) * 100
if previous.total_revenue > 0 else 0
)
sales_change = (
(current.total_sales / (previous.total_sales / 2) - 1) * 100
if previous.total_sales > 0 else 0
)
report = []
report.append("=" * 60)
report.append("WEEKLY PERFORMANCE REPORT")
report.append(f"Week ending: {datetime.now().strftime('%Y-%m-%d')}")
report.append("=" * 60)
report.append("")
report.append("SALES METRICS")
report.append("-" * 40)
report.append(f"Orders: {current.total_sales} ({revenue_change:+.1f}% vs last week)")
report.append(f"Revenue: ${current.total_revenue:.2f} ({sales_change:+.1f}% vs last week)")
report.append(f"Avg Order Value: ${current.total_revenue / current.total_sales:.2f}"
if current.total_sales > 0 else "")
report.append("")
report.append("TOP PERFORMERS")
report.append("-" * 40)
top = self.analytics.top_performers("views", 5)
for i, m in enumerate(top, 1):
report.append(f"{i}. {m.title[:40]}")
report.append(f" Views: {m.views:,} | Favorites: {m.favorites}")
return "\n".join(report)
def listing_report(self, listing_id: int) -> str:
"""Generate a detailed report for a single listing."""
metrics = self.analytics.get_listing_metrics(listing_id, days=30)
report = []
report.append("=" * 60)
report.append("LISTING PERFORMANCE REPORT")
report.append("=" * 60)
report.append("")
report.append(f"Title: {metrics.title}")
report.append(f"Listing ID: {metrics.listing_id}")
report.append(f"Price: ${metrics.price:.2f}")
report.append(f"Created: {metrics.created_date.strftime('%Y-%m-%d')}")
report.append("")
report.append("METRICS (30 days)")
report.append("-" * 40)
report.append(f"Views: {metrics.views:,}")
report.append(f"Favorites: {metrics.favorites}")
report.append(f"Sales: {metrics.sales}")
report.append(f"Revenue: ${metrics.revenue:.2f}")
report.append("")
report.append("CALCULATED RATES")
report.append("-" * 40)
report.append(f"Conversion Rate: {metrics.conversion_rate * 100:.2f}%")
report.append(f"Favorite Rate: {metrics.favorite_rate * 100:.2f}%")
report.append(f"Revenue per View: ${metrics.revenue_per_view:.4f}")
# Performance assessment
report.append("")
report.append("ASSESSMENT")
report.append("-" * 40)
if metrics.conversion_rate > 0.02:
report.append("✓ Conversion rate is excellent (>2%)")
elif metrics.conversion_rate > 0.01:
report.append("● Conversion rate is good (1-2%)")
else:
report.append("⚠ Conversion rate needs improvement (<1%)")
if metrics.favorite_rate > 0.05:
report.append("✓ High interest (>5% favorite rate)")
return "\n".join(report)
# etsy_client/visualization.py
"""Data visualization for Etsy analytics."""
from typing import Dict, Any, List
from datetime import datetime, timedelta
import json
def generate_chart_data(
data: List[Dict],
x_key: str,
y_key: str
) -> Dict[str, List]:
"""
Prepare data for charting.
Args:
data: List of data dictionaries
x_key: Key for x-axis values
y_key: Key for y-axis values
Returns:
Dictionary with 'labels' and 'values' lists
"""
return {
'labels': [d[x_key] for d in data],
'values': [d[y_key] for d in data]
}
def create_ascii_bar_chart(
data: Dict[str, float],
title: str = "Chart",
max_width: int = 40
) -> str:
"""
Create a simple ASCII bar chart.
Args:
data: Dictionary of label: value pairs
title: Chart title
max_width: Maximum bar width in characters
Returns:
ASCII chart string
"""
if not data:
return "No data to display"
max_value = max(data.values())
max_label_len = max(len(str(k)) for k in data.keys())
lines = []
lines.append(title)
lines.append("=" * (max_label_len + max_width + 15))
for label, value in data.items():
bar_length = int((value / max_value) * max_width) if max_value > 0 else 0
bar = "█" * bar_length
lines.append(f"{label:<{max_label_len}} │ {bar} {value:.2f}")
return "\n".join(lines)
def create_html_report(
shop_metrics: Dict,
listing_metrics: List[Dict],
output_file: str
):
"""
Create an HTML analytics report.
Args:
shop_metrics: Shop-level metrics
listing_metrics: List of listing metrics
output_file: Output HTML file path
"""
html = """
<!DOCTYPE html>
<html>
<head>
<title>Etsy Shop Analytics Report</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.metric-card {
display: inline-block;
padding: 20px;
margin: 10px;
border: 1px solid #ddd;
border-radius: 8px;
text-align: center;
}
.metric-value { font-size: 2em; font-weight: bold; color: #F1641E; }
.metric-label { color: #666; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.chart-container { width: 600px; margin: 20px auto; }
</style>
</head>
<body>
<h1>📊 Shop Analytics Report</h1>
<p>Generated: """ + datetime.now().strftime('%Y-%m-%d %H:%M') + """</p>
<h2>Overview</h2>
<div class="metrics">
<div class="metric-card">
<div class="metric-value">""" + str(shop_metrics.get('total_listings', 0)) + """</div>
<div class="metric-label">Active Listings</div>
</div>
<div class="metric-card">
<div class="metric-value">""" + f"${shop_metrics.get('total_revenue', 0):.2f}" + """</div>
<div class="metric-label">Total Revenue</div>
</div>
<div class="metric-card">
<div class="metric-value">""" + f"{shop_metrics.get('total_views', 0):,}" + """</div>
<div class="metric-label">Total Views</div>
</div>
<div class="metric-card">
<div class="metric-value">""" + str(shop_metrics.get('total_sales', 0)) + """</div>
<div class="metric-label">Total Sales</div>
</div>
</div>
<h2>Top Listings by Views</h2>
<table>
<tr>
<th>Listing</th>
<th>Views</th>
<th>Favorites</th>
<th>Price</th>
<th>Conversion</th>
</tr>
"""
# Add listing rows
for listing in listing_metrics[:20]:
conversion = listing.get('conversion_rate', 0) * 100
html += f"""
<tr>
<td>{listing.get('title', '')[:50]}</td>
<td>{listing.get('views', 0):,}</td>
<td>{listing.get('favorites', 0)}</td>
<td>${listing.get('price', 0):.2f}</td>
<td>{conversion:.2f}%</td>
</tr>
"""
html += """
</table>
<div class="chart-container">
<canvas id="viewsChart"></canvas>
</div>
<script>
// Views chart
const ctx = document.getElementById('viewsChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: """ + json.dumps([l.get('title', '')[:20] for l in listing_metrics[:10]]) + """,
datasets: [{
label: 'Views',
data: """ + json.dumps([l.get('views', 0) for l in listing_metrics[:10]]) + """,
backgroundColor: '#F1641E'
}]
},
options: {
plugins: { title: { display: true, text: 'Top 10 Listings by Views' } }
}
});
</script>
</body>
</html>
"""
with open(output_file, 'w') as f:
f.write(html)
print(f"Report generated: {output_file}")
# scripts/generate_report.py
"""Generate comprehensive analytics reports."""
import sys
from pathlib import Path
from datetime import datetime
sys.path.insert(0, str(Path(__file__).parent.parent))
from config import Config
from etsy_client import EtsyClient
from etsy_client.analytics import ShopAnalytics, ReportGenerator
from etsy_client.visualization import create_html_report, create_ascii_bar_chart
from etsy_client.users import get_my_shop_id
def generate_all_reports(output_dir: str = "data/reports"):
"""Generate all analytics reports."""
client = EtsyClient(
api_key=Config.ETSY_API_KEY,
access_token=Config.ETSY_ACCESS_TOKEN
)
shop_id = get_my_shop_id(client)
analytics = ShopAnalytics(client, shop_id)
report_gen = ReportGenerator(analytics)
# Create output directory
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Daily summary (console)
print(report_gen.daily_summary())
print()
# Weekly report (file)
weekly = report_gen.weekly_report()
weekly_file = output_path / f"weekly_{timestamp}.txt"
weekly_file.write_text(weekly)
print(f"Weekly report saved: {weekly_file}")
# Get metrics for HTML report
shop_metrics = analytics.get_shop_metrics(days=30)
listing_metrics = analytics.get_all_listing_metrics()
# Sort by views
listing_metrics_sorted = sorted(
[{'title': m.title, 'views': m.views, 'favorites': m.favorites,
'price': m.price, 'conversion_rate': m.conversion_rate}
for m in listing_metrics],
key=lambda x: x['views'],
reverse=True
)
# HTML report
html_file = output_path / f"report_{timestamp}.html"
create_html_report(
{
'total_listings': shop_metrics.active_listings,
'total_revenue': shop_metrics.total_revenue,
'total_views': shop_metrics.total_views,
'total_sales': shop_metrics.total_sales
},
listing_metrics_sorted,
str(html_file)
)
# Tag analysis (console)
print("\n" + "=" * 60)
print("TAG PERFORMANCE ANALYSIS")
print("=" * 60)
tag_perf = analytics.tag_performance()
top_tags = dict(list(tag_perf.items())[:10])
tag_views = {tag: data['total_views'] for tag, data in top_tags.items()}
print(create_ascii_bar_chart(tag_views, "Top Tags by Views"))
# Underperformers
print("\n" + "=" * 60)
print("UNDERPERFORMING LISTINGS")
print("=" * 60)
under = analytics.underperformers(min_age_days=30, min_views=50)
if under:
print(f"\nFound {len(under)} underperforming listings:\n")
for m in under[:5]:
print(f"• {m.title[:50]}")
print(f" Views: {m.views} | Conversion: {m.conversion_rate*100:.2f}%")
else:
print("\nNo underperforming listings found!")
if __name__ == "__main__":
generate_all_reports()
# scripts/daily_analytics.py
"""Send daily analytics via email or webhook."""
import sys
from pathlib import Path
from datetime import datetime
import json
import requests
sys.path.insert(0, str(Path(__file__).parent.parent))
from config import Config
from etsy_client import EtsyClient
from etsy_client.analytics import ShopAnalytics
from etsy_client.users import get_my_shop_id
def send_slack_analytics(webhook_url: str, shop_id: int, analytics: ShopAnalytics):
"""Send analytics to Slack."""
metrics = analytics.get_shop_metrics(days=1)
# Compare to yesterday
yesterday = analytics.get_shop_metrics(days=2)
revenue_change = (
(metrics.total_revenue / (yesterday.total_revenue / 2) - 1) * 100
if yesterday.total_revenue > 0 else 0
)
# Build Slack message
message = {
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"📊 Daily Shop Analytics - {datetime.now().strftime('%Y-%m-%d')}"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": f"*Sales Today:*\n{metrics.total_sales}"
},
{
"type": "mrkdwn",
"text": f"*Revenue:*\n${metrics.total_revenue:.2f}"
}
]
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": f"*Active Listings:*\n{metrics.active_listings}"
},
{
"type": "mrkdwn",
"text": f"*Revenue Trend:*\n{revenue_change:+.1f}%"
}
]
}
]
}
# Add top performer if there were sales
if metrics.total_sales > 0:
top = analytics.top_performers("views", 1)
if top:
message["blocks"].append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Top Listing:* {top[0].title[:50]}"
}
})
# Send to Slack
response = requests.post(webhook_url, json=message)
if response.status_code == 200:
print("✓ Analytics sent to Slack")
else:
print(f"✗ Failed to send to Slack: {response.status_code}")
def save_daily_metrics(analytics: ShopAnalytics, output_file: str):
"""Save daily metrics to JSON for historical tracking."""
metrics = analytics.get_shop_metrics(days=1)
# Load existing data
data = []
try:
with open(output_file, 'r') as f:
data = json.load(f)
except FileNotFoundError:
pass
# Add today's data
data.append({
'date': datetime.now().strftime('%Y-%m-%d'),
'sales': metrics.total_sales,
'revenue': metrics.total_revenue,
'active_listings': metrics.active_listings,
'total_views': metrics.total_views,
'total_favorites': metrics.total_favorites
})
# Keep last 365 days
data = data[-365:]
with open(output_file, 'w') as f:
json.dump(data, f, indent=2)
print(f"✓ Metrics saved to {output_file}")
def main():
"""Run daily analytics."""
client = EtsyClient(
api_key=Config.ETSY_API_KEY,
access_token=Config.ETSY_ACCESS_TOKEN
)
shop_id = get_my_shop_id(client)
analytics = ShopAnalytics(client, shop_id)
# Save metrics
save_daily_metrics(analytics, "data/daily_metrics.json")
# Send to Slack if configured
slack_webhook = Config.SLACK_WEBHOOK_URL if hasattr(Config, 'SLACK_WEBHOOK_URL') else None
if slack_webhook:
send_slack_analytics(slack_webhook, shop_id, analytics)
if __name__ == "__main__":
main()
# scripts/dashboard.py
"""Simple web dashboard for Etsy analytics."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
# Note: Requires dash installation: pip install dash plotly
try:
from dash import Dash, html, dcc
import plotly.express as px
import plotly.graph_objects as go
except ImportError:
print("Dashboard requires dash and plotly: pip install dash plotly")
sys.exit(1)
from config import Config
from etsy_client import EtsyClient
from etsy_client.analytics import ShopAnalytics
from etsy_client.users import get_my_shop_id
def create_dashboard():
"""Create and run the analytics dashboard."""
# Initialize data
client = EtsyClient(
api_key=Config.ETSY_API_KEY,
access_token=Config.ETSY_ACCESS_TOKEN
)
shop_id = get_my_shop_id(client)
analytics = ShopAnalytics(client, shop_id)
# Get data
shop_metrics = analytics.get_shop_metrics(days=30)
listing_metrics = analytics.get_all_listing_metrics()
# Sort listings
listing_data = sorted(
[{'title': m.title[:30], 'views': m.views, 'favorites': m.favorites,
'price': m.price, 'conversion': m.conversion_rate * 100}
for m in listing_metrics],
key=lambda x: x['views'],
reverse=True
)
# Create app
app = Dash(__name__)
app.layout = html.Div([
html.H1("📊 Etsy Shop Dashboard", style={'textAlign': 'center'}),
# Metrics cards
html.Div([
html.Div([
html.H3(f"{shop_metrics.active_listings}"),
html.P("Active Listings")
], className='metric-card', style={
'display': 'inline-block', 'padding': '20px',
'margin': '10px', 'border': '1px solid #ddd',
'borderRadius': '8px', 'textAlign': 'center'
}),
html.Div([
html.H3(f"${shop_metrics.total_revenue:.2f}"),
html.P("Revenue (30 days)")
], className='metric-card', style={
'display': 'inline-block', 'padding': '20px',
'margin': '10px', 'border': '1px solid #ddd',
'borderRadius': '8px', 'textAlign': 'center'
}),
html.Div([
html.H3(f"{shop_metrics.total_views:,}"),
html.P("Total Views")
], className='metric-card', style={
'display': 'inline-block', 'padding': '20px',
'margin': '10px', 'border': '1px solid #ddd',
'borderRadius': '8px', 'textAlign': 'center'
}),
html.Div([
html.H3(f"{shop_metrics.total_sales}"),
html.P("Sales (30 days)")
], className='metric-card', style={
'display': 'inline-block', 'padding': '20px',
'margin': '10px', 'border': '1px solid #ddd',
'borderRadius': '8px', 'textAlign': 'center'
}),
], style={'textAlign': 'center'}),
# Charts
html.Div([
dcc.Graph(
id='views-chart',
figure=px.bar(
listing_data[:10],
x='title', y='views',
title='Top 10 Listings by Views'
)
)
], style={'width': '50%', 'display': 'inline-block'}),
html.Div([
dcc.Graph(
id='conversion-chart',
figure=px.scatter(
listing_data,
x='views', y='conversion',
size='favorites', hover_name='title',
title='Views vs Conversion Rate'
)
)
], style={'width': '50%', 'display': 'inline-block'}),
])
return app
if __name__ == "__main__":
app = create_dashboard()
print("Dashboard running at http://localhost:8050")
app.run_server(debug=True)
Analytics tell you what’s happening, but webhooks tell you when it happens. The next chapter covers implementing real-time notifications through Etsy’s webhook system.
| ← Chapter 8: Inventory and Pricing Automation | Table of Contents | Chapter 10: Webhooks and Real-Time Updates → |