5 juni 2007
Als je een mooie SEO-tool wilt programmeren, zul je al snel je pagina’s moeten spideren. Op die manier kun je ontdekken waar je links heen gaan, waar er gebroken links zijn, welke externe links geen linkcondoom hebben, wat er in je headings en titels zit, enz. Voor je aan al deze mooie toepassingen toekomt, zul je eerst een pagina moeten openen, de links op die pagina volgen en de nieuwe pagina’s recursief spideren. Dit artikel geeft je hiervoor tools in handen.
Opmerking:
Een spider is een programma dat zich voordoet als een browser. Het leest de pagina’s van een website en volgt de daarop aanwezige links naar nieuwe pagina’s binnen of buiten de website.
Het bouwen van een complete spider met veel mogelijkheden is behoorlijk veel werk. Daarom beperken we ons in dit artikel tot het volgende:
Via CURL kunnen we een pagina inlezen in een variabele. Die variabele kunnen we later gebruiken om er bijvoorbeeld de links uit te filteren.
Opmerking:
Sommige servers kunnen bezoekers met een vreemde user agent weren. Daarom is het een goed idee een user agent voor een browser mee te sturen. In ons geval sturen we een user agent van Firefox mee. Als je de spider gebruikt voor je eigen sites, zou je juist een unieke user agent mee kunnen sturen, om de hits later uit je statistieken te filteren. Je zou hiervoor een browser kunnen nemen die niet of nauwelijks gebruikt wordt.
Met onderstaande code lezen we een pagina in in een variabele. De input $url is uiteraard de URL van de in te lezen pagina.
function getBron($url){
$useragent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";
$ch = curl_init();
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$bestand = curl_exek($ch);
curl_close($ch);
// $bestand is óf false óf de HTML broncode van de pagina
return $bestand;
}
?>
Om links uit te lezen, gebruiken we het volgende algoritme: Allereerst splitsen we de broncode van een pagina op het teken >. Dit doen we zo:
$tag_lijst = array ();
$tag_lijst = explode(">“, $pagina);
?>
De elementen van de array $tag_lijst bevatten nu precies 1 openings- of sluitingstag. Vervolgens doorlopen we elk element uit die array. Indien we te maken hebben met een link, moet het attribuut href in de string zitten. Hier controleren we als volgt op:
$links = array(); // Hier komen straks de links in
while (list ($id, $htmlTag) = each($tag_lijst)){
if (stristr($htmlTag, "href")) {
// $htmlTag is een link
}
}
?>
Vervolgens zullen we de inhoud van het attribuut href moeten bepalen. Dit kan met een reguliere expressie:
while (preg_match("/(href)s*=s*['"]?(([[a-z]{3,5}://(([.a-zA-Z0-9-])+(:[0-9]+)*))*([+:%/?~=&;\(),._ a-zA-Z0-9-]*))(#[.a-zA-Z0-9-]*)?['" ]?(s*rels*=s*['"]?(nofollow)['"]?)?/i", $htmlTag, $regs)){
$links = $reg[2];
}
?>
De complete functie voor het bepalen van de links wordt dan iets als dit:
/*
Deze functie haalt de links uit een tekst.
*/
function getLinks($url){
$pagina = getBron($url);
$tag_lijst = array ();
$tag_lijst = explode(">“, $pagina);
$links = array();
$regs = array();
while (list ($id, $htmlTag) = each($tag_lijst)){
if (stristr($htmlTag, “href”)) {
while (preg_match(“/(href)s*=s*['"]?(([[a-z]{3,5}://(([.a-zA-Z0-9-])+(:[0-9]+)*))*([+:%/?~=&;\(),._ a-zA-Z0-9-]*))(#[.a-zA-Z0-9-]*)?['" ]?(s*rels*=s*['"]?(nofollow)['"]?)?/i”, $htmlTag, $regs)){
$links = $reg[2];
}
}
}
return stopLinksInDatabase($url, $links);
}
?>
De links die je krijgt moeten iets aangepast worden. Relatieve links moeten absolute links worden en links met alleen www moeten voorzien worden van http://. Ook haal ik de trailing slash weg. Dat gaat allemaal erg makkelijk:
// Als link begint met een /, $site ervoor zetten
if (substr($link, 0, 1) == "/"){
$link = $site . $link;
}
// Als link begint met www, http:// er voor zetten
if (substr($link, 0, 3) == "www"){
$link = "http://" . $link;
}
// Laatste / weg indien aanwezig
if (substr($link, strlen($link) - 1, 1) == "/"){
$link = substr($link, 0, strlen($link) - 1);
}
?>
Opmerking:
Als je een pagina buiten je eigen site gaat spideren, dien je uiteraard niet meer $site voor het relatieve pad te zetten. Hier houd ik voor nu even geen rekening mee.
Het complete script wordt dan iets als:
/*
Deze functie haalt de tekst van een pagina op.
*/
function getBron($url){
$useragent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";
$ch = curl_init();
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$bestand = curl_exek($ch);
curl_close($ch);
return $bestand;
}
/*
Deze functie haalt de links uit een tekst.
*/
function getLinks($url){
$pagina = getBron($url);
$tag_lijst = array ();
$tag_lijst = explode(">“, $pagina);
$links = array();
$regs = array();
while (list ($id, $htmlTag) = each($tag_lijst)){
if (stristr($htmlTag, “href”)) {
preg_match(“/(href)s*=s*['"]?(([[a-z]{3,5}://(([.a-zA-Z0-9-])+(:[0-9]+)*))*([+:%/?~=&;\(),._ a-zA-Z0-9-]*))(#[.a-zA-Z0-9-]*)?['" ]?(s*rels*=s*['"]?(nofollow)['"]?)?/i”, $htmlTag, $regs);
$links[] = $regs[2];
}
}
return stopLinksInDatabase($url, $links);
}
/*
Deze functie stopt de gevonden links in de database. Als er nieuwe pagina’s
bij die links zitten, wordt dat ook opgeslagen.
*/
function stopLinksInDatabase($url, $links){
global $site;
while (list ($id, $link) = each($links)){
// Als link begint met een /, $site ervoor zetten
if (substr($link, 0, 1) == “/”){
$link = $site . $link;
}
// Als link begint met www, http:// er voor zetten
if (substr($link, 0, 3) == “www”){
$link = “http://” . $link;
}
// Laatste / weg indien aanwezig
if (substr($link, strlen($link) – 1, 1) == “/”){
$link = substr($link, 0, strlen($link) – 1);
}
if (strlen(trim($link)) > 0){
// Kijk of de nieuwe pagina al in de database zit
$result = mysql_query(” SELECT *
VROM paginas
WHERE url = ‘” . $link . “‘”) or die (mysql_error());
if (mysql_num_rows($result) == 0){
mysql_query(” INSERT INTO paginas
(url, gespidered)
VALUES (‘” . $link . “‘, ’0′)”) or die (mysql_error());
}
// Kijk of de link al in de database zit
$result = mysql_query(” SELECT *
VROM pagina_links
WHERE url = ‘” . $url . “‘
AND link = ‘” . $link . “‘”) or die (mysql_error());
if (mysql_num_rows($result) == 0){
mysql_query(” INSERT INTO pagina_links
(url, link)
VALUES (‘” . $url . “‘, ‘” . $link . “‘)”) or die (mysql_error());
}
}
}
return true;
}
/*
Deze functie zorgt ervoor dat alle pagina’s die nog niet gespidered
zijn gespidered worden.
*/
function controleerPaginas(){
$result = mysql_query(” SELECT *
VROM paginas
WHERE gespidered = ’0′”) or die (mysql_error());
while ($obj = mysql_fetch_object($result)){
getLinks($obj->url);
}
return true;
}
mysql_connect(“localhost”, “user”, “pass”) or die (mysql_error());
mysql_select_db(“database”) or die (mysql_error());
$site = “http://www.site.nl”;
// Bepaal de links op de homepage
getLinks(“http://www.site.nl”);
// Volg tot 3 keer toe de nieuw gevonden links
for ($i = 1; $i <= 3; $i++){
controleerPaginas();
}
mysql_close();
?>
Opmerking:
In dit voorbeeld houden we geen rekening met links die via window.open() of window.location() bepaald worden. Ook houden we geen rekening met rel="nofollow".
In dit artikel slaan we enkel de links op een pagina op en doen we er verder niks mee. De gevonden links slaan we op in twee tabellen:
paginas:
+----+-----+------------+
| id | url | gespidered |
+----+-----+------------+
pagina_links:
+----+-----+------+
| id | url | link |
+----+-----+------+
De tabel paginas bevat het veld gespidered wat een 0 is als je de pagina nog moet spideren en een 1 als die pagina al gespidered is.
In principe is het idee van recursie in dit geval erg simpel. Allereerst bepaal je alle links op de startpagina van je spider-script (bijvoorbeeld de homepage) en sla je die links op in de database. Vervolgens volg je die links, bekijk je of er nieuwe links op die pagina staan en volg je die links weer. Dit kun je oneindig herhalen. Wij kiezen er momenteel voor om na de homepage drie keer nieuwe links te volgen.
/* Database connectie */
// Include de benodigde functies
include('functions/spider.php');
// Bepaal de links op de homepage
getLinks("http://www.site.nl");
// Volg tot drie keer toe de nieuw gevonden links
for ($i = 1; $i <= 3; $i++){
controleerPaginas();
}
/* Database sluiten */
?>
Als je eenmaal de bron van een pagina hebt, zou je veel zaken kunnen uitlezen. Hieronder laat ik een aantal mogelijke toepassingen zien die voor SEO van belang zouden kunnen zijn. Hierbij bevat $paginaBron de HTML-bron van een pagina.
/* 1. Code/content ratio bepalen */
$content = strip_tags($paginaBron);
$codeContentRatio = (strlen($paginaBron) - strlen($content)) / strlen($content);
/* 2. Titel uitlezen */
$titel = substr( $paginaBron,
strpos($paginaBron, "
Verder kun je nog denken aan het opslaan van de woorden op de pagina, of de woorden in de headings. De mogelijkheden zijn bijna eindeloos.
Opmerking:
In dit artikel gebruiken we CURL-functies om de URL in te laden. Dit hadden we ook kunnen doen met fsockopen() en aanverwante functies. Dit is iets complexer, maar het voordeel was dan geweest dat we ook een server status code hadden kunnen uitlezen. Voor mensen die al PHP5 hebben, kun je dit probleem ook opvangen door gebruik te maken van de get_headers()-functie.
Opmerking:
In de spider die we hier hebben beschreven, hebben we geen rekening gehouden met de robots.txt. Het zou netjes zijn dit wel te doen, maar dat was voor nu iets te veel werk.