Montag, 25 August 2025

Top 5 diese Woche

Ähnliche Tutorials

Keyword Headless Browser

Dieses Projekt ist eine eigenständige PHP-Webseite, mit der überprüft werden kann, auf welcher Position eine bestimmte Domain bei Google für ein eingegebenes Keyword rankt.

Funktionsweise

  1. Der Benutzer gibt ein Keyword und eine Domain über ein Webformular ein.
  2. Das Script startet im Hintergrund einen Headless Browser (Puppeteer / Node.js), der die Google-Suchergebnisse aufruft.
  3. Die Ergebnisseite wird ausgelesen und auf Vorkommen der angegebenen Domain untersucht.
  4. Falls die Domain gefunden wird, gibt das Script die genaue Platzierung (Ranking-Position) zurück.
  5. Falls nicht, wird eine Meldung ausgegeben, dass die Domain nicht in den ersten Ergebnissen vertreten ist.

Vorteile gegenüber reinem PHP/cURL-Scraping

  • Umgehung von Problemen mit JavaScript-basierten Inhalten, die cURL nicht rendern kann
  • realistischere Ergebnisse, da ein kompletter Browserlauf durchgeführt wird
  • bessere Kompatibilität mit Googles dynamischer Auslieferung

Einschränkungen & Warnhinweis

  • Dieses Tool nutzt Scraping von Google-Suchergebnissen
  • Dies verstößt gegen die Google-Nutzungsbedingungen
  • Bei häufiger Nutzung können IP-Sperren, CAPTCHAs oder Blockierungen auftreten
  • Ergebnisse können variieren, abhängig von:
    • Standort / Spracheinstellungen
    • personalisierten Suchergebnissen
    • Google-Updates
<?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';

  // Package anlegen falls nicht vorhanden
  $pkg = $workdir . DIRECTORY_SEPARATOR . 'package.json';
  if (!file_exists($pkg)) {
    $pkgJson = json_encode([
      'name' => 'rankchecker-runtime',
      'private' => true,
      'type' => 'commonjs',
      'dependencies' => new stdClass()
    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    @file_put_contents($pkg, $pkgJson);
  }
  // Puppeteer installieren
  $cmd = 'cd ' . escapeshellarg($workdir) . ' && npm install puppeteer 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
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 TIMEOUT = parseInt(args.timeout || '30000',10)||30000;
const OUTFILE = args.out || '';

if (!KEYWORD || !DOMAIN) {
  process.stdout.write(JSON.stringify({ ok:false, error:'Fehlende Parameter' }));
  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 {
    await page.waitForTimeout(800);
    const selectors = [
      'button[aria-label="Alle akzeptieren"]',
      'button[aria-label="Ich stimme zu"]',
      'button[aria-label="Accept all"]',
      '#L2AGLb'
    ];
    for (const sel of selectors) {
      const el = await page.$(sel);
      if (el) { await el.click(); await page.waitForTimeout(600); return true; }
    }
    for (const f of page.mainFrame().childFrames()) {
      const btn = await f.$('button[aria-label="Alle akzeptieren"]');
      if (btn) { await btn.click(); await page.waitForTimeout(600); return true; }
    }
  } catch {}
  return false;
}

async function extractOrganic(page){
  return await page.evaluate(() => {
    function isOrganicAnchor(a){
      const href = a.getAttribute('href') || '';
      if (!href.startsWith('http')) return false;
      try {
        const u = new URL(href);
        const h = u.hostname;
        if (h.endsWith('.google.com') || h.endsWith('.google.de')) return false;
        if (h === 'webcache.googleusercontent.com') return false;
      } catch { return false; }
      return true;
    }
    const roots = ['#search','div[role="main"]'];
    const out = [];
    for (const sel of roots) {
      const r = document.querySelector(sel);
      if (!r) continue;
      const as = Array.from(r.querySelectorAll('a')).filter(isOrganicAnchor);
      for (const a of as) {
        const href = a.href;
        const tEl = a.querySelector('h3');
        const title = tEl ? tEl.textContent.trim() : (a.textContent || '').trim();
        if (!out.find(x => x.href === href)) out.push({ href, title });
      }
    }
    return out;
  });
}

(async () => {
  const started = 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: 1000 });

  let start = 0;
  let collected = [];
  let foundRank = -1;
  let pagesTried = 0;

  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(800);

    const results = await extractOrganic(page);
    for (const r of results) {
      const host = normalizeHost(r.href);
      if (!host) continue;
      if (!collected.find(x => x.href === r.href)) collected.push({ href: r.href, title: r.title, host });
    }
    for (let i = 0; i < collected.length; i++) {
      if (hostMatches(collected[i].host, DOMAIN)) { foundRank = i + 1; break; }
    }
    start += 10;
    pagesTried++;
  }

  const payload = {
    ok: true,
    keyword: KEYWORD,
    domain: DOMAIN,
    country: COUNTRY,
    lang: LANG,
    checkedResults: collected.length,
    rank: foundRank > 0 ? foundRank : null,
    found: foundRank > 0,
    pagesTried,
    ms: Date.now() - started,
    preview: collected.slice(0, 20).map((x,i)=>({ rank:i+1, host:x.host, title:x.title, url:x.href }))
  };

  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;
}

$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;

$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)
            . ' --timeout=30000'
            . ' --out='     . escapeshellarg($outFile)
            . ' 2>&1';
      $execOut = shell_exec('cd ' . escapeshellarg($workdir) . ' && ' . $cmd);
      // Ergebnis laden
      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. ' . substr((string)$execOut, 0, 2000);
      }
    }
  }
}
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Keyword Rank Checker</title>
<style>
:root { --bg:#f5f6fa; --card:#ffffff; --text:#2f3640; --muted:#7f8fa6; --primary:#273c75; --accent:#44bd32; --danger:#e84118; }
* { box-sizing: border-box; }
body { margin:0; font-family: Arial, sans-serif; background: var(--bg); color: var(--text); }
header { background: var(--primary); color:#fff; padding:24px 16px; text-align:center; }
header h1 { margin:0; font-size:28px; }
header p { margin:6px 0 0 0; opacity:.9; }
.container { max-width: 920px; margin: 24px auto; padding: 0 16px; }
.card { background: var(--card); border-radius: 12px; box-shadow: 0 6px 18px rgba(0,0,0,.08); padding: 20px; }
.form-grid { display:grid; grid-template-columns: 1fr 1fr; gap:16px; }
.form-row { display:flex; flex-direction:column; gap:8px; }
label { font-weight:600; font-size:14px; color:#3d3d3d; }
input[type=text], select { padding:12px; border:1px solid #dcdde1; border-radius:8px; font-size:15px; width:100%; }
.actions { margin-top: 8px; display:flex; gap:12px; flex-wrap:wrap; }
button, input[type=submit] { background: var(--accent); color:#fff; border:0; border-radius:8px; padding:12px 16px; font-size:15px; cursor:pointer; transition:.2s; }
button:hover, input[type=submit]:hover { filter:brightness(1.05); }
.note { margin-top:12px; font-size:13px; color: var(--muted); }
.result { margin-top:18px; border:2px solid #e1f3e3; background:#f2fff3; border-radius:10px; padding:16px; }
.errorbox { margin-top:18px; border:2px solid #ffd6d2; background:#fff2f0; border-radius:10px; padding:16px; color: var(--danger); }
.kv { display:grid; grid-template-columns: 180px 1fr; gap:8px 16px; margin-top:10px; }
.kv div:nth-child(odd) { color:#555; }
.table { width:100%; border-collapse: collapse; margin-top:14px; }
.table th, .table td { text-align:left; padding:10px; border-bottom:1px solid #eee; font-size:14px; }
.table th { background:#fafafa; }
.badge { display:inline-block; padding:6px 10px; border-radius:999px; font-weight:600; }
.badge.green { background:#e7f8ea; color:#166534; }
.badge.red { background:#fde7e7; color:#7f1d1d; }
footer { text-align:center; padding:18px; color:#666; margin: 24px auto; }
.footer-box { margin-top: 24px; padding: 14px; background: #f9f9f9; border-top: 1px solid #ddd; text-align: center; font-size: 13px; color: #666; border-radius:10px; }
.footer-box a { color: inherit; text-decoration: none; }
.footer-box a:hover { text-decoration: underline; }
@media (max-width:720px){ .form-grid { grid-template-columns:1fr; } }
.warning { margin:16px 0 0 0; padding:12px; background:#fff7e6; border:1px solid #ffe7ba; border-radius:8px; font-size:13px; }
</style>
</head>
<body>
<header>
  <h1>Keyword Rank Checker</h1>
  <p>Prüfe die Ranking Position einer Domain für ein Keyword bei Google</p>
</header>

<div class="container">
  <div class="card">
    <form method="get">
      <div class="form-grid">
        <div class="form-row">
          <label for="keyword">Keyword</label>
          <input type="text" id="keyword" name="keyword" placeholder="Beispiel. Immobilien Frankfurt" value="<?= safe($keyword) ?>" required>
        </div>
        <div class="form-row">
          <label for="domain">Domain</label>
          <input type="text" id="domain" name="domain" placeholder="Beispiel. beispiel.de" value="<?= safe($domain) ?>" required>
        </div>
        <div class="form-row">
          <label for="country">Land</label>
          <select id="country" name="country">
            <?php
              $countries = ['de'=>'Deutschland','at'=>'Österreich','ch'=>'Schweiz','us'=>'USA','gb'=>'UK','fr'=>'Frankreich','es'=>'Spanien','it'=>'Italien','nl'=>'Niederlande','pl'=>'Polen'];
              foreach ($countries as $code=>$label) {
                $sel = strtolower($country) === $code ? 'selected' : '';
                echo "<option value=\"".safe($code)."\" $sel>".safe($label)."</option>";
              }
            ?>
          </select>
        </div>
        <div class="form-row">
          <label for="lang">Sprache</label>
          <select id="lang" name="lang">
            <?php
              $langs = ['de'=>'Deutsch','en'=>'Englisch','fr'=>'Französisch','es'=>'Spanisch','it'=>'Italienisch','nl'=>'Niederländisch','pl'=>'Polnisch'];
              foreach ($langs as $code=>$label) {
                $sel = strtolower($lang) === $code ? 'selected' : '';
                echo "<option value=\"".safe($code)."\" $sel>".safe($label)."</option>";
              }
            ?>
          </select>
        </div>
        <div class="form-row">
          <label for="max">Maximale Treffer</label>
          <input type="text" id="max" name="max" value="<?= safe((string)$max) ?>">
        </div>
      </div>
      <div class="actions">
        <input type="submit" value="Ranking prüfen">
      </div>
      <p class="note">Hinweis. Ergebnisse können je nach Standort, Sprache und Zeitpunkt variieren</p>
      <div class="warning">
        Warnung. Das automatisierte Abrufen von Google Seiten kann gegen Nutzungsbedingungen verstoßen. Verwende offizielle Schnittstellen oder SERP APIs für den produktiven Einsatz
      </div>
    </form>

    <?php if ($runError): ?>
      <div class="errorbox">
        <strong>Fehler bei der Ausführung</strong><br>
        <?= nl2br(safe($runError)) ?>
      </div>
    <?php endif; ?>

    <?php if ($result && empty($runError)): ?>
      <div class="result">
        <div style="display:flex; align-items:center; gap:10px; flex-wrap:wrap;">
          <?php if ($result['found']): ?>
            <span class="badge green">Treffer gefunden</span>
            <div><strong>Platz</strong> <?= (int)$result['rank'] ?></div>
          <?php else: ?>
            <span class="badge red">Kein Treffer in den geprüften Ergebnissen</span>
          <?php endif; ?>
        </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']) ?></div>
          <div>Sprache</div><div><?= safe($result['lang']) ?></div>
          <div>Geprüfte Treffer</div><div><?= (int)$result['checkedResults'] ?></div>
          <div>Zeit</div><div><?= (int)$result['ms'] ?> ms</div>
        </div>

        <?php if (!empty($result['preview'])): ?>
          <h3 style="margin-top:16px;">Vorschau der ersten Treffer</h3>
          <table class="table">
            <thead>
              <tr>
                <th>Platz</th>
                <th>Host</th>
                <th>Titel</th>
                <th>URL</th>
              </tr>
            </thead>
            <tbody>
              <?php foreach ($result['preview'] as $row): ?>
                <tr>
                  <td><?= (int)$row['rank'] ?></td>
                  <td><?= safe($row['host']) ?></td>
                  <td><?= safe($row['title']) ?></td>
                  <td><a href="<?= safe($row['url']) ?>" target="_blank" rel="noopener"><?= safe($row['url']) ?></a></td>
                </tr>
              <?php endforeach; ?>
            </tbody>
          </table>
        <?php endif; ?>
      </div>
    <?php endif; ?>
  </div>

  <div class="footer-box">
    © <?= date('Y') ?> <a href="https://www.dreamcodes.net" target="_blank" rel="noopener">Dreamcodes.net</a> Alle Rechte vorbehalten
  </div>
</div>

<footer>
</footer>
</body>
</html>
Vorheriges Tutorial
Nächstes Tutorial

Hier etwas für dich dabei?