Dienstag, 20 Januar 2026

Diese Woche am beliebtesten

Vertiefendes Material

SEO Headless Browser

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

  1. Keyword- & Domain-Abfrage
    • Eingabe eines Keywords und einer Domain
    • Ausgabe der genauen Platzierung der Domain in den Google-Suchergebnissen
  2. Konkurrenzanalyse
    • Ermittlung der Top 10 und Top 100 Domains für das eingegebene Keyword
    • Darstellung der direkten Wettbewerber, die um das Keyword ranken
  3. 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
  4. 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>
Dreamcodes Redaktion
Dreamcodes Redaktion
Qualität als Standard. Verantwortung als Prinzip. Jede Ressource auf Dreamcodes basiert auf geprüften Best Practices und fundierter Praxiserfahrung. Unser Anspruch ist ein belastbares Fundament statt experimenteller Lösungen. Die Integration und Absicherung der Inhalte liegt in Ihrem Ermessen. Wir liefern die fachliche Basis, die Verantwortung für den produktiven Einsatz verbleibt bei Ihnen.
Vorheriges Tutorial
Nächstes Tutorial

Vielleicht einen Blick WERT?