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
- Der Benutzer gibt ein Keyword und eine Domain über ein Webformular ein.
- Das Script startet im Hintergrund einen Headless Browser (Puppeteer / Node.js), der die Google-Suchergebnisse aufruft.
- Die Ergebnisseite wird ausgelesen und auf Vorkommen der angegebenen Domain untersucht.
- Falls die Domain gefunden wird, gibt das Script die genaue Platzierung (Ranking-Position) zurück.
- 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>