- Remap 20 articles from generic team names (UK Data Services Legal Team, Analytics Team, Technical Team etc.) to matching named authors from the author database (Sarah Chen, David Martinez, Michael Thompson, etc.) - Add 5 new named authors to author-bio.php: Alex Kumar, David Thompson, Emily Roberts, Michael Chen, Sarah Mitchell - Eliminates author name/bio mismatch where team name showed but Editorial Team bio/role rendered instead
521 lines
25 KiB
PHP
521 lines
25 KiB
PHP
<?php
|
|
// Enhanced security headers
|
|
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
|
|
|
|
// Article-specific SEO variables
|
|
$article_title = "Cloud-Native Scraping Architecture for Enterprise Scale";
|
|
$article_description = "Design scalable, resilient web scraping infrastructure using modern cloud technologies and containerization. A comprehensive guide for UK enterprises.";
|
|
$article_keywords = "cloud-native web scraping, enterprise scraping architecture, scalable data extraction, containerized scraping, UK cloud infrastructure";
|
|
$article_author = "James Wilson";
|
|
$canonical_url = "https://ukdataservices.co.uk/blog/articles/cloud-native-scraping-architecture";
|
|
$article_published = "2025-05-25T09:00:00+00:00";
|
|
$article_modified = "2025-05-25T09:00:00+00:00";
|
|
$og_image = "https://ukdataservices.co.uk/assets/images/icon-automation.svg";
|
|
$read_time = 11;
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><?php echo htmlspecialchars($article_title); ?> | UK Data Services Blog</title>
|
|
<meta name="description" content="<?php echo htmlspecialchars($article_description); ?>">
|
|
<meta name="keywords" content="<?php echo htmlspecialchars($article_keywords); ?>">
|
|
<meta name="author" content="<?php echo htmlspecialchars($article_author); ?>">
|
|
<meta name="robots" content="index, follow">
|
|
<link rel="canonical" href="<?php echo htmlspecialchars($canonical_url); ?>">
|
|
|
|
<!-- Article-specific meta tags -->
|
|
<meta name="article:published_time" content="<?php echo $article_published; ?>">
|
|
<meta name="article:modified_time" content="<?php echo $article_modified; ?>">
|
|
<meta name="article:author" content="<?php echo htmlspecialchars($article_author); ?>">
|
|
<meta name="article:section" content="Technology">
|
|
<meta name="article:tag" content="Cloud Architecture, Web Scraping, Enterprise Technology, DevOps">
|
|
|
|
<!-- Preload critical resources -->
|
|
<link rel="preload" href="../../assets/css/main.css" as="style">
|
|
<link rel="preload" href="../../assets/images/ukds-main-logo.png" as="image">
|
|
|
|
<!-- Open Graph / Social Media -->
|
|
<meta property="og:type" content="article">
|
|
<meta property="og:url" content="<?php echo htmlspecialchars($canonical_url); ?>">
|
|
<meta property="og:title" content="<?php echo htmlspecialchars($article_title); ?>">
|
|
<meta property="og:description" content="<?php echo htmlspecialchars($article_description); ?>">
|
|
<meta property="og:image" content="<?php echo htmlspecialchars($og_image); ?>">
|
|
|
|
<!-- Twitter Card -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="<?php echo htmlspecialchars($article_title); ?>">
|
|
<meta name="twitter:description" content="<?php echo htmlspecialchars($article_description); ?>">
|
|
<meta name="twitter:image" content="<?php echo htmlspecialchars($og_image); ?>">
|
|
|
|
<!-- Favicon and App Icons -->
|
|
<link rel="icon" type="image/svg+xml" href="../../assets/images/favicon.svg">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="../../assets/images/apple-touch-icon.svg">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@300;400;500;600;700&family=Lato:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
|
|
<!-- Styles -->
|
|
<link rel="stylesheet" href="../../assets/css/main.css">
|
|
<link rel="stylesheet" href="../../assets/css/cro-enhancements.css">
|
|
|
|
<!-- Article Schema -->
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "Article",
|
|
"mainEntityOfPage": {
|
|
"@type": "WebPage",
|
|
"@id": "<?php echo htmlspecialchars($canonical_url); ?>"
|
|
},
|
|
"headline": "<?php echo htmlspecialchars($article_title); ?>",
|
|
"description": "<?php echo htmlspecialchars($article_description); ?>",
|
|
"image": "<?php echo htmlspecialchars($og_image); ?>",
|
|
"author": {
|
|
"@type": "Organization",
|
|
"name": "UK Data Services",
|
|
"url": "https://ukdataservices.co.uk"
|
|
},
|
|
"publisher": {
|
|
"@type": "Organization",
|
|
"name": "UK Data Services",
|
|
"logo": {
|
|
"@type": "ImageObject",
|
|
"url": "https://ukdataservices.co.uk/assets/images/ukds-main-logo.png"
|
|
}
|
|
},
|
|
"datePublished": "<?php echo $article_published; ?>",
|
|
"dateModified": "<?php echo $article_modified; ?>"
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<!-- Skip to content link for accessibility -->
|
|
<a href="#main-content" class="skip-to-content">Skip to main content</a>
|
|
|
|
<?php include($_SERVER["DOCUMENT_ROOT"] . "/includes/nav.php"); ?><!-- Article Content -->
|
|
<main id="main-content">
|
|
<article class="article-page">
|
|
<div class="container">
|
|
<div class="article-meta">
|
|
<span class="category"><a href="/blog/categories/web-scraping.php">Web Scraping</a></span>
|
|
<time datetime="2025-05-25">25 May 2025</time>
|
|
<span class="read-time">11 min read</span>
|
|
</div>
|
|
<header class="article-header">
|
|
<h1><?php echo htmlspecialchars($article_title); ?></h1>
|
|
<p class="article-lead"><?php echo htmlspecialchars($article_description); ?></p>
|
|
|
|
<div class="article-author">
|
|
<div class="author-info">
|
|
<span>By <?php echo htmlspecialchars($article_author); ?></span>
|
|
</div>
|
|
<div class="share-buttons">
|
|
<a href="https://www.linkedin.com/sharing/share-offsite/?url=<?php echo urlencode($canonical_url); ?>" class="share-button linkedin" aria-label="Share on LinkedIn" rel="noopener" target="_blank">
|
|
<img loading="lazy" src="../../assets/images/icon-linkedin.svg" alt="LinkedIn">
|
|
</a>
|
|
<a href="https://twitter.com/intent/tweet?url=<?php echo urlencode($canonical_url); ?>&text=<?php echo urlencode($article_title); ?>" class="share-button twitter" aria-label="Share on Twitter" rel="noopener" target="_blank">
|
|
<img loading="lazy" src="../../assets/images/icon-twitter.svg" alt="Twitter">
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="article-content">
|
|
<div class="content-wrapper">
|
|
<h2>The Evolution of Web Scraping Infrastructure</h2>
|
|
<p>Traditional web scraping architectures often struggle with modern enterprise requirements. Single-server setups, monolithic applications, and rigid infrastructures can't handle the scale, reliability, and flexibility demanded by today's data-driven organisations.</p>
|
|
|
|
<p>Cloud-native architectures offer a paradigm shift, providing unlimited scalability, built-in redundancy, and cost-effective resource utilisation. This guide explores how UK enterprises can build robust scraping infrastructures that grow with their needs.</p>
|
|
|
|
<h2>Core Principles of Cloud-Native Design</h2>
|
|
|
|
<h3>1. Microservices Architecture</h3>
|
|
<p>Break down your scraping system into discrete, manageable services:</p>
|
|
<ul>
|
|
<li><strong>Scheduler Service:</strong> Manages scraping tasks and priorities</li>
|
|
<li><strong>Scraper Workers:</strong> Execute individual scraping jobs</li>
|
|
<li><strong>Parser Service:</strong> Extracts structured data from raw content</li>
|
|
<li><strong>Storage Service:</strong> Handles data persistence and retrieval</li>
|
|
<li><strong>API Gateway:</strong> Provides unified access to all services</li>
|
|
</ul>
|
|
|
|
<h3>2. Containerisation</h3>
|
|
<p>Docker containers ensure consistency across environments:</p>
|
|
<pre><code>
|
|
# Example Dockerfile for scraper worker
|
|
FROM python:3.9-slim
|
|
|
|
WORKDIR /app
|
|
|
|
COPY requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
COPY . .
|
|
|
|
CMD ["python", "scraper_worker.py"]
|
|
</code></pre>
|
|
|
|
<h3>3. Orchestration with Kubernetes</h3>
|
|
<p>Kubernetes provides enterprise-grade container orchestration:</p>
|
|
<pre><code>
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: scraper-workers
|
|
spec:
|
|
replicas: 10
|
|
selector:
|
|
matchLabels:
|
|
app: scraper-worker
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: scraper-worker
|
|
spec:
|
|
containers:
|
|
- name: scraper
|
|
image: ukds/scraper-worker:latest
|
|
resources:
|
|
requests:
|
|
memory: "512Mi"
|
|
cpu: "500m"
|
|
limits:
|
|
memory: "1Gi"
|
|
cpu: "1000m"
|
|
</code></pre>
|
|
|
|
<h2>Architecture Components</h2>
|
|
|
|
<h3>Task Queue System</h3>
|
|
<p>Implement robust task distribution using message queues:</p>
|
|
<ul>
|
|
<li><strong>Amazon SQS:</strong> Managed queue service for AWS</li>
|
|
<li><strong>RabbitMQ:</strong> Open-source message broker</li>
|
|
<li><strong>Redis Queue:</strong> Lightweight option for smaller workloads</li>
|
|
<li><strong>Apache Kafka:</strong> High-throughput streaming platform</li>
|
|
</ul>
|
|
|
|
<h3>Worker Pool Management</h3>
|
|
<p>Dynamic scaling based on workload:</p>
|
|
<pre><code>
|
|
# Kubernetes Horizontal Pod Autoscaler
|
|
apiVersion: autoscaling/v2
|
|
kind: HorizontalPodAutoscaler
|
|
metadata:
|
|
name: scraper-hpa
|
|
spec:
|
|
scaleTargetRef:
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
name: scraper-workers
|
|
minReplicas: 5
|
|
maxReplicas: 100
|
|
metrics:
|
|
- type: Resource
|
|
resource:
|
|
name: cpu
|
|
target:
|
|
type: Utilization
|
|
averageUtilization: 70
|
|
- type: Pods
|
|
pods:
|
|
metric:
|
|
name: pending_tasks
|
|
target:
|
|
type: AverageValue
|
|
averageValue: "30"
|
|
</code></pre>
|
|
|
|
<h3>Distributed Storage</h3>
|
|
<p>Scalable storage solutions for different data types:</p>
|
|
<ul>
|
|
<li><strong>Object Storage:</strong> S3 for raw HTML and images</li>
|
|
<li><strong>Document Database:</strong> MongoDB for semi-structured data</li>
|
|
<li><strong>Data Warehouse:</strong> Snowflake or BigQuery for analytics</li>
|
|
<li><strong>Cache Layer:</strong> Redis for frequently accessed data</li>
|
|
</ul>
|
|
|
|
<h2>Handling Scale and Performance</h2>
|
|
|
|
<h3>Proxy Management</h3>
|
|
<p>Enterprise-scale scraping requires sophisticated proxy rotation:</p>
|
|
<pre><code>
|
|
class ProxyManager:
|
|
def __init__(self, proxy_pool):
|
|
self.proxies = proxy_pool
|
|
self.health_check_interval = 60
|
|
self.failure_threshold = 3
|
|
|
|
def get_proxy(self):
|
|
# Select healthy proxy with lowest recent usage
|
|
healthy_proxies = self.get_healthy_proxies()
|
|
return self.select_optimal_proxy(healthy_proxies)
|
|
|
|
def mark_failure(self, proxy):
|
|
# Track failures and remove bad proxies
|
|
self.failure_count[proxy] += 1
|
|
if self.failure_count[proxy] >= self.failure_threshold:
|
|
self.quarantine_proxy(proxy)
|
|
</code></pre>
|
|
|
|
<h3>Rate Limiting and Throttling</h3>
|
|
<p>Respect target websites while maximising throughput:</p>
|
|
<ul>
|
|
<li>Domain-specific rate limits</li>
|
|
<li>Adaptive throttling based on response times</li>
|
|
<li>Backoff strategies for errors</li>
|
|
<li>Distributed rate limiting across workers</li>
|
|
</ul>
|
|
|
|
<h3>Browser Automation at Scale</h3>
|
|
<p>Running headless browsers efficiently:</p>
|
|
<ul>
|
|
<li><strong>Playwright:</strong> Modern automation with better performance</li>
|
|
<li><strong>Puppeteer:</strong> Chrome/Chromium automation</li>
|
|
<li><strong>Selenium Grid:</strong> Distributed browser testing</li>
|
|
<li><strong>Browser pools:</strong> Reuse browser instances</li>
|
|
</ul>
|
|
|
|
<h2>Monitoring and Observability</h2>
|
|
|
|
<h3>Metrics Collection</h3>
|
|
<p>Essential metrics for scraping infrastructure:</p>
|
|
<ul>
|
|
<li>Tasks per second</li>
|
|
<li>Success/failure rates</li>
|
|
<li>Response times</li>
|
|
<li>Data quality scores</li>
|
|
<li>Resource utilisation</li>
|
|
<li>Cost per scrape</li>
|
|
</ul>
|
|
|
|
<h3>Logging Architecture</h3>
|
|
<p>Centralised logging for debugging and analysis:</p>
|
|
<pre><code>
|
|
# Structured logging example
|
|
{
|
|
"timestamp": "2025-05-25T10:30:45Z",
|
|
"level": "INFO",
|
|
"service": "scraper-worker",
|
|
"pod_id": "scraper-worker-7d9f8b-x2m4n",
|
|
"task_id": "task-123456",
|
|
"url": "https://example.com/products",
|
|
"status": "success",
|
|
"duration_ms": 1234,
|
|
"data_extracted": {
|
|
"products": 50,
|
|
"prices": 50,
|
|
"images": 150
|
|
}
|
|
}
|
|
</code></pre>
|
|
|
|
<h3>Alerting and Incident Response</h3>
|
|
<p>Proactive monitoring with automated responses:</p>
|
|
<ul>
|
|
<li>Anomaly detection for scraping patterns</li>
|
|
<li>Automated scaling triggers</li>
|
|
<li>Quality degradation alerts</li>
|
|
<li>Cost threshold warnings</li>
|
|
</ul>
|
|
|
|
<h2>Security Considerations</h2>
|
|
|
|
<h3>Network Security</h3>
|
|
<ul>
|
|
<li><strong>VPC Isolation:</strong> Private networks for internal communication</li>
|
|
<li><strong>Encryption:</strong> TLS for all external connections</li>
|
|
<li><strong>Firewall Rules:</strong> Strict ingress/egress controls</li>
|
|
<li><strong>API Authentication:</strong> OAuth2/JWT for service access</li>
|
|
</ul>
|
|
|
|
<h3>Data Security</h3>
|
|
<ul>
|
|
<li><strong>Encryption at Rest:</strong> Encrypt all stored data</li>
|
|
<li><strong>Access Controls:</strong> Role-based permissions</li>
|
|
<li><strong>Audit Logging:</strong> Track all data access</li>
|
|
<li><strong>Compliance:</strong> GDPR-compliant data handling</li>
|
|
</ul>
|
|
|
|
<h2>Cost Optimisation Strategies</h2>
|
|
|
|
<h3>Resource Optimisation</h3>
|
|
<ul>
|
|
<li><strong>Spot Instances:</strong> Use for non-critical workloads</li>
|
|
<li><strong>Reserved Capacity:</strong> Commit for predictable loads</li>
|
|
<li><strong>Auto-scaling:</strong> Scale down during quiet periods</li>
|
|
<li><strong>Resource Tagging:</strong> Track costs by project/client</li>
|
|
</ul>
|
|
|
|
<h3>Data Transfer Optimisation</h3>
|
|
<ul>
|
|
<li>Compress data before storage</li>
|
|
<li>Use CDN for frequently accessed content</li>
|
|
<li>Implement smart caching strategies</li>
|
|
<li>Minimise cross-region transfers</li>
|
|
</ul>
|
|
|
|
<h2>Implementation Roadmap</h2>
|
|
|
|
<h3>Phase 1: Foundation (Weeks 1-4)</h3>
|
|
<ol>
|
|
<li>Set up cloud accounts and networking</li>
|
|
<li>Implement basic containerisation</li>
|
|
<li>Deploy initial Kubernetes cluster</li>
|
|
<li>Create CI/CD pipelines</li>
|
|
</ol>
|
|
|
|
<h3>Phase 2: Core Services (Weeks 5-8)</h3>
|
|
<ol>
|
|
<li>Develop microservices architecture</li>
|
|
<li>Implement task queue system</li>
|
|
<li>Set up distributed storage</li>
|
|
<li>Create monitoring dashboard</li>
|
|
</ol>
|
|
|
|
<h3>Phase 3: Scale & Optimise (Weeks 9-12)</h3>
|
|
<ol>
|
|
<li>Implement auto-scaling policies</li>
|
|
<li>Optimise resource utilisation</li>
|
|
<li>Add advanced monitoring</li>
|
|
<li>Performance tuning</li>
|
|
</ol>
|
|
|
|
<h2>Real-World Performance Metrics</h2>
|
|
<p>What to expect from a well-architected cloud-native scraping system:</p>
|
|
<ul>
|
|
<li><strong>Throughput:</strong> 1M+ pages per hour</li>
|
|
<li><strong>Availability:</strong> 99.9% uptime</li>
|
|
<li><strong>Scalability:</strong> 10x surge capacity</li>
|
|
<li><strong>Cost:</strong> £0.001-0.01 per page scraped</li>
|
|
<li><strong>Latency:</strong> Sub-second task scheduling</li>
|
|
</ul>
|
|
|
|
<h2>Common Pitfalls and Solutions</h2>
|
|
|
|
<h3>Over-Engineering</h3>
|
|
<p><strong>Problem:</strong> Building for Google-scale when you need SME-scale<br>
|
|
<strong>Solution:</strong> Start simple, evolve based on actual needs</p>
|
|
|
|
<h3>Underestimating Complexity</h3>
|
|
<p><strong>Problem:</strong> Not planning for edge cases and failures<br>
|
|
<strong>Solution:</strong> Implement comprehensive error handling from day one</p>
|
|
|
|
<h3>Ignoring Costs</h3>
|
|
<p><strong>Problem:</strong> Surprise cloud bills from unoptimised resources<br>
|
|
<strong>Solution:</strong> Implement cost monitoring and budgets early</p>
|
|
|
|
<h2>Future-Proofing Your Architecture</h2>
|
|
<p>Design with tomorrow's requirements in mind:</p>
|
|
<ul>
|
|
<li><strong>AI Integration:</strong> Prepare for ML-based parsing and extraction</li>
|
|
<li><strong>Edge Computing:</strong> Consider edge nodes for geographic distribution</li>
|
|
<li><strong>Serverless Options:</strong> Evaluate functions for specific workloads</li>
|
|
<li><strong>Multi-Cloud:</strong> Avoid vendor lock-in with portable designs</li>
|
|
</ul>
|
|
|
|
<div class="article-cta">
|
|
<h3>Build Your Enterprise Scraping Infrastructure</h3>
|
|
<p>UK Data Services architects and implements cloud-native scraping solutions that scale with your business. Let our experts design a system tailored to your specific requirements.</p>
|
|
<a href="/quote" class="btn btn-primary">Get Architecture Consultation</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Related Articles -->
|
|
<aside class="related-articles">
|
|
<h3>Related Articles</h3>
|
|
<div class="related-grid">
|
|
<article class="related-card">
|
|
<span class="category">Web Scraping</span>
|
|
<h4><a href="javascript-heavy-sites-scraping.php">Scraping JavaScript-Heavy Sites: Advanced Techniques</a></h4>
|
|
<span class="read-time">6 min read</span>
|
|
<?php include($_SERVER['DOCUMENT_ROOT'] . '/includes/author-bio.php'); ?>
|
|
|
|
<?php include($_SERVER['DOCUMENT_ROOT'] . '/includes/article-footer.php'); ?>
|
|
</div>
|
|
</article>
|
|
<article class="related-card">
|
|
<span class="category">Data Analytics</span>
|
|
<h4><a href="data-quality-validation-pipelines.php">Building Robust Data Quality Validation Pipelines</a></h4>
|
|
<span class="read-time">9 min read</span>
|
|
<?php include($_SERVER['DOCUMENT_ROOT'] . '/includes/author-bio.php'); ?>
|
|
|
|
<?php include($_SERVER['DOCUMENT_ROOT'] . '/includes/article-footer.php'); ?>
|
|
</div>
|
|
</article>
|
|
<article class="related-card">
|
|
<span class="category">Technology</span>
|
|
<h4><a href="data-automation-strategies-uk-businesses.php">Data Automation Strategies for UK Businesses</a></h4>
|
|
<span class="read-time">9 min read</span>
|
|
<?php include($_SERVER['DOCUMENT_ROOT'] . '/includes/author-bio.php'); ?>
|
|
|
|
<?php include($_SERVER['DOCUMENT_ROOT'] . '/includes/article-footer.php'); ?>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
<?php include($_SERVER['DOCUMENT_ROOT'] . '/includes/author-bio.php'); ?>
|
|
|
|
<?php include($_SERVER['DOCUMENT_ROOT'] . '/includes/article-footer.php'); ?>
|
|
</div>
|
|
</article>
|
|
</main>
|
|
|
|
<!-- Footer -->
|
|
<footer class="footer">
|
|
<div class="container">
|
|
<div class="footer-content">
|
|
<div class="footer-section">
|
|
<div class="footer-logo">
|
|
<img loading="lazy" src="../../assets/images/logo-white.svg" alt="UK Data Services" loading="lazy">
|
|
</div>
|
|
<p>Enterprise data intelligence solutions for modern British business.</p>
|
|
</div>
|
|
|
|
<div class="footer-section">
|
|
<h3>Quick Links</h3>
|
|
<ul>
|
|
<li><a href="/#services">Services</a></li>
|
|
<li><a href="/blog/">Blog</a></li>
|
|
<li><a href="/case-studies/">Case Studies</a></li>
|
|
<li><a href="/about">About</a></li>
|
|
<li><a href="/#contact">Contact</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="footer-section">
|
|
<h3>Legal</h3>
|
|
<ul>
|
|
<li><a href="/privacy-policy">Privacy Policy</a></li>
|
|
<li><a href="/terms-of-service">Terms of Service</a></li>
|
|
<li><a href="/cookie-policy">Cookie Policy</a></li>
|
|
<li><a href="/gdpr-compliance">GDPR Compliance</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="footer-bottom">
|
|
<p>© <?php echo date('Y'); ?> UK Data Services. All rights reserved.</p>
|
|
<div class="social-links">
|
|
<a href="https://linkedin.com/company/uk-data-services" aria-label="LinkedIn" rel="noopener" target="_blank">
|
|
<img loading="lazy" src="../../assets/images/icon-linkedin.svg" alt="LinkedIn" loading="lazy">
|
|
</a>
|
|
<a href="https://twitter.com/ukdataservices" aria-label="Twitter" rel="noopener" target="_blank">
|
|
<img loading="lazy" src="../../assets/images/icon-twitter.svg" alt="Twitter" loading="lazy">
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<!-- Scripts -->
|
|
<script src="../../assets/js/main.js"></script>
|
|
<script src="../../assets/js/cro-enhancements.js"></script>
|
|
</body>
|
|
</html>
|