Dieses Projekt ist eine SEO-Analyse-Webseite in PHP, die mithilfe eines Headless Browsers (z. B. Puppeteer) Google-Suchergebnisse durchsucht und Ranking-Daten ermittelt.
Funktionsumfang
- Keyword- & Domain-Abfrage
- Eingabe eines Keywords und einer Domain
- Ausgabe der genauen Platzierung der Domain in den Google-Suchergebnissen
- Konkurrenzanalyse
- Ermittlung der Top 10 und Top 100 Domains für das eingegebene Keyword
- Darstellung der direkten Wettbewerber, die um das Keyword ranken
- Sichtbarkeitsanalyse
- Prüfung, ob die Domain in den Top 10 (erste Seite) oder in den Top 100 gelistet ist
- Ausgabe einer Übersicht über die Ranking-Verteilung
- SEO-Daten aus den Suchergebnissen
- Auslesen von Title und Meta Description, wie sie in den Google-Snippets erscheinen
- Analyse der URL-Struktur (Keyword im Title, URL, Meta Description)
- Optionale Speicherung dieser Daten für spätere Vergleiche
Vorteile gegenüber einfacher Keyword-Abfrage
- Umfassende Übersicht über eigene Position + Wettbewerber
- Einblick in SEO-Optimierung der Konkurrenz (Meta-Title, Description, URL)
- Transparente Darstellung der Sichtbarkeit in Top 10 und Top 100
Einschränkungen & Warnhinweis
- Das Projekt nutzt Scraping von Google-Suchergebnissen, was gegen die Google-Nutzungsbedingungen verstößt
- Häufige Abfragen können IP-Sperren, CAPTCHAs oder Verzögerungen verursachen
- Ergebnisse sind abhängig von Standort, Spracheinstellungen und personalisierten Suchergebnissen
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
function safe($s){ return htmlspecialchars($s ?? '', ENT_QUOTES, 'UTF-8'); }
function has_shell(){ return function_exists('shell_exec'); }
function node_exists(){ if(!has_shell()) return false; $v=@shell_exec('node -v 2>&1'); return is_string($v) && preg_match('/^v\d+\./', trim($v)); }
function ensure_workdir(){ $dir=__DIR__.DIRECTORY_SEPARATOR.'.rankchecker_runtime'; if(!is_dir($dir)) @mkdir($dir,0775,true); return $dir; }
function npm_install_if_needed($workdir){
if(!has_shell()) return 'Shell ist deaktiviert';
$nodeModules = $workdir . DIRECTORY_SEPARATOR . 'node_modules';
if(is_dir($nodeModules) && is_dir($nodeModules . DIRECTORY_SEPARATOR . 'puppeteer')) return 'ok';
$pkg = $workdir . DIRECTORY_SEPARATOR . 'package.json';
if(!file_exists($pkg)){
$pkgJson = json_encode([
'name'=>'rankchecker-runtime',
'private'=>true,
'type'=>'commonjs',
'dependencies'=>(object)['puppeteer'=>'*']
], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
@file_put_contents($pkg, $pkgJson);
}
$cmd = 'cd ' . escapeshellarg($workdir) . ' && npm install --no-audit --no-fund 2>&1';
$out = shell_exec($cmd);
if(is_dir($nodeModules . DIRECTORY_SEPARATOR . 'puppeteer')) return 'ok';
return "Installationsfehler. Ausgabe: " . substr((string)$out,0,2000);
}
function write_js($workdir){
$js = <<<'JS'
/**
* rank.js
* Puppeteer script that:
* - loads Google SERP (by country/lang)
* - extracts organic results (top)
* - detects featured snippet, PAA, local pack, top stories
* - visits first N results and collects SEO metrics (title, meta description, h1, canonical, robots, status)
* - writes JSON to --out file or stdout
*
* Note: selectors are heuristic; Google structure may change
*/
const fs = require('node:fs');
const { URL } = require('node:url');
const puppeteer = require('puppeteer');
const args = process.argv.slice(2).reduce((a,c)=>{
const m = c.match(/^--([^=]+)=(.*)$/);
if(m) a[m[1]] = m[2];
return a;
},{});
const KEYWORD = args.keyword || '';
const DOMAIN = (args.domain || '').toLowerCase().replace(/^https?:\/\//,'').replace(/^www\./,'');
const COUNTRY = (args.country || 'de').toLowerCase();
const LANG = (args.lang || 'de').toLowerCase();
const MAX_RESULTS = Math.min(parseInt(args.max || '100',10)||100, 100);
const SEO_VISIT = Math.min(parseInt(args.seo || '8',10)||8, 20);
const TIMEOUT = parseInt(args.timeout || '30000',10) || 30000;
const OUTFILE = args.out || '';
if(!KEYWORD || !DOMAIN){
process.stdout.write(JSON.stringify({ok:false, error:'missing parameters'}));
process.exit(0);
}
function normalizeHost(href){
try { return new URL(href).hostname.replace(/^www\./,''); } catch { return ''; }
}
function hostMatches(host, target){
if(!host || !target) return false;
return host === target || host.endsWith('.' + target);
}
async function acceptConsent(page){
// try some common consent buttons
const selectors = [
'button[aria-label="Alle akzeptieren"]',
'button[aria-label="Ich stimme zu"]',
'button[aria-label="Accept all"]',
'#L2AGLb',
'button:has-text("Alle akzeptieren")',
'button:has-text("Ich stimme zu")'
];
try{
await page.waitForTimeout(700);
for(const s of selectors){
try{
const el = await page.$(s);
if(el){ await el.click(); await page.waitForTimeout(600); return true; }
}catch{}
}
}catch(e){}
return false;
}
async function extractOrganic(page){
// returns list of {href,title,snippet}
return await page.evaluate(()=> {
const out = [];
function isOrganic(a){
const href = a.getAttribute('href') || '';
if(!href.startsWith('http')) return false;
try{
const u = new URL(href);
if(u.hostname.endsWith('.google.com') || u.hostname.endsWith('.google.de')) return false;
if(u.hostname === 'webcache.googleusercontent.com') return false;
}catch{ return false; }
return true;
}
const roots = ['#search','div[role="main"]'];
for(const sel of roots){
const root = document.querySelector(sel);
if(!root) continue;
const anchors = Array.from(root.querySelectorAll('a')).filter(isOrganic);
for(const a of anchors){
const href = a.href;
const titleEl = a.querySelector('h3');
const title = titleEl ? titleEl.innerText.trim() : (a.innerText||'').trim();
const snippetEl = a.closest('.g') ? a.closest('.g').querySelector('.VwiC3b') : null;
const snippet = snippetEl ? snippetEl.innerText.trim() : '';
if(!out.find(x=>x.href===href)){
out.push({ href, title, snippet });
}
}
}
return out;
});
}
async function detectSERPFeatures(page){
// heuristics to detect Featured Snippet, PAA, Local Pack, Top Stories
const data = { featured:false, featuredText:null, paa:[], localPack:false, topStories:false };
try{
// featured snippet: common containers
const featSelectors = [
'div[data-attrid="wa:/description"]',
'div#topstuff .ifM9O', // older
'div[data-attrid^="wa:"]',
'div[class*="bVh"]',
'.kp-wholepage' // generic
];
for(const s of featSelectors){
const el = await page.$(s);
if(el){
const text = await page.evaluate(e => e.innerText, el);
data.featured = true;
data.featuredText = text.slice(0,600);
break;
}
}
// PAA - People Also Ask (questions)
const paaEls = await page.$$('.related-question-pair, div[jsname="N760b"], div[role="question"]');
for(const e of paaEls.slice(0,8)){
try{
const txt = await page.evaluate(el => el.innerText, e);
if(txt) data.paa.push(txt.trim().slice(0,300));
}catch{}
}
// Local pack (maps)
const local = await page.$('g-scrolling-carousel, .VkpGBb, .xpdopen .section-scrollbox, .LjRbx');
if(local) data.localPack = true;
// Top stories
const top = await page.$('.g:has(a[href*="/articles/"]), div[data-attrid="kc:/news"] , .xpdopen .Escalate');
if(top) data.topStories = true;
}catch(e){}
return data;
}
(async()=>{
const startTime = Date.now();
const tld = COUNTRY === 'de' ? 'google.de' : 'google.com';
const baseUrl = `https://www.${tld}/search?hl=${LANG}&gl=${COUNTRY}`;
const browser = await puppeteer.launch({ headless:true, args:['--no-sandbox','--disable-setuid-sandbox','--disable-dev-shm-usage'] });
const page = await browser.newPage();
await page.setExtraHTTPHeaders({ 'Accept-Language': LANG });
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome Safari');
await page.setViewport({ width:1280, height:900 });
let start = 0;
let collected = [];
let foundRank = -1;
let pagesTried = 0;
// check pages until MAX_RESULTS or found
while(collected.length < MAX_RESULTS && start < 100 && foundRank < 0){
const url = `${baseUrl}&q=${encodeURIComponent(KEYWORD)}&num=100&start=${start}`;
try { await page.goto(url, { waitUntil:'domcontentloaded', timeout: TIMEOUT }); }
catch { try { await page.goto(url, { waitUntil:'load', timeout: TIMEOUT }); } catch{} }
await acceptConsent(page);
await page.waitForTimeout(700);
const serpFeatures = await detectSERPFeatures(page);
const organics = await extractOrganic(page);
for(const r of organics){
const host = normalizeHost(r.href);
if(!host) continue;
if(!collected.find(x=>x.href===r.href)){
collected.push({ href:r.href, title:r.title, snippet:r.snippet, host });
}
}
for(let i=0;i<collected.length;i++){
if(hostMatches(collected[i].host, DOMAIN)){ foundRank = i+1; break; }
}
pagesTried++;
start += 10;
}
// For SEO details, visit first SEO_VISIT results (or less)
const seoDetails = [];
const visitCount = Math.min(SEO_VISIT, collected.length);
for(let i=0;i<visitCount;i++){
const item = collected[i];
let meta = { rank: i+1, url: item.href, host: item.host, title: item.title || null, snippet: item.snippet || null, status: null, pageTitle:null, metaDescription:null, h1:null, canonical:null, robots:null };
try{
const resp = await page.goto(item.href, { waitUntil:'domcontentloaded', timeout: 20000 });
if(resp && resp.status) meta.status = resp.status();
await page.waitForTimeout(500);
const info = await page.evaluate(()=> {
const get = (sel)=>{ const e=document.querySelector(sel); return e? e.getAttribute('content') || e.innerText || null : null; };
const title = document.title || null;
const metaDesc = (document.querySelector('meta[name="description"]') || {}).content || null;
const h1 = document.querySelector('h1') ? document.querySelector('h1').innerText.trim() : null;
const canon = (document.querySelector('link[rel="canonical"]') || {}).href || null;
const robots = (document.querySelector('meta[name="robots"]') || {}).content || null;
return { title, metaDesc, h1, canon, robots };
});
meta.pageTitle = info.title || meta.pageTitle;
meta.metaDescription = info.metaDesc || null;
meta.h1 = info.h1 || null;
meta.canonical = info.canon || null;
meta.robots = info.robots || null;
}catch(e){
meta.error = String(e.message).slice(0,250);
}
seoDetails.push(meta);
}
// compute visibility and competitors
const total = collected.length;
const top10 = collected.slice(0,10);
const top10Hosts = top10.map(x=>x.host);
const top100Hosts = collected.map(x=>x.host);
// count occurrences for hosts
const hostCounts = {};
for(const h of top100Hosts){ hostCounts[h] = (hostCounts[h]||0)+1; }
// competitor domains sorted
const comp = Object.keys(hostCounts).map(h=>({host:h,count:hostCounts[h]})).sort((a,b)=>b.count-a.count);
const payload = {
ok:true,
keyword:KEYWORD,
domain:DOMAIN,
country:COUNTRY,
lang:LANG,
checkedResults: total,
rank: foundRank>0?foundRank:null,
found: foundRank>0,
pagesTried,
ms: Date.now()-startTime,
serpFeatures,
competitors: comp,
seoDetails,
preview: collected.slice(0,50).map((x,i)=>({ rank:i+1, host:x.host, title:x.title, url:x.href, snippet:x.snippet }))
};
if(OUTFILE){
try{ fs.writeFileSync(OUTFILE, JSON.stringify(payload,null,2),'utf8'); }
catch(e){ process.stdout.write(JSON.stringify({ok:false,error:String(e)})); }
} else {
process.stdout.write(JSON.stringify(payload));
}
await browser.close();
process.exit(0);
})();
JS;
$path = $workdir . DIRECTORY_SEPARATOR . 'rank.js';
@file_put_contents($path, $js);
return $path;
}
////////////////////
// Handle request //
////////////////////
$keyword = isset($_GET['keyword']) ? trim($_GET['keyword']) : '';
$domain = isset($_GET['domain']) ? trim($_GET['domain']) : '';
$country = isset($_GET['country']) ? trim($_GET['country']) : 'de';
$lang = isset($_GET['lang']) ? trim($_GET['lang']) : 'de';
$max = isset($_GET['max']) ? max(10, min(100, (int)$_GET['max'])) : 100;
$seo = isset($_GET['seo']) ? max(1, min(20, (int)$_GET['seo'])) : 8;
$runError = '';
$result = null;
if ($keyword !== '' && $domain !== '') {
if (!has_shell()) {
$runError = 'Server lässt keine Shell Befehle zu. Headless Browser kann nicht gestartet werden.';
} elseif (!node_exists()) {
$runError = 'Node.js ist nicht installiert. Bitte Node v18 oder neuer bereitstellen.';
} else {
$workdir = ensure_workdir();
$installState = npm_install_if_needed($workdir);
if ($installState !== 'ok') {
$runError = $installState;
} else {
$jsPath = write_js($workdir);
$outFile = $workdir . DIRECTORY_SEPARATOR . 'last_result.json';
@unlink($outFile);
$cmd = 'node ' . escapeshellarg($jsPath)
. ' --keyword=' . escapeshellarg($keyword)
. ' --domain=' . escapeshellarg($domain)
. ' --country=' . escapeshellarg($country)
. ' --lang=' . escapeshellarg($lang)
. ' --max=' . escapeshellarg((string)$max)
. ' --seo=' . escapeshellarg((string)$seo)
. ' --out=' . escapeshellarg($outFile)
. ' --timeout=30000'
. ' 2>&1';
$execOut = shell_exec('cd ' . escapeshellarg($workdir) . ' && ' . $cmd);
if (file_exists($outFile)) {
$json = @file_get_contents($outFile);
$data = json_decode($json, true);
if (is_array($data) && !empty($data['ok'])) $result = $data;
else $runError = 'Unklare Antwort des Headless Browsers.';
} else {
$runError = 'Keine Ausgabedatei erzeugt. Konsole-Ausgabe: ' . substr((string)$execOut,0,2000);
}
}
}
}
///////////////
// Render UI //
///////////////
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Erweiterter Keyword & SEO Checker</title>
<style>
:root{--bg:#f5f6fa;--card:#fff;--text:#222;--muted:#6b7280;--accent:#2563eb;--good:#10b981;--bad:#ef4444}
*{box-sizing:border-box}
body{margin:0;font-family:Inter,Arial,Helvetica,sans-serif;background:var(--bg);color:var(--text)}
.header{background:var(--accent);color:#fff;padding:22px 16px;text-align:center}
.header h1{margin:0;font-size:22px}
.container{max-width:1100px;margin:20px auto;padding:0 16px}
.card{background:var(--card);border-radius:12px;padding:18px;box-shadow:0 8px 24px rgba(15,23,42,0.06)}
.form-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}
.label{font-weight:600;margin-bottom:6px;color:#111827}
.input,select{width:100%;padding:10px;border:1px solid #e5e7eb;border-radius:8px}
.row{margin-top:12px}
.actions{margin-top:12px;display:flex;gap:10px;align-items:center}
.btn{background:var(--accent);color:#fff;padding:10px 14px;border-radius:10px;border:0;cursor:pointer}
.note{font-size:13px;color:var(--muted);margin-top:10px}
.warning{background:#fff7e6;border:1px solid #fde68a;padding:10px;border-radius:8px;margin-top:12px;color:#92400e}
.result{margin-top:16px;padding:14px;border-radius:10px;background:#f8fffb;border:1px solid #d1fae5}
.kv{display:grid;grid-template-columns:160px 1fr;gap:8px 12px}
.table{width:100%;border-collapse:collapse;margin-top:12px}
.table th,.table td{padding:8px;border-bottom:1px solid #eee;text-align:left;font-size:14px}
.small{font-size:13px;color:var(--muted)}
.footer-box{margin-top:18px;padding:12px;background:#fff;border:1px solid #eee;border-radius:10px;text-align:center;color:#666}
@media (max-width:900px){.form-grid{grid-template-columns:1fr}.kv{grid-template-columns:120px 1fr}}
</style>
</head>
<body>
<div class="header"><h1>Erweiterter Keyword & SEO Checker</h1></div>
<div class="container">
<div class="card">
<form method="get">
<div class="form-grid">
<div>
<div class="label">Keyword</div>
<input class="input" name="keyword" value="<?= safe($keyword) ?>" placeholder="z.B. Immobilien Frankfurt" required>
</div>
<div>
<div class="label">Domain</div>
<input class="input" name="domain" value="<?= safe($domain) ?>" placeholder="z.B. beispiel.de" required>
</div>
<div>
<div class="label">Land</div>
<select name="country" class="input">
<?php $countries=['de'=>'Deutschland','at'=>'Österreich','ch'=>'Schweiz','us'=>'USA','gb'=>'UK','fr'=>'Frankreich','es'=>'Spanien']; foreach($countries as $c=>$n){ $sel = strtolower($country)==$c?'selected':''; echo "<option value=\"".safe($c)."\" $sel>".safe($n)."</option>"; } ?>
</select>
</div>
</div>
<div class="row form-grid" style="grid-template-columns:1fr 1fr 1fr;">
<div>
<div class="label">Sprache</div>
<select name="lang" class="input">
<?php $langs=['de'=>'Deutsch','en'=>'Englisch','fr'=>'Französisch','es'=>'Spanisch']; foreach($langs as $c=>$n){ $sel = strtolower($lang)==$c?'selected':''; echo "<option value=\"".safe($c)."\" $sel>".safe($n)."</option>"; } ?>
</select>
</div>
<div>
<div class="label">Max Treffer (Top)</div>
<input class="input" name="max" value="<?= safe((string)$max) ?>">
</div>
<div>
<div class="label">SEO-Details Seiten</div>
<input class="input" name="seo" value="<?= safe((string)$seo) ?>">
</div>
</div>
<div class="actions">
<button class="btn" type="submit">Prüfung starten</button>
<div class="note">Hinweis: Ergebnisse können variieren. Tool ist zu Testzwecken.</div>
</div>
<div class="warning">Warnung: Automatisiertes Abrufen von Google kann gegen die Nutzungsbedingungen verstoßen. Nutze offizielle APIs für produktiven Einsatz.</div>
</form>
<?php if($runError): ?>
<div class="result" style="background:#fff4f4;border:1px solid #ffd6d2;color:var(--bad)">
<strong>Fehler:</strong><br><?= nl2br(safe($runError)) ?>
</div>
<?php endif; ?>
<?php if($result): ?>
<div class="result">
<div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
<?php if($result['found']): ?>
<div style="padding:6px 10px;border-radius:8px;background:#ecfdf5;color:#065f46;font-weight:600">Gefunden: Platz <?= (int)$result['rank'] ?></div>
<?php else: ?>
<div style="padding:6px 10px;border-radius:8px;background:#fff1f2;color:#7f1d1d;font-weight:600">Nicht in Top <?= (int)$result['checkedResults'] ?></div>
<?php endif; ?>
<div class="small">Geprüfte Treffer: <?= (int)$result['checkedResults'] ?> · Zeit: <?= (int)$result['ms'] ?> ms · Seiten geprüft für SEO: <?= count($result['seoDetails']) ?></div>
</div>
<div class="kv">
<div>Keyword</div><div><?= safe($result['keyword']) ?></div>
<div>Domain</div><div><?= safe($result['domain']) ?></div>
<div>Land</div><div><?= safe($result['country']).' · '.$result['lang'] ?></div>
<div>Seiten getriedet</div><div><?= (int)$result['pagesTried'] ?></div>
</div>
<h3 style="margin-top:12px;">SERP-Elemente</h3>
<table class="table">
<tr><th>Element</th><th>Ergebnis</th></tr>
<tr><td>Featured Snippet</td><td><?= $result['serpFeatures']['featured'] ? 'Ja' : 'Nein' ?> <?= $result['serpFeatures']['featuredText'] ? '<div class="small">'.safe(substr($result['serpFeatures']['featuredText'],0,200)).'…</div>':'' ?></td></tr>
<tr><td>People Also Ask (PAA)</td><td><?= count($result['serpFeatures']['paa']) ?> Fragen</td></tr>
<tr><td>Local Pack</td><td><?= $result['serpFeatures']['localPack'] ? 'Ja' : 'Nein' ?></td></tr>
<tr><td>Top Stories</td><td><?= $result['serpFeatures']['topStories'] ? 'Ja' : 'Nein' ?></td></tr>
</table>
<h3 style="margin-top:12px;">Konkurrenzdomains (häufigkeit in Top <?= (int)$result['checkedResults'] ?>)</h3>
<table class="table">
<thead><tr><th>Domain</th><th>Anzahl</th></tr></thead>
<tbody>
<?php foreach($result['competitors'] as $c): ?>
<tr><td><?= safe($c['host']) ?></td><td><?= (int)$c['count'] ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
<?php if(!empty($result['seoDetails'])): ?>
<h3 style="margin-top:12px;">SEO-Details (erste <?= count($result['seoDetails']) ?> Ergebnisse)</h3>
<table class="table">
<thead><tr><th>#</th><th>Host</th><th>Status</th><th>Title</th><th>Meta Description</th><th>H1</th><th>Canonical</th></tr></thead>
<tbody>
<?php foreach($result['seoDetails'] as $d): ?>
<tr>
<td><?= (int)$d['rank'] ?></td>
<td><?= safe($d['host']) ?></td>
<td><?= isset($d['status']) ? (int)$d['status'] : '—' ?></td>
<td><?= safe(mb_strimwidth($d['pageTitle'] ?? $d['title'] ?? '',0,70,'…')) ?></td>
<td><?= safe(mb_strimwidth($d['metaDescription'] ?? '',0,90,'…')) ?></td>
<td><?= safe(mb_strimwidth($d['h1'] ?? '',0,40,'…')) ?></td>
<td><?= safe(mb_strimwidth($d['canonical'] ?? '',0,60,'…')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<h3 style="margin-top:12px;">Vorschau Top Treffer (erste 50)</h3>
<table class="table">
<thead><tr><th>#</th><th>Host</th><th>Titel</th><th>URL</th></tr></thead>
<tbody>
<?php foreach($result['preview'] as $p): ?>
<tr>
<td><?= (int)$p['rank'] ?></td>
<td><?= safe($p['host']) ?></td>
<td><?= safe(mb_strimwidth($p['title'] ?? '',0,80,'…')) ?></td>
<td><a href="<?= safe($p['url']) ?>" target="_blank" rel="noopener"><?= safe($p['url']) ?></a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div style="margin-top:12px;">
<form method="get" style="display:inline;">
<input type="hidden" name="keyword" value="<?= safe($keyword) ?>">
<input type="hidden" name="domain" value="<?= safe($domain) ?>">
<input type="hidden" name="country" value="<?= safe($country) ?>">
<input type="hidden" name="lang" value="<?= safe($lang) ?>">
<input type="hidden" name="max" value="<?= safe((string)$max) ?>">
<input type="hidden" name="seo" value="<?= safe((string)$seo) ?>">
<button class="btn" type="submit">Erneut prüfen</button>
</form>
<a class="btn" style="background:#6b7280;margin-left:8px;" href="javascript:location.reload()">Seite neu laden</a>
</div>
</div>
<?php endif; ?>
<div class="footer-box">
© <?= date('Y') ?> <a href="https://www.dreamcodes.net" target="_blank" rel="noopener">Dreamcodes.net</a> – Alle Rechte vorbehalten
</div>
</div>
</div>
</body>
</html>