visitor-scraper / index.html
melassy's picture
Add 2 files
d3883f0 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Visitor Analytics Scraper</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #4361ee;
--secondary-color: #3f37c9;
--accent-color: #4895ef;
--dark-color: #1a1a2e;
--light-color: #f8f9fa;
--danger-color: #ef233c;
--success-color: #2ecc71;
--warning-color: #ff9f1c;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fb;
color: var(--dark-color);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.logo {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--primary-color);
font-size: 1.5rem;
font-weight: 700;
}
.logo i {
font-size: 2rem;
}
.search-container {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
}
.search-input {
flex: 1;
padding: 0.8rem 1rem;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 1rem;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 2px 10px rgba(67, 97, 238, 0.2);
}
.search-btn {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
padding: 0 2rem;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
.search-btn:hover {
background-color: var(--secondary-color);
}
.dashboard {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 2rem;
}
.stats-card {
background-color: white;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
}
.stats-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.stats-title {
font-size: 1.2rem;
font-weight: 600;
}
.refresh-btn {
background-color: var(--light-color);
border: none;
border-radius: 50%;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.refresh-btn:hover {
background-color: #e9ecef;
}
.stat-item {
margin-bottom: 1rem;
}
.stat-label {
display: block;
font-size: 0.9rem;
color: #6c757d;
margin-bottom: 0.2rem;
}
.stat-value {
font-size: 1.5rem;
font-weight: 600;
color: var(--dark-color);
}
.visitors-table {
width: 100%;
border-collapse: collapse;
background-color: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
}
.visitors-table th, .visitors-table td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #eee;
}
.visitors-table th {
background-color: #f8f9fa;
font-weight: 600;
color: #495057;
}
.visitors-table tr:last-child td {
border-bottom: none;
}
.visitor-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
object-fit: cover;
margin-right: 0.5rem;
vertical-align: middle;
}
.visitor-info {
display: flex;
align-items: center;
}
.visitor-name {
font-weight: 500;
}
.visitor-email {
font-size: 0.8rem;
color: #6c757d;
}
.flag-icon {
width: 20px;
margin-right: 0.5rem;
vertical-align: middle;
}
.status {
display: inline-block;
padding: 0.3rem 0.6rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 500;
}
.status-new {
background-color: rgba(72, 149, 239, 0.1);
color: var(--accent-color);
}
.status-returning {
background-color: rgba(46, 204, 113, 0.1);
color: var(--success-color);
}
.pagination {
display: flex;
justify-content: center;
margin-top: 1.5rem;
gap: 0.5rem;
}
.pagination-btn {
background-color: white;
border: 1px solid #ddd;
border-radius: 6px;
padding: 0.5rem 1rem;
cursor: pointer;
transition: all 0.3s ease;
}
.pagination-btn:hover {
background-color: #f1f3f5;
}
.pagination-btn.active {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.tabs {
display: flex;
margin-bottom: 2rem;
border-bottom: 1px solid #ddd;
}
.tab {
padding: 0.8rem 1.5rem;
cursor: pointer;
font-weight: 500;
color: #6c757d;
position: relative;
transition: all 0.3s ease;
}
.tab:hover {
color: var(--primary-color);
}
.tab.active {
color: var(--primary-color);
}
.tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 3px;
background-color: var(--primary-color);
border-radius: 3px 3px 0 0;
}
.chart-container {
background-color: white;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
margin-bottom: 2rem;
}
@media (max-width: 992px) {
.dashboard {
grid-template-columns: 1fr;
}
}
.loader {
display: none;
border: 4px solid #f3f3f3;
border-top: 4px solid var(--primary-color);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 2rem auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.notification {
position: fixed;
top: 20px;
right: 20px;
background-color: var(--success-color);
color: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: none;
z-index: 1000;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="logo">
<i class="fas fa-users"></i>
<span>VisitorScraper Pro</span>
</div>
<div>
<button class="search-btn" id="exportBtn">
<i class="fas fa-file-export"></i> Export Data
</button>
</div>
</header>
<div class="search-container">
<input type="text" class="search-input" id="websiteUrl" placeholder="Enter website URL (e.g., https://example.com)" value="example.com">
<button class="search-btn" id="scrapeBtn">
<i class="fas fa-search"></i> Scrape Visitors
</button>
</div>
<div class="loader" id="loader"></div>
<div class="tabs">
<div class="tab active">Dashboard</div>
<div class="tab">Analyze</div>
<div class="tab">Settings</div>
</div>
<div class="dashboard" id="dashboard">
<div>
<div class="stats-card">
<div class="stats-header">
<h3 class="stats-title">Visitor Statistics</h3>
<button class="refresh-btn" id="refreshStats">
<i class="fas fa-sync-alt"></i>
</button>
</div>
<div class="stat-item">
<span class="stat-label">Total Visitors</span>
<span class="stat-value" id="totalVisitors">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Unique Visitors</span>
<span class="stat-value" id="uniqueVisitors">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Returning Visitors</span>
<span class="stat-value" id="returningVisitors">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Avg. Visit Duration</span>
<span class="stat-value" id="avgDuration">0m 0s</span>
</div>
</div>
<div class="stats-card" style="margin-top: 1.5rem;">
<div class="stats-header">
<h3 class="stats-title">Traffic Sources</h3>
</div>
<div class="stat-item">
<span class="stat-label">Direct</span>
<span class="stat-value" id="directTraffic">0%</span>
</div>
<div class="stat-item">
<span class="stat-label">Organic Search</span>
<span class="stat-value" id="organicTraffic">0%</span>
</div>
<div class="stat-item">
<span class="stat-label">Social Media</span>
<span class="stat-value" id="socialTraffic">0%</span>
</div>
<div class="stat-item">
<span class="stat-label">Referral</span>
<span class="stat-value" id="referralTraffic">0%</span>
</div>
</div>
</div>
<div>
<div class="chart-container">
<canvas id="visitorChart" height="200"></canvas>
</div>
<div class="stats-card">
<div class="stats-header">
<h3 class="stats-title">Recent Visitors</h3>
<div class="dropdown">
<button class="refresh-btn">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
</div>
<div style="overflow-x: auto;">
<table class="visitors-table">
<thead>
<tr>
<th>Visitor</th>
<th>Location</th>
<th>Device</th>
<th>Time Spent</th>
<th>Status</th>
</tr>
</thead>
<tbody id="visitorsTableBody">
<!-- Visitors will be dynamically inserted here -->
</tbody>
</table>
</div>
<div class="pagination">
<button class="pagination-btn active">1</button>
<button class="pagination-btn">2</button>
<button class="pagination-btn">3</button>
<button class="pagination-btn">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="notification" id="notification">
<i class="fas fa-check-circle"></i> Data scraped successfully!
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Mock data for demonstration
const mockVisitors = [
{
id: 1,
name: "John Doe",
email: "john@example.com",
location: "United States",
device: "Desktop",
timeSpent: "4m 32s",
status: "returning",
ip: "192.168.1.1",
avatar: "https://randomuser.me/api/portraits/men/32.jpg"
},
{
id: 2,
name: "Jane Smith",
email: "jane@example.com",
location: "Germany",
device: "Mobile",
timeSpent: "2m 15s",
status: "new",
ip: "192.168.1.2",
avatar: "https://randomuser.me/api/portraits/women/44.jpg"
},
{
id: 3,
name: "Robert Johnson",
email: "robert@example.com",
location: "United Kingdom",
device: "Tablet",
timeSpent: "8m 47s",
status: "returning",
ip: "192.168.1.3",
avatar: "https://randomuser.me/api/portraits/men/67.jpg"
},
{
id: 4,
name: "Emily Davis",
email: "emily@example.com",
location: "France",
device: "Desktop",
timeSpent: "5m 21s",
status: "new",
ip: "192.168.1.4",
avatar: "https://randomuser.me/api/portraits/women/33.jpg"
},
{
id: 5,
name: "Michael Brown",
email: "michael@example.com",
location: "Canada",
device: "Mobile",
timeSpent: "3m 54s",
status: "returning",
ip: "192.168.1.5",
avatar: "https://randomuser.me/api/portraits/men/22.jpg"
}
];
// Elements
const scrapeBtn = document.getElementById('scrapeBtn');
const exportBtn = document.getElementById('exportBtn');
const refreshStats = document.getElementById('refreshStats');
const loader = document.getElementById('loader');
const dashboard = document.getElementById('dashboard');
const notification = document.getElementById('notification');
const visitorsTableBody = document.getElementById('visitorsTableBody');
const tabs = document.querySelectorAll('.tab');
const paginationBtns = document.querySelectorAll('.pagination-btn');
// Statistics elements
const totalVisitors = document.getElementById('totalVisitors');
const uniqueVisitors = document.getElementById('uniqueVisitors');
const returningVisitors = document.getElementById('returningVisitors');
const avgDuration = document.getElementById('avgDuration');
const directTraffic = document.getElementById('directTraffic');
const organicTraffic = document.getElementById('organicTraffic');
const socialTraffic = document.getElementById('socialTraffic');
const referralTraffic = document.getElementById('referralTraffic');
// Chart initialization
const ctx = document.getElementById('visitorChart').getContext('2d');
let visitorChart = new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],
datasets: [{
label: 'Visitors',
data: [120, 190, 170, 220, 280, 250, 310],
backgroundColor: 'rgba(67, 97, 238, 0.2)',
borderColor: 'rgba(67, 97, 238, 1)',
borderWidth: 2,
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
grid: {
display: true,
color: 'rgba(0, 0, 0, 0.05)'
}
},
x: {
grid: {
display: false
}
}
}
}
});
// Tab functionality
tabs.forEach(tab => {
tab.addEventListener('click', function() {
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
// Pagination functionality
paginationBtns.forEach(btn => {
btn.addEventListener('click', function() {
if (this.textContent.trim() !== '') {
paginationBtns.forEach(b => b.classList.remove('active'));
this.classList.add('active');
}
});
});
// Display mock visitors
function displayVisitors(visitors) {
visitorsTableBody.innerHTML = '';
visitors.forEach(visitor => {
const statusClass = visitor.status === 'new' ? 'status-new' : 'status-returning';
const statusText = visitor.status === 'new' ? 'New' : 'Returning';
const row = document.createElement('tr');
row.innerHTML = `
<td>
<div class="visitor-info">
<img src="${visitor.avatar}" alt="${visitor.name}" class="visitor-avatar">
<div>
<div class="visitor-name">${visitor.name}</div>
<div class="visitor-email">${visitor.email}</div>
</div>
</div>
</td>
<td>
<img src="https://flagcdn.com/w20/${getCountryCode(visitor.location).toLowerCase()}.png" class="flag-icon">
${visitor.location}
</td>
<td>${visitor.device}</td>
<td>${visitor.timeSpent}</td>
<td><span class="status ${statusClass}">${statusText}</span></td>
`;
visitorsTableBody.appendChild(row);
});
}
// Helper function to get country code (simplified)
function getCountryCode(country) {
const countryMap = {
'United States': 'US',
'Germany': 'DE',
'United Kingdom': 'GB',
'France': 'FR',
'Canada': 'CA'
};
return countryMap[country] || 'US';
}
// Update statistics
function updateStats() {
totalVisitors.textContent = mockVisitors.length;
uniqueVisitors.textContent = mockVisitors.filter(v => v.status === 'new').length;
returningVisitors.textContent = mockVisitors.filter(v => v.status === 'returning').length;
// Calculate average time spent
const times = mockVisitors.map(v => {
const parts = v.timeSpent.split(/[ms]/).filter(Boolean).map(Number);
return parts[0] * 60 + (parts[1] || 0);
});
const avgSeconds = Math.round(times.reduce((a, b) => a + b, 0) / times.length);
const minutes = Math.floor(avgSeconds / 60);
const seconds = avgSeconds % 60;
avgDuration.textContent = `${minutes}m ${seconds}s`;
// Traffic sources
directTraffic.textContent = '40%';
organicTraffic.textContent = '35%';
socialTraffic.textContent = '15%';
referralTraffic.textContent = '10%';
}
// Scrape button click handler
scrapeBtn.addEventListener('click', function() {
const websiteUrl = document.getElementById('websiteUrl').value.trim();
if (!websiteUrl) {
alert('Please enter a website URL');
return;
}
// Show loader, hide dashboard
loader.style.display = 'block';
dashboard.style.opacity = '0.5';
dashboard.style.pointerEvents = 'none';
// Simulate scraping process
setTimeout(function() {
// Hide loader, show dashboard
loader.style.display = 'none';
dashboard.style.opacity = '1';
dashboard.style.pointerEvents = 'auto';
// Update data
displayVisitors(mockVisitors);
updateStats();
// Update chart
visitorChart.data.datasets[0].data = [150, 210, 190, 240, 300, 270, 330];
visitorChart.update();
// Show notification
notification.style.display = 'flex';
setTimeout(() => {
notification.style.display = 'none';
}, 3000);
}, 2000);
});
// Export button click handler
exportBtn.addEventListener('click', function() {
alert('Visitor data exported to CSV (simulated)');
});
// Refresh stats button click handler
refreshStats.addEventListener('click', function() {
this.querySelector('i').classList.add('fa-spin');
setTimeout(() => {
this.querySelector('i').classList.remove('fa-spin');
updateStats();
}, 1000);
});
// Initial load
displayVisitors(mockVisitors);
updateStats();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
</html>