Képzeld el, hogy a kezedben van a kulcs az információhoz. Nem csupán felhasználóként, hanem alkotóként. Érdekelne, hogyan működik a kulisszák mögött egy olyan gigantikus rendszer, mint a Google, és szeretnél valami hasonlót – ha jóval kisebb léptékben is – magad is létrehozni? Akkor jó helyen jársz! Ebben a cikkben elmerülünk a PHP-alapú keresőmotor fejlesztésének izgalmas világában, lépésről lépésre végigvezetve téged az alapoktól a működő prototípusig.
A „saját Google” kifejezés természetesen erős túlzás. A valóságban egy Google kaliberű keresőrendszer milliárd dolláros befektetéseket, több ezer mérnök munkáját és több millió szervert igényel. Azonban az alapelvek megértése és egy egyszerűbb, ám funkcionális saját kereső felépítése egy kiváló tanulási folyamat, ami rengeteget ad a webes technológiák és az adatfeldolgozás megértéséhez. Vágjunk is bele!
Miért épp PHP? 🤔 Az ideális választás a kezdéshez
Sokan talán meglepődnek, hogy egy komplex feladathoz, mint egy keresőmotor, miért éppen a PHP-t javasoljuk. Nos, a válasz egyszerű: a PHP rendkívül sokoldalú, széles körben elterjedt, könnyen tanulható és gyorsan prototipizálható nyelv, különösen webes környezetben. A szerveroldali szkriptekhez ideális, és kiválóan alkalmas az alacsonyabb szintű webes interakciók kezelésére, mint például az URL-ek lekérdezése vagy a HTML tartalom elemzése. Ráadásul hatalmas közössége és kiterjedt könyvtár-támogatása is van, ami nagyban megkönnyíti a fejlesztést.
Bár léteznek specializáltabb megoldások, mint az Elasticsearch vagy a Solr, ezek használata feltételez bizonyos előzetes tudást. Mi viszont most tényleg az alapoktól építkezünk, és a PHP tökéletes eszköz arra, hogy megértsük a motorháztető alatti működési elveket anélkül, hogy túl sok absztrakciós réteggel kellene megküzdenünk.
A keresőmotor alapkövei: Miből is áll egy ilyen rendszer? 🧱
Mielőtt belevágnánk a kódolásba, értsük meg, milyen főbb komponensekből épül fel egy keresőmotor. Gondolj egy házra: vannak alapjai, falai, tetője. Egy kereső esetében ezek a következőek:
1. Pók (Crawler vagy Spider) 🕸️
Ez a program járja be az internetet vagy egy meghatározott webhelygyűjteményt. Feladata a weboldalak letöltése, a bennük található linkek megtalálása és újabb oldalak felfedezése. Olyan, mint egy digitális felfedező, aki folyamatosan kutat új „földrészek” (weboldalak) után.
2. Indexelő (Indexer) 📚
Miután a pók letöltött egy oldalt, az indexelő veszi át a stafétát. Ez a komponens dolgozza fel a nyers HTML tartalmat: kinyeri a releváns szöveget, eltávolítja a „stop szavakat” (pl. „a”, „az”, „és”), és szavakra bontja (tokenizálja) a szöveget. Ezután tárolja ezeket a szavakat, azok előfordulási helyét és gyakoriságát egy hatékonyan kereshető struktúrában, az úgynevezett fordított indexben (inverted index).
3. Adatbázis (Database) 💾
Ez a rendszer memóriája. Itt tárolódnak a pók által talált oldalak adatai (URL, cím, tartalom, utolsó indexelés ideje) és az indexelő által feldolgozott szavak, valamint azok kapcsolata az oldalakhoz. Egy jól megtervezett adatbázis tervezés kulcsfontosságú a gyors és hatékony működéshez.
4. Keresőfelület és Lekérdezés Feldolgozó (Search Interface & Query Processor) 🔍
Ez az, amit a felhasználók látnak és használnak: a keresőmező, ahová beírják a kérdésüket. A lekérdezés feldolgozó veszi át ezt a bemenetet, megtisztítja, és az adatbázisban keresi a releváns oldalakat a megadott kulcsszavak alapján.
5. Rangsoroló Algoritmus (Ranking Algorithm) 🏆
Miután a lekérdezés feldolgozó megtalálta az összes releváns oldalt, a rangsoroló algoritmus feladata, hogy eldöntse, milyen sorrendben mutassa meg azokat. Ez a komponens a kereső agya, ami dönti el, melyek a „legjobb” találatok. Egy egyszerű rendszernél ez lehet a kulcsszavak előfordulási gyakorisága, összetettebb rendszereknél viszont figyelembe veszi a linkek számát, minőségét, a tartalom frissességét és még számos más tényezőt.
Lépésről lépésre: A PHP-alapú kereső fejlesztése 🚀
1. Az alapok lefektetése: Adatbázis tervezése (MySQL)
Kezdjük az adatbázissal! Egy egyszerű MySQL séma elegendő lesz. Szükségünk van néhány táblára:
pages
: Az indexelt oldalak tárolására.id
(INT, PRIMARY KEY, AUTO_INCREMENT)url
(VARCHAR(255), UNIQUE)title
(VARCHAR(255))description
(TEXT) – Meta leírás vagy első pár sorlast_indexed
(DATETIME)
words
: A feldolgozott egyedi szavak tárolására.id
(INT, PRIMARY KEY, AUTO_INCREMENT)word
(VARCHAR(100), UNIQUE)
page_word_index
: A fordított index, ami összeköti a szavakat az oldalakkal és tárolja az előfordulásokat.page_id
(INT, FOREIGN KEY a pages.id-re)word_id
(INT, FOREIGN KEY a words.id-re)frequency
(INT) – Hányszor fordul elő a szó az oldalon- PRIMARY KEY (
page_id
,word_id
)
Íme a SQL kód a táblák létrehozásához:
CREATE TABLE pages (
id INT AUTO_INCREMENT PRIMARY KEY,
url VARCHAR(255) UNIQUE NOT NULL,
title VARCHAR(255),
description TEXT,
last_indexed DATETIME
);
CREATE TABLE words (
id INT AUTO_INCREMENT PRIMARY KEY,
word VARCHAR(100) UNIQUE NOT NULL
);
CREATE TABLE page_word_index (
page_id INT NOT NULL,
word_id INT NOT NULL,
frequency INT DEFAULT 0,
PRIMARY KEY (page_id, word_id),
FOREIGN KEY (page_id) REFERENCES pages(id) ON DELETE CASCADE,
FOREIGN KEY (word_id) REFERENCES words(id) ON DELETE CASCADE
);
2. A Pók (Crawler) megírása PHP-ban 🕸️
Ez lesz az első PHP szkriptünk. Szükségünk lesz a cURL
-re az oldalak letöltéséhez és a DOMDocument
-re a HTML elemzéséhez.
<?php
require_once 'config.php'; // Adatbázis kapcsolódási adatok
function crawlPage($url) {
echo "Keresés az URL-en: " . $url . "n";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// User-Agent beállítása, hogy ne tűnjünk botnak
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MyPHPCrawler/1.0)');
// Rövid késleltetés, hogy ne terheljük túl a szervert
usleep(500000); // 0.5 másodperc
$html = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200 || $html === false) {
echo "Hiba az URL letöltésekor: " . $url . " (HTTP Kód: " . $httpCode . ")n";
return [];
}
// HTML elemzés a DOMDocument segítségével
$dom = new DOMDocument();
@$dom->loadHTML($html); // A @ elnyomja a HTML parsing hibákat
$xpath = new DOMXPath($dom);
$links = [];
$hrefs = $xpath->evaluate("//a/@href");
foreach ($hrefs as $href) {
$link = trim($href->nodeValue);
// Abszolút URL-re konvertálás
$absoluteLink = resolveUrl($url, $link);
// Csak a HTTP/HTTPS linkek érdekelnek
if (filter_var($absoluteLink, FILTER_VALIDATE_URL) &&
(strpos($absoluteLink, 'http://') === 0 || strpos($absoluteLink, 'https://') === 0)
) {
$links[] = $absoluteLink;
}
}
// Oldal címének és leírásának kinyerése
$titleNodes = $xpath->evaluate("//title");
$title = $titleNodes->length > 0 ? $titleNodes[0]->nodeValue : 'Nincs cím';
$descriptionNodes = $xpath->evaluate("//meta[@name='description']/@content");
$description = $descriptionNodes->length > 0 ? $descriptionNodes[0]->nodeValue : 'Nincs leírás';
// A tartalom eltávolítása a script és style tag-ekből
$cleanHtml = preg_replace('/<scriptb[^>]*>.*?</script>/is', '', $html);
$cleanHtml = preg_replace('/<styleb[^>]*>.*?</style>/is', '', $cleanHtml);
$content = strip_tags($cleanHtml); // Minden HTML tag eltávolítása
// Oldal mentése az adatbázisba és az indexelés elindítása
savePageAndIndex($url, $title, $description, $content);
return array_unique($links);
}
function resolveUrl($base, $relative) {
// Nagyon egyszerű URL feloldás, élesben ennél robusztusabb kell
if (substr($relative, 0, 4) == 'http') {
return $relative;
}
//... (komplexebb URL feloldási logika ide)
return rtrim($base, '/') . '/' . ltrim($relative, '/');
}
// Kezdő URL-ek listája
$queue = ['http://localhost/mywebsite/']; // Példa
$visited = [];
while (!empty($queue) && count($visited) < 100) { // Max 100 oldal a teszthez
$currentUrl = array_shift($queue);
if (!in_array($currentUrl, $visited)) {
$visited[] = $currentUrl;
$newLinks = crawlPage($currentUrl);
foreach ($newLinks as $link) {
if (!in_array($link, $visited) && !in_array($link, $queue)) {
$queue[] = $link;
}
}
}
}
echo "Keresés befejezve. Indexelt oldalak száma: " . count($visited) . "n";
?>
Fontos megjegyzés: A fenti kód egy alapvető demonstráció. Egy valós web crawler sokkal robusztusabb, kezeli a hibákat, a relatív URL-eket, a robots.txt
fájlt, és a duplikált tartalmakat. A resolveUrl
függvény is jóval komplexebb lenne.
3. Az Indexelő (Indexer) munkája 📚
A savePageAndIndex
függvény felelős az oldal tartalmának feldolgozásáért és az adatbázisba mentéséért. Itt történik a teljes szöveges keresés előkészítése.
<?php
// config.php - adatbázis kapcsolódás
$db = new PDO('mysql:host=localhost;dbname=my_search_engine', 'root', '');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
function savePageAndIndex($url, $title, $description, $content) {
global $db;
try {
$db->beginTransaction();
// Ellenőrizzük, létezik-e már az oldal
$stmt = $db->prepare("SELECT id FROM pages WHERE url = ?");
$stmt->execute([$url]);
$pageId = $stmt->fetchColumn();
if (!$pageId) {
$stmt = $db->prepare("INSERT INTO pages (url, title, description, last_indexed) VALUES (?, ?, ?, NOW())");
$stmt->execute([$url, $title, $description]);
$pageId = $db->lastInsertId();
} else {
// Frissítés, ha már létezik
$stmt = $db->prepare("UPDATE pages SET title = ?, description = ?, last_indexed = NOW() WHERE id = ?");
$stmt->execute([$title, $description, $pageId]);
// Töröljük a régi indexbejegyzéseket az adott oldalhoz
$stmt = $db->prepare("DELETE FROM page_word_index WHERE page_id = ?");
$stmt->execute([$pageId]);
}
// Szavak feldolgozása
$words = getWordsFromString($content);
$wordFrequencies = array_count_values($words);
foreach ($wordFrequencies as $word => $frequency) {
if (mb_strlen($word) < 3) continue; // Túl rövid szavak kihagyása
// Ellenőrizzük, létezik-e már a szó
$stmt = $db->prepare("SELECT id FROM words WHERE word = ?");
$stmt->execute([$word]);
$wordId = $stmt->fetchColumn();
if (!$wordId) {
$stmt = $db->prepare("INSERT INTO words (word) VALUES (?)");
$stmt->execute([$word]);
$wordId = $db->lastInsertId();
}
// Index bejegyzés létrehozása/frissítése
$stmt = $db->prepare("INSERT INTO page_word_index (page_id, word_id, frequency) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE frequency = ?");
$stmt->execute([$pageId, $wordId, $frequency, $frequency]);
}
$db->commit();
echo "Oldal indexelve: " . $url . "n";
} catch (PDOException $e) {
$db->rollBack();
echo "Adatbázis hiba indexeléskor: " . $e->getMessage() . "n";
}
}
function getWordsFromString($text) {
// Átalakítás kisbetűsre, speciális karakterek eltávolítása (kivéve ékezetek)
$text = mb_strtolower($text, 'UTF-8');
$text = preg_replace('/[^a-z0-9áéíóöőúüűs]/u', ' ', $text);
$words = preg_split('/s+/', $text, -1, PREG_SPLIT_NO_EMPTY);
// Stop szavak kiszűrése (példa, bővíthető)
$stopWords = ['a', 'az', 'és', 'vagy', 'egy', 'is', 'nem', 'mint', 'hogy', 'de'];
return array_diff($words, $stopWords);
}
?>
Ez a szkript már képes letölteni egy oldalt, kinyerni a címet, leírást és a tiszta szöveget, majd azt szavakra bontva beindexeli az adatbázisba. A getWordsFromString
függvény kulcsszerepet játszik a szöveg előkészítésében.
4. A Keresőfelület és a Lekérdezés Feldolgozása 🔍
Most építsük meg a felhasználói felületet! Ez egy egyszerű HTML űrlap és egy PHP szkript lesz.
<!-- index.php -->
<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Saját PHP Keresőm</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.search-form { margin-bottom: 30px; }
.search-form input[type="text"] { width: 300px; padding: 8px; font-size: 16px; }
.search-form input[type="submit"] { padding: 8px 15px; font-size: 16px; cursor: pointer; }
.result-item { border: 1px solid #eee; padding: 15px; margin-bottom: 15px; border-radius: 5px; }
.result-item h3 { margin-top: 0; }
.result-item a { color: #006621; text-decoration: none; }
.result-item a:hover { text-decoration: underline; }
.result-item .url { color: #006621; font-size: 0.9em; }
.result-item p { color: #545454; }
</style>
</head>
<body>
<h1>Keresés a Saját Indexemben</h1>
<form action="index.php" method="get" class="search-form">
<input type="text" name="q" placeholder="Keresőszó..." value="<?php echo htmlspecialchars($_GET['q'] ?? ''); ?>">
<input type="submit" value="Keresés">
</form>
<div id="results">
<?php
if (isset($_GET['q']) && $_GET['q'] !== '') {
require_once 'config.php'; // Adatbázis kapcsolódás
$query = trim($_GET['q']);
$searchWords = getWordsFromString($query); // Ugyanaz a függvény, mint az indexelésnél
if (!empty($searchWords)) {
$placeholders = implode(',', array_fill(0, count($searchWords), '?'));
$sql = "
SELECT
p.id, p.url, p.title, p.description,
SUM(pwi.frequency) AS score
FROM
pages p
JOIN
page_word_index pwi ON p.id = pwi.page_id
JOIN
words w ON pwi.word_id = w.id
WHERE
w.word IN ($placeholders)
GROUP BY
p.id, p.url, p.title, p.description
ORDER BY
score DESC
LIMIT 20
";
$stmt = $db->prepare($sql);
$stmt->execute($searchWords);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($results) > 0) {
echo "<h2>Találatok a(z) '<strong>" . htmlspecialchars($query) . "</strong>' kifejezésre:</h2>";
foreach ($results as $result) {
echo "<div class='result-item'>";
echo "<h3><a href='" . htmlspecialchars($result['url']) . "' target='_blank'>" . htmlspecialchars($result['title']) . "</a></h3>";
echo "<div class='url'>" . htmlspecialchars($result['url']) . "</div>";
echo "<p>" . htmlspecialchars(substr($result['description'] ?: 'Nincs leírás.', 0, 200)) . "...</p>";
echo "<p><small>Relevancia pont: " . $result['score'] . "</small></p>";
echo "</div>";
}
} else {
echo "<h2>Nincs találat a(z) '<strong>" . htmlspecialchars($query) . "</strong>' kifejezésre.</h2>";
}
} else {
echo "<h2>Kérjük, adjon meg érvényes kulcsszavakat.</h2>";
}
}
?>
</div>
</body>
</html>
5. Rangsorolás és találatok megjelenítése 🏆
A fenti PHP szkriptben a rangsoroló algoritmus egy nagyon egyszerű megközelítést alkalmaz: a SUM(pwi.frequency) AS score
segítségével azokat az oldalakat sorolja előre, ahol a keresett szavak összesített gyakorisága magasabb. Ez egy alapvető, de jól érthető módszer a relevanciára. Természetesen egy valós keresőrendszer ennél jóval összetettebb, figyelembe veszi a szavak pozícióját (címben, bekezdés elején stb.), a linkek minőségét és mennyiségét (PageRank-szerű elvek), a tartalom frissességét és még számtalan más paramétert. De kezdetnek ez a rangsoroló algoritmus tökéletes.
A "Saját Google" korlátai és a valóság 🤯
Miután megépítettük ezt az egyszerű keresőt, fontos, hogy reálisan lássuk a képességeit. A most elkészített rendszer egy kiváló proof-of-concept, ami bemutatja az alapvető működési elveket. Azonban óriási különbség van egy ilyen hobbi projekt és egy globális keresőmotor között. Itt jön az a bizonyos "vélemény valós adatokon alapulva":
"A Google nem véletlenül lett a világ legnagyobb keresőmotorja. Valós adatok szerint 2023-ban a Google több mint 50 milliárd weboldalt indexel, naponta több milliárd keresést dolgoz fel, és ehhez több százezer szerver, valamint egy rendkívül komplex, elosztott infrastruktúra szükséges. A mi kis PHP-alapú keresőnk valószínűleg már néhány ezer oldalnál komoly teljesítményproblémákba ütközne, és sosem tudna felérni a Google fejlett nyelvi modellezéséhez, mesterséges intelligencia alapú rangsorolásához vagy a felhasználói szándék megértéséhez."
Ez a projekt nem a Google versenytársa, hanem egy nagyszerű tanulási eszköz, ami betekintést enged a nagy rendszerek mögé. A valóságban az "egy Google-szerű" kereső felépítése olyan kihívásokkal jár, mint a petabájtnyi adat kezelése, a valós idejű indexelés, a hibatűrés, a skálázhatóság, a szinonimák felismerése, a természetes nyelvi feldolgozás (NLP) és a gépi tanulás. Ezek olyan szintek, amelyeket egyetlen PHP szkript nem tud elérni.
Jó tanácsok és jövőbeli fejlesztések ✨
Ha tovább szeretnéd fejleszteni a saját PHP kereső rendszeredet, íme néhány ötlet:
- Késleltetett Indexelés: Ne indexelj azonnal, hanem gyűjtsd össze az oldalakat egy "várólistára" (queue), és egy különálló, időzített szkript (cron job) indexelje be őket.
- Robusztusabb Pók: Kezeld a
robots.txt
fájlokat, a metatagokat, a különböző HTTP státuszkódokat, és legyen fejlettebb a relatív URL-ek feloldása. - Stop Szavak és Szótövezés (Stemming): Bővítsd a stop szavak listáját, és implementálj egy egyszerű szótövező algoritmust (pl. Porter Stemmer), hogy a "fut", "futó", "futás" szavakat ugyanannak a gyökérszónak tekintse.
- Cache: Használj memóriában tárolt gyorsítótárat (pl. Redis, Memcached) a gyakran keresett szavak vagy oldalak eredményeinek gyorsabb kiszolgálásához.
- UI/UX: Fejleszd a felhasználói felületet, hogy esztétikusabb és könnyebben kezelhető legyen.
- Fejlettebb Rangsorolás: Vezess be több tényezőt a rangsorolásba, például a kulcsszavak pozícióját az oldalon (címben, leírásban lévő szavak nagyobb súlyt kapnak).
- Hibatűrés és Naplózás: Implementálj részletes hibakezelést és naplózást, hogy lásd, mi történik a rendszerrel.
Konklúzió 🎉
Gratulálunk! Reméljük, ez a részletes útmutató segítséget nyújtott abban, hogy megértsd és akár el is készítsd a saját PHP-alapú keresőmotorodat. Ez a projekt nem csak a kódolási készségeidet fejleszti, hanem mélyebb betekintést enged a webes infrastruktúrák és az adatfeldolgozás komplex világába. Ne feledd, a cél nem a Google felülmúlása, hanem a tanulás és a felfedezés öröme. Kísérletezz bátran, és alkoss valami újat!
Kinek ne tetszene egy olyan projekt, ahol a semmiből építhetünk fel egy olyan rendszert, ami alapvetően megváltoztatta a világunkat? Ez a fajta keresőmotor fejlesztés igazi élmény, és reméljük, hogy a most megszerzett tudás segíteni fog a jövőbeli, még nagyobb kihívásokban is!