Montag, 25 August 2025

Top 5 diese Woche

Ähnliche Tutorials

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>
Vorheriges Tutorial
Nächstes Tutorial

Hier etwas für dich dabei?