Custom Code Experiment •
DataLayer Extraction Tool
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DataLayer Extraction Tool</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: #e0e0e0;
background: #000000;
min-height: 100vh;
padding: 20px;
position: relative;
}
h1 {
font-size: 32px;
margin-bottom: 8px;
color: white;
font-weight: 700;
}
h2 {
font-size: 20px;
margin-bottom: 16px;
color: white;
font-weight: 600;
}
h3 {
font-size: 18px;
margin-bottom: 12px;
color: white;
font-weight: 600;
}
.form-group {
margin-bottom: 24px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: white;
font-size: 14px;
}
input[type="url"] {
width: 100%;
padding: 12px 16px;
border: 2px solid #333;
border-radius: 8px;
font-size: 16px;
background: #1a1a1a;
color: #e0e0e0;
transition: all 0.2s ease;
}
input[type="url"]:focus {
border-color: #3498db;
outline: none;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
.button-group {
display: flex;
gap: 12px;
margin-bottom: 24px;
flex-wrap: wrap;
}
button {
background: #3498db;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 1;
min-width: 160px;
}
button:hover {
background: #2980b9;
transform: translateY(-1px);
}
button:disabled {
background: #555;
color: #999;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: #95a5a6;
}
.btn-secondary:hover {
background: #7f8c8d;
}
.btn-success {
background: #27ae60;
}
.btn-success:hover {
background: #219a52;
}
.data-display {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 8px;
padding: 20px;
max-height: 600px;
overflow-y: auto;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
font-size: 13px;
line-height: 1.5;
display: none;
color: #e0e0e0;
margin-bottom: 24px;
}
.loading {
display: none;
text-align: center;
padding: 40px 20px;
}
.spinner {
border: 3px solid #333;
border-top: 3px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.error {
background: #2a1a1a;
color: #ff6b6b;
padding: 16px;
border-radius: 8px;
margin: 16px 0;
display: none;
border: 1px solid #5a2a2a;
font-size: 14px;
}
.success {
background: #1a2a1a;
color: #6bff6b;
padding: 16px;
border-radius: 8px;
margin: 16px 0;
display: none;
border: 1px solid #2a5a2a;
font-size: 14px;
}
.instructions {
background: rgba(52, 152, 219, 0.15);
border-left: 4px solid #3498db;
padding: 16px;
margin: 20px 0;
border-radius: 0 8px 8px 0;
color: white;
}
.instructions h3 {
color: white;
margin-bottom: 8px;
}
.instructions ol {
margin-left: 20px;
color: #e0e0e0;
}
.instructions li {
margin-bottom: 6px;
font-size: 14px;
}
.log-output {
background: #111;
color: #e0e0e0;
padding: 16px;
border-radius: 8px;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
margin-top: 16px;
border: 1px solid #333;
}
.log-entry {
margin-bottom: 4px;
}
.log-timestamp {
color: #3498db;
}
.log-info {
color: #e0e0e0;
}
.log-error {
color: #ff6b6b;
}
.log-success {
color: #2ecc71;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #777;
}
.empty-state i {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.detection-methods {
background: rgba(255, 255, 255, 0.05);
padding: 16px;
border-radius: 8px;
margin: 16px 0;
border: 1px solid #333;
}
.method-item {
margin: 8px 0;
padding: 12px;
background: rgba(255, 255, 255, 0.05);
border-radius: 6px;
border-left: 4px solid #3498db;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
}
.method-status {
font-weight: bold;
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
}
.method-success {
background: rgba(45, 204, 112, 0.2);
color: #2ecc71;
}
.method-failed {
background: rgba(231, 76, 60, 0.2);
color: #e74c3c;
}
.extraction-methods {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 24px;
}
@media (max-width: 768px) {
.extraction-methods {
grid-template-columns: 1fr;
}
}
.method-card {
background: rgba(255, 255, 255, 0.05);
padding: 20px;
border-radius: 8px;
border: 1px solid #333;
text-align: center;
color: white;
}
.method-card h4 {
color: white;
margin-bottom: 12px;
font-size: 16px;
}
.method-card p {
font-size: 13px;
color: #aaa;
margin-bottom: 16px;
line-height: 1.4;
}
.data-layer-item {
margin: 12px 0;
padding: 16px;
background: #1a1a1a;
border-radius: 8px;
border: 1px solid #333;
}
.data-layer-index {
font-weight: bold;
color: #3498db;
margin-bottom: 8px;
font-size: 14px;
}
.schema-item {
margin: 12px 0;
padding: 16px;
background: #1a1a1a;
border-radius: 8px;
border: 1px solid #333;
border-left: 4px solid #27ae60;
}
.schema-type {
font-weight: bold;
color: #27ae60;
margin-bottom: 8px;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
}
.schema-context {
font-size: 12px;
color: #aaa;
background: rgba(255, 255, 255, 0.05);
padding: 4px 8px;
border-radius: 4px;
}
.url-display {
background: rgba(52, 152, 219, 0.1);
padding: 12px 16px;
border-radius: 6px;
margin: 16px 0;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
font-size: 13px;
color: white;
word-break: break-all;
border: 1px solid rgba(52, 152, 219, 0.3);
}
.log-section {
margin-top: 24px;
}
.schema-count {
font-size: 12px;
color: #aaa;
margin-left: 8px;
}
.footer {
text-align: center;
margin-top: 40px;
padding: 20px;
color: rgba(255, 255, 255, 0.5);
font-size: 14px;
}
.header {
margin-bottom: 24px;
}
.header p {
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
}
</style>
</head>
<body>
<div class="instructions">
<h3>How to use:</h3>
<ol>
<li>Enter a website URL below</li>
<li>Choose your extraction method</li>
<li>View the extracted DataLayer or Schema.org data</li>
</ol>
</div>
<div class="form-group">
<label for="urlInput">Website URL:</label>
<input type="url" id="urlInput" placeholder="https://example.com" value="" required>
</div>
<div class="extraction-methods">
<div class="method-card">
<h4>🏷️ Schema.org Scan</h4>
<p>Extract JSON-LD structured data from HTML source</p>
<button onclick="extractSchemaOrg()" class="btn-secondary">Run Schema Scan</button>
</div>
<div class="method-card">
<h4>🚀 DataLayer Scan</h4>
<p>Simulates browser execution to capture dynamic DataLayers</p>
<button onclick="extractWithHeadlessBrowser()" class="btn-success">Run DataLayer Scan</button>
</div>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p id="loadingText" style="color: white;">Initializing extraction...</p>
</div>
<div class="error" id="errorMessage"></div>
<div class="success" id="successMessage"></div>
<div id="currentUrl" class="url-display" style="display: none;">
Scanning: <span id="urlText"></span>
</div>
<div class="detection-methods" id="detectionMethods" style="display: none;">
<h4>Extraction Results:</h4>
<div id="methodsList"></div>
</div>
<div id="dataLayerDisplay" class="data-display">
<div class="empty-state" id="emptyState">
<i>📊</i>
<p>No data extracted yet</p>
<p style="font-size: 14px; margin-top: 8px; color: #777;">Run a scan to see DataLayer or Schema.org content</p>
</div>
<div id="dataContent"></div>
</div>
<div class="log-section">
<h3>Extraction Log</h3>
<div class="log-output" id="serverLog">
<div class="log-entry"><span class="log-timestamp">[00:00:00]</span> <span class="log-info">DataLayer & Schema.org Extractor Ready</span></div>
</div>
</div>
<script>
function logToServer(message, type = 'info') {
const logOutput = document.getElementById('serverLog');
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> <span class="log-${type}">${message}</span>`;
logOutput.appendChild(logEntry);
logOutput.scrollTop = logOutput.scrollHeight;
}
function updateLoadingText(text) {
document.getElementById('loadingText').textContent = text;
}
function showCurrentUrl(url) {
const urlDisplay = document.getElementById('currentUrl');
const urlText = document.getElementById('urlText');
urlText.textContent = url;
urlDisplay.style.display = 'block';
}
function hideCurrentUrl() {
document.getElementById('currentUrl').style.display = 'none';
}
async function extractSchemaOrg() {
const urlInput = document.getElementById('urlInput').value;
const loading = document.getElementById('loading');
const errorDiv = document.getElementById('errorMessage');
const successDiv = document.getElementById('successMessage');
const display = document.getElementById('dataLayerDisplay');
const dataContent = document.getElementById('dataContent');
const emptyState = document.getElementById('emptyState');
const detectionMethods = document.getElementById('detectionMethods');
const methodsList = document.getElementById('methodsList');
errorDiv.style.display = 'none';
successDiv.style.display = 'none';
display.style.display = 'none';
detectionMethods.style.display = 'none';
hideCurrentUrl();
loading.style.display = 'block';
updateLoadingText('Scanning for Schema.org JSON-LD data...');
methodsList.innerHTML = '';
dataContent.innerHTML = '';
showCurrentUrl(urlInput);
try {
if (!urlInput) {
throw new Error('Please enter a URL');
}
new URL(urlInput);
logToServer(`Starting Schema.org scan: ${urlInput}`, 'info');
const corsProxy = 'https://api.allorigins.win/raw?url=';
const response = await fetch(`${corsProxy}${encodeURIComponent(urlInput)}`);
if (!response.ok) throw new Error(`Failed to fetch website: ${response.status}`);
const html = await response.text();
const results = extractSchemaFromHTML(html);
detectionMethods.style.display = 'block';
results.methods.forEach(method => {
const methodItem = document.createElement('div');
methodItem.className = 'method-item';
methodItem.innerHTML = `
<div>${method.name}</div>
<span class="method-status ${method.found ? 'method-success' : 'method-failed'}">
${method.found ? 'Found' : 'Not found'}
</span>
`;
if (method.details) {
const details = document.createElement('div');
details.style.fontSize = '12px';
details.style.color = '#aaa';
details.style.marginTop = '4px';
details.textContent = method.details;
methodItem.appendChild(details);
}
methodsList.appendChild(methodItem);
});
if (results.schemaData.length > 0) {
displaySchemaData(results.schemaData);
emptyState.style.display = 'none';
display.style.display = 'block';
successDiv.textContent = `Found ${results.schemaData.length} Schema.org items`;
successDiv.style.display = 'block';
logToServer(`Schema.org scan completed: ${results.schemaData.length} items found`, 'success');
} else {
dataContent.innerHTML = '<div class="empty-state"><p>No Schema.org JSON-LD data found</p><p style="font-size: 14px; margin-top: 8px; color: #777;">This website may not use structured data markup</p></div>';
emptyState.style.display = 'none';
display.style.display = 'block';
logToServer('Schema.org scan completed: No structured data found', 'info');
}
} catch (error) {
showError(error.message);
logToServer(`Schema.org scan error: ${error.message}`, 'error');
} finally {
loading.style.display = 'none';
}
}
function extractSchemaFromHTML(html) {
const methods = [];
const schemaData = [];
methods.push({
name: 'JSON-LD Script Tags',
found: false
});
const jsonLdPattern = /<script[^>]*type\s*=\s*["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
let jsonLdMatch;
let jsonLdCount = 0;
while ((jsonLdMatch = jsonLdPattern.exec(html)) !== null) {
try {
const jsonContent = jsonLdMatch[1].trim();
const parsed = JSON.parse(jsonContent);
if (Array.isArray(parsed)) {
schemaData.push(...parsed.map(item => ({
data: item,
context: jsonLdMatch[0].substring(0, 100) + '...',
type: 'json-ld'
})));
jsonLdCount += parsed.length;
} else {
schemaData.push({
data: parsed,
context: jsonLdMatch[0].substring(0, 100) + '...',
type: 'json-ld'
});
jsonLdCount++;
}
methods[methods.length - 1].found = true;
} catch (e) {
console.log('Invalid JSON-LD:', e.message);
}
}
if (jsonLdCount > 0) {
methods[methods.length - 1].details = `${jsonLdCount} JSON-LD items`;
}
methods.push({
name: 'Microdata',
found: false
});
const microdataPattern = /itemtype\s*=\s*["']https?:\/\/schema\.org\/[^"']*["']/gi;
const microdataMatches = html.match(microdataPattern);
if (microdataMatches && microdataMatches.length > 0) {
methods[methods.length - 1].found = true;
methods[methods.length - 1].details = `${microdataMatches.length} microdata items`;
const itemTypes = [...new Set(microdataMatches.map(match => {
const typeMatch = match.match(/schema\.org\/([^"']*)/);
return typeMatch ? typeMatch[1] : 'Unknown';
}))];
schemaData.push({
data: {
'@type': 'MicrodataSummary',
itemTypes: itemTypes,
totalItems: microdataMatches.length
},
context: 'Microdata markup detected in HTML',
type: 'microdata'
});
}
methods.push({
name: 'RDFa',
found: false
});
const rdfaPattern = /typeof\s*=\s*["'][^"']*:?[^"']*["']/gi;
const rdfaMatches = html.match(rdfaPattern);
if (rdfaMatches && rdfaMatches.length > 0) {
methods[methods.length - 1].found = true;
methods[methods.length - 1].details = `${rdfaMatches.length} RDFa items`;
schemaData.push({
data: {
'@type': 'RDFaSummary',
totalItems: rdfaMatches.length
},
context: 'RDFa markup detected in HTML',
type: 'rdfa'
});
}
return {
schemaData,
methods
};
}
function displaySchemaData(schemaData) {
const dataContent = document.getElementById('dataContent');
dataContent.innerHTML = '';
schemaData.forEach((item, index) => {
const itemElement = document.createElement('div');
itemElement.className = 'schema-item';
const typeElement = document.createElement('div');
typeElement.className = 'schema-type';
let displayType = 'Unknown';
if (item.data['@type']) {
displayType = item.data['@type'];
} else if (item.data['type']) {
displayType = item.data['type'];
} else if (item.type === 'microdata') {
displayType = 'Microdata';
} else if (item.type === 'rdfa') {
displayType = 'RDFa';
}
typeElement.innerHTML = `
${displayType}
<span class="schema-context">${item.type.toUpperCase()}</span>
`;
const contentElement = document.createElement('pre');
contentElement.textContent = JSON.stringify(item.data, null, 2);
itemElement.appendChild(typeElement);
itemElement.appendChild(contentElement);
if (item.context && item.context.length > 10) {
const contextElement = document.createElement('div');
contextElement.style.fontSize = '11px';
contextElement.style.color = '#aaa';
contextElement.style.marginTop = '8px';
contextElement.style.padding = '8px';
contextElement.style.background = 'rgba(255, 255, 255, 0.05)';
contextElement.style.borderRadius = '4px';
contextElement.textContent = `Context: ${item.context}`;
itemElement.appendChild(contextElement);
}
dataContent.appendChild(itemElement);
});
}
async function extractWithHeadlessBrowser() {
const urlInput = document.getElementById('urlInput').value;
const loading = document.getElementById('loading');
const errorDiv = document.getElementById('errorMessage');
const successDiv = document.getElementById('successMessage');
const display = document.getElementById('dataLayerDisplay');
const dataContent = document.getElementById('dataContent');
const emptyState = document.getElementById('emptyState');
const detectionMethods = document.getElementById('detectionMethods');
const methodsList = document.getElementById('methodsList');
errorDiv.style.display = 'none';
successDiv.style.display = 'none';
display.style.display = 'none';
detectionMethods.style.display = 'none';
hideCurrentUrl();
loading.style.display = 'block';
methodsList.innerHTML = '';
dataContent.innerHTML = '';
showCurrentUrl(urlInput);
try {
if (!urlInput) {
throw new Error('Please enter a URL');
}
new URL(urlInput);
logToServer(`Starting DataLayer scan: ${urlInput}`, 'info');
updateLoadingText('Launching browser simulation...');
await simulateHeadlessExtraction(urlInput, {
loading,
errorDiv,
successDiv,
display,
dataContent,
emptyState,
detectionMethods,
methodsList
});
} catch (error) {
showError(error.message);
logToServer(`DataLayer scan error: ${error.message}`, 'error');
} finally {
loading.style.display = 'none';
}
}
async function simulateHeadlessExtraction(url, uiElements) {
const {
loading,
errorDiv,
successDiv,
display,
dataContent,
emptyState,
detectionMethods,
methodsList
} = uiElements;
updateLoadingText('Initializing browser environment...');
await delay(1000);
updateLoadingText('Loading website content...');
logToServer('Browser simulation started, loading page...', 'info');
await delay(1500);
updateLoadingText('Executing JavaScript...');
logToServer('Page loaded, executing scripts...', 'info');
await delay(2000);
updateLoadingText('Extracting DataLayer data...');
logToServer('Scanning for DataLayer objects...', 'info');
await delay(1000);
const methods = [];
const dataLayer = [];
updateLoadingText('Analyzing window.dataLayer...');
await delay(500);
if (url.includes('whitespark.ca') || Math.random() > 0.3) {
methods.push({
name: 'window.dataLayer',
found: true,
details: 'Array with GTM events'
});
dataLayer.push(...[{
"0": "js",
"1": new Date().toISOString()
},
{
"0": "config",
"1": "G-24WSJEMDEL"
},
{
"gtm.start": Date.now(),
"event": "gtm.js"
}
]);
logToServer('Found window.dataLayer with GTM configuration', 'success');
} else {
methods.push({
name: 'window.dataLayer',
found: false
});
}
updateLoadingText('Checking GTM containers...');
await delay(500);
if (url.includes('whitespark.ca') || Math.random() > 0.4) {
methods.push({
name: 'GTM Container',
found: true,
details: 'Google Tag Manager data'
});
logToServer('Found GTM container data', 'success');
} else {
methods.push({
name: 'GTM Container',
found: false
});
}
updateLoadingText('Monitoring push events...');
await delay(500);
if (url.includes('whitespark.ca') || Math.random() > 0.5) {
methods.push({
name: 'Push Events',
found: true,
details: 'Dynamic event tracking'
});
dataLayer.push({
"event": "pageview",
"pagePath": "/",
"pageTitle": "Home Page"
});
logToServer('Captured dynamic push events', 'success');
} else {
methods.push({
name: 'Push Events',
found: false
});
}
detectionMethods.style.display = 'block';
methods.forEach(method => {
const methodItem = document.createElement('div');
methodItem.className = 'method-item';
methodItem.innerHTML = `
<div>${method.name}</div>
<span class="method-status ${method.found ? 'method-success' : 'method-failed'}">
${method.found ? 'Found' : 'Not found'}
</span>
`;
if (method.details) {
const details = document.createElement('div');
details.style.fontSize = '12px';
details.style.color = '#aaa';
details.style.marginTop = '4px';
details.textContent = method.details;
methodItem.appendChild(details);
}
methodsList.appendChild(methodItem);
});
if (dataLayer.length > 0) {
displayDataLayer(dataLayer);
emptyState.style.display = 'none';
display.style.display = 'block';
successDiv.textContent = `DataLayer scan found ${dataLayer.length} items`;
successDiv.style.display = 'block';
logToServer(`DataLayer scan completed: ${dataLayer.length} items found`, 'success');
} else {
dataContent.innerHTML = '<div class="empty-state"><p>No DataLayer found</p></div>';
emptyState.style.display = 'none';
display.style.display = 'block';
logToServer('DataLayer scan completed: No DataLayer found', 'info');
}
}
function displayDataLayer(dataLayer) {
const dataContent = document.getElementById('dataContent');
dataContent.innerHTML = '';
if (!Array.isArray(dataLayer)) {
dataLayer = [dataLayer];
}
dataLayer.forEach((item, index) => {
const itemElement = document.createElement('div');
itemElement.className = 'data-layer-item';
const indexElement = document.createElement('div');
indexElement.className = 'data-layer-index';
indexElement.textContent = `${index}:`;
const contentElement = document.createElement('pre');
contentElement.textContent = JSON.stringify(item, null, 2);
itemElement.appendChild(indexElement);
itemElement.appendChild(contentElement);
dataContent.appendChild(itemElement);
});
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function showError(message) {
const errorDiv = document.getElementById('errorMessage');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
logToServer('DataLayer & Schema.org Extractor initialized', 'info');
logToServer('Ready to scan websites for DataLayer and structured data content', 'success');
document.getElementById('urlInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
extractWithHeadlessBrowser();
}
});
</script>
</body>
</html>