Pobieranie PageRank

Data dodania:
31 marca 09
Autor:
Łukasz Rutkowski
Kategoria:
PHP

Czasem pisząc w PHP chcemy uzyskać PageRank danej strony. Trudno znaleźć dobre rozwiązanie, które działa. Zaprezentuję Ci Drogi Czytelniku mój sposób na ten problem. Skrypt będzie w pewnym stopniu zabezpieczony przed banem od Google, gdyż wyniki będą przechowywane w cache’u.

UWAGA! Na serwerach, które mają wyłączone odwoływanie się funkcji file_get_contents do serwerów zewnętrznych, poniższy skrypt nie będzie działał ( zazwyczaj są to serwery darmowe ).

PageRank – co to?

PageRank – metoda nadawania indeksowanym stronom internetowym określonej wartości liczbowej, oznaczającej jej jakość.

Krótko i zwięźle. ;-) Od siebie dodam, że w całości algorytm PageRank znają jedynie pracownicy Google i nie jest on znany przez żadnego “szaraka”.

Przygotowywujemy się

W tym artykule postaram się wyjaśnić, jak szybko i prosto można napisać skrypt, który będzie pobierał PageRank strony, której adres będzie przekazywany poprzez zmienną GET strona. Wyniki będą przechowywane w specjalnie do tego przeznaczonym cache’u ( tak jakby okresowej pamięci wyników ) o ważności 24h. Wykorzystamy do tego dwie klasy, które są dostępne pod tym adresem: link. Niestety są one napisane dla PHP4, a dzisiaj wykorzystuje się PHP5. Niby nie jest to większy problem, bo każda wersja PHP jest zgodna wstecz, lecz stosowanie skryptów napisanych dla wersji 4. słynnego interpretera stron, jest niewskazane. Na szczęście kod obu klas nie jest długi, więc mogłem szybko przerobić je dla Was, aby były zgodne z najnowszym PHP. Obydwie znajdują się poniżej.

Plik google_pagerank.class.php :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
    <?php
    /**
    * TechBrew.net's "popstats" is available from http://code.google.com/p/popstats/
    * 
    * This work is dual-licensed under the GNU Lesser General Public License
    * and the Creative Commons Attribution-Share Alike 3.0 License.
    * Copies or derivatives must retain both attribution and licensing statement.
    *
    * To view a copy of these licenses, visit:
    * http://creativecommons.org/licenses/by-sa/3.0/
    * http://www.gnu.org/licenses/lgpl.html
    *
    * This software is provided AS-IS with no warranty whatsoever.
    */
 
    require_once('cacher.class.php');
 
    /**
    * Google PageRank Fetcher
    *
    * Requires an API key to access Technorati stats.
    * (Get one at http://technorati.com/developers/apikey.html)
    *
    * @author Mark Woodman, http://techbrew.net
    *         Based on original code presumed to be in the public domain.
    *
    * $URL$
    * $Rev$
    * $Author$
    * $Date$
    * $Id$
    */
    class GooglePageRank
    {
      private $site;
      public $pagerank;
 
       /**
        * Constructor.
        *
        * @param url        The site URL to check for PageRank
        * @param cacheTime  (Optional) Length of time in seconds to cache results.
        */
       public function __construct($site, $cacheTime=86400)
       {
          $this->site = $site;
          if(count($site)==0) die('Google needs a site URL to check pagerank.');
 
          // Calculated variables
          $info = 'info:' . urldecode($site);
          $checksum = $this->checksum($this->strord($info));
          $url = "http://www.google.com/search?client=navclient-auto&ch=6{$checksum}&features=Rank&q={$info}";
 
          // Pull pagerank through cache
          $cacher = new Cacher('_google');
          $result = $cacher->fetchContents($url, $cacheTime);
 
          // Parse results
          $this->raw = trim($result);
          preg_match('/Rank_[0-9]:[0-9]:(.*)/', $result, $r);
          if(!isset($r[1]))
          {
             trigger_error("Couldn't get Pagerank for {$site}.  Got: [{$result}]", E_USER_NOTICE);
             error_log("\n" . date('r') . "Couldn't get Pagerank for {$site}", 3, 'error_log');
             $this->pagerank = -1;
          }
          else
          {
             $this->pagerank =(isset($r[1])) ? $r[1] : 'Error';
          }
       }
 
        /**
         * Converts number to int 32
         * (Required for pagerank hash)
         */
        private function to_int_32 (&$x) {
          $z = hexdec(80000000);
          $y = (int) $x;
          if($y ==- $z && $x <- $z){
           $y = (int) ((-1) * $x);
           $y = (-1) * $y;
          }
          $x = $y;
        }
 
        /**
         * Fills in zeros on a number
         * (Required for pagerank hash)
         */
        private function zero_fill ($a, $b) {
          $z = hexdec(80000000);
          if ($z & $a) {
            $a = ($a >> 1);
            $a &= (~$z);
            $a |= 0x40000000;
            $a = ($a >> ($b - 1));
          } else {
            $a = ($a >> $b);
          }
          return $a;
        }
 
        /**
         * Pagerank hash prerequisites
         */
        private function mix($a, $b, $c) {
          $a -= $b; $a -= $c; $this->to_int_32($a); $a = (int)($a ^ ($this->zero_fill($c,13)));
          $b -= $c; $b -= $a; $this->to_int_32($b); $b = (int)($b ^ ($a<<8));
          $c -= $a; $c -= $b; $this->to_int_32($c); $c = (int)($c ^ ($this->zero_fill($b,13)));
          $a -= $b; $a -= $c; $this->to_int_32($a); $a = (int)($a ^ ($this->zero_fill($c,12)));
          $b -= $c; $b -= $a; $this->to_int_32($b); $b = (int)($b ^ ($a<<16));
          $c -= $a; $c -= $b; $this->to_int_32($c); $c = (int)($c ^ ($this->zero_fill($b,5)));
          $a -= $b; $a -= $c; $this->to_int_32($a); $a = (int)($a ^ ($this->zero_fill($c,3)));
          $b -= $c; $b -= $a; $this->to_int_32($b); $b = (int)($b ^ ($a<<10));
          $c -= $a; $c -= $b; $this->to_int_32($c); $c = (int)($c ^ ($this->zero_fill($b,15)));
          return array($a,$b,$c);
        }
 
        /**
         * Pagerank checksum hash emulator
         */
        private function checksum ($url, $length = null, $init = 0xE6359A60) {
          if (is_null($length)) {
            $length = sizeof($url);
          }
          $a = $b = 0x9E3779B9;
          $c = $init;
          $k = 0;
          $len = $length;
          while($len >= 12) {
          $a += ($url[$k+0] + ($url[$k+1] << 8) + ($url[$k+2] << 16) + ($url[$k+3] << 24));
          $b += ($url[$k+4] + ($url[$k+5] << 8) + ($url[$k+6] << 16) + ($url[$k+7] << 24));
          $c += ($url[$k+8] + ($url[$k+9] << 8) + ($url[$k+10] << 16) + ($url[$k+11] << 24));
          $mix = $this->mix($a,$b,$c);
          $a = $mix[0]; $b = $mix[1]; $c = $mix[2];
          $k += 12;
          $len -= 12;
          }
          $c += $length;
          switch($len) {
            case 11: $c += ($url[$k + 10] << 24);
            case 10: $c += ($url[$k + 9] << 16);
            case 9: $c += ($url[$k + 8] << 8);
            case 8: $b += ($url[$k + 7] << 24);
            case 7: $b += ($url[$k + 6] << 16);
            case 6: $b += ($url[$k + 5] << 8);
            case 5: $b += ($url[$k + 4]);
            case 4: $a += ($url[$k + 3] << 24);
            case 3: $a += ($url[$k + 2] << 16);
            case 2: $a += ($url[$k + 1] << 8);
            case 1: $a += ($url[$k + 0]);
          }
          $mix = $this->mix($a, $b, $c);
          return $mix[2];
        }
 
        /**
         * ASCII conversion of a string
         */
        private function strord($string) {
          for($i = 0; $i < strlen($string); $i++) {
            $result[$i] = ord($string{$i});
          }
          return $result;
        }
 
        /**
         * Number formatting for use with pagerank hash
         */
        private function format_number ($number='', $divchar = ',', $divat = 3) {
          $decimals = '';
          $formatted = '';
          if (strstr($number, '.')) {
            $pieces = explode('.', $number);
            $number = $pieces[0];
            $decimals = '.' . $pieces[1];
          } else {
            $number = (string) $number;
          }
          if (strlen($number) <= $divat)
            return $number;
            $j = 0;
          for ($i = strlen($number) - 1; $i >= 0; $i--) {
            if ($j == $divat) {
              $formatted = $divchar . $formatted;
              $j = 0;
            }
            $formatted = $number[$i] . $formatted;
            $j++;
          }
          return $formatted . $decimals;
        }
       public function __destruct() {
          unset($this->site);
          unset($this->pagerank);
       }
 
      }
    ?>

Plik cacher.class.php :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
    <?php
    /**
    * TechBrew.net's "popstats" is available from http://code.google.com/p/popstats/
    * 
    * This work is dual-licensed under the GNU Lesser General Public License
    * and the Creative Commons Attribution-Share Alike 3.0 License.
    * Copies or derivatives must retain both attribution and licensing statement.
    *
    * To view a copy of these licenses, visit:
    * http://creativecommons.org/licenses/by-sa/3.0/
    * http://www.gnu.org/licenses/lgpl.html
    *
    * This software is provided AS-IS with no warranty whatsoever.
    */
 
    /**
    * Cacher utility to write the output of a remote URI request
    * to the local filesystem.
    *
    * @author Mark Woodman, http://techbrew.net
    * @version 15 April 2007
    */
    class Cacher
    {
       private $cachedir = CACHE_DIR;
       private $suffix;
 
       /**
        * Constructor.  Requires CACHE_DIR directive to be set
        * prior to use.  CACHE_DIR will contain cache files
        * which hold the contents of requested URLs.
        */
       public function __construct($suffix='')
       {
          if(!(CACHE_DIR))
          {
             die('CACHE_DIR not configured.');
          }
          $this->suffix = $suffix;
       }
 
       /**
        * Fetch a URL.  If it has already
        * been cached within the specified cacheTime,
        * the cached copy is returned.  Otherwise a
        * fresh copy is retrieved and cached.
        *
        * If it can't write to the cache for some reason, the original URL
        * is returned.
        *
        * @param   $url        The URL to retrieve
        * @param   $cacheTime  The length of time to cache the requested URL.
        */
       private function fetch($url, $cacheTime=86600)
       {     
          // Determine cache file name
          $cacheFile = $this->cachedir . md5($url) . $this->suffix . '.cache' ;
          $refresh = true;
          if(@file_exists($cacheFile))
          {
             $refresh = (time() - $cacheTime > @filemtime($cacheFile)) ;
          }
          @clearstatcache();
 
         // Cache file if needed
         if($refresh)
         {
             try
             {
                $tries = 0;
                $errors = 0;
                $contents = false;
                while($tries<3)
                {
                   $tries++;
                   if(!$contents)
                   {
                      $contents = @file_get_contents($url);
                   }
                   if(!$contents)
                   {
                      $error = $error .(' GET_FAIL ');
                   }
                   else
                   {
                      $result = @file_put_contents($cacheFile, $contents);
                      if(!$result)
                      {
                         $error = $error .(' PUT_FAIL ');
                      }
                      else
                      {
                         return $cacheFile;   
                      }
                   }
                   // TODO:  Is this necessary?  Is there a better way?
                   usleep(10000);
                }
                error_log("\n" . date('r') . " - Failed to cache: {$url}", 3, 'error_log');
                error_log("\n" . date('r') . " - Failure reasons: {$error}", 3, 'error_log');
                return false;
             }
             catch(Exception $e)
             {
                error_log("\n" . date('r') . " - {$e}", 3, 'error_log');
                error_log("\n" . date('r') . " - Cacher error: {$error}", 3, 'error_log');
                return false;
             }
 
         }
         return $cacheFile;
       }
 
       /**
        * Fetch a URL and return contents as a string.  If it has already
        * been cached within the specified cacheTime,
        * the cached copy is returned.  Otherwise a
        * fresh copy is retrieved and cached.
        *
        * @param   $url        The URL to retrieve
        * @param   $cacheTime  The length of time to cache the requested URL.
        */
       public function fetchContents($url, $cacheTime=86600)
       {
          $file = $this->fetch($url, $cacheTime);
          if(!$file) return false;
          return file_get_contents($file);   
       }
 
       /**
        * Clear cache files for a url.
        */
       private function clear($url)
       {
          // Determine cache file name
          $cacheFile = $this->cachedir . md5($url) . $this->suffix . '.cache' ;
          if(@file_exists($cacheFile))
          {
             @unlink($cacheFile);
          }
       }
       public function __destruct() {
          unset($this->cachedir);
          unset($this->suffix);
       }
    }
 
    ?>

UWAGA! Klasy te są autorstwa Mark’a Woodman’a i są one publikowane na licencji Creative Commons Attribution-Share Alike 3.0 License oraz GNU Lesser General Public License

Skoro już mamy kod klas, to warto by gdzieś zapisać. ;-) Pierwszą umieszczamy w pliku pod nazwą google_pagerank.class.php, a drugą: cacher.class.php. Obydwa pliki umieszczamy w folderze includes.
Aby przygotować nasz katalog do działania z tymi klasami, musimy jeszcze utworzyć katalog cache ( oczywiście ma on znajdować się w głównym folderze ; musi zawierać prawa do zapisu ). Do tego tworzymy plik index.php i to nam wystarczy, aby napisać coś, co już będzie można nazwać prostym sprawdzaczem PageRank’u.

Bierzemy się za pisanie

Przechodzimy do edycji pliku index.php. Pierw musimy do niego załadować klasę GooglePageRank. Pamiętając, że znajduje się ona w folderze includes, w pliku google_pagerank.class.php oraz korzystając z pseudofunkcji require_once, wykonujemy to:

    require_once 'includes/google_pagerank.class.php';

O umieszczanie pliku z klasą Cacher nie musimy się martwić, gdyż plik, który przed chwilą połączyliśmy z index.php, sam go pobiera.
Klasa cache’ująca do działania potrzebuje zdefiniowanej stałej o nazwie CACHE_DIR, która będzie zawierała ścieżkę do katalogu, do którego będą zapisywane wyniki zapytań do serwerów Google’a. Robimy to za pomocą funkcji define() :

    define('CACHE_DIR', './cache/');

Mamy już wszystko, co potrzebujemy do napisania najprostszego skryptu. Wystarczy utworzyć obiekt klasy GooglePageRank z parametrem adresu strony ( u nas to zmienna GET adres ) oraz wyświetlić zawartość zmiennej pagerank, nowoutworzonej klasy:

1
2
    $pagerank = new GooglePageRank($_GET['adres']);
    echo $pagerank->pagerank;

I tutaj mała podpowiedź: gdy chcemy, aby cache odświeżał się częściej, bądź radziej niż 24 godziny, to przy tworzeniu obiektu musimy podać 2. parametr, którego wartość będzie liczbą całkowitą wyrażoną w sekundach. Np. by co 12h ( 43200 sekund ) pobierało od nowa PageRank dla danej strony, tworzymy tak obiekt:

    $pagerank = new GooglePageRank($_GET['adres'],43200);

Jeżeli miałby ktoś problem z kodem pliku index.php, umieszczam jego całe ( straasznie długie ;-) ) źródło:

1
2
3
4
5
6
7
    <?php  
    require_once 'includes/google_pagerank.class.php';
    define('CACHE_DIR', './cache/');
 
    $pagerank = new GooglePageRank($_GET['adres']);
    echo $pagerank->pagerank; 
    ?>
Opis wyniku

Jeżeli chcemy uruchomić nasz skrypt z wartością zmiennej GET adres równą http://webday.pl , to do przeglądarki wpisujemy:

http://adres-naszego-serwera/?adres=http://webday.pl

Przypominam, że serwer domyślnie odwołuje do pliku index.php, więc powyższy link pokaże to samo, co ten:

    http://adres-naszego-serwera/index.php?adres=http://webday.pl

Działanie możemy również sprawdzić, umieszczając prosty formularz w pliku index.php:

1
2
3
4
    <form action="index.php" method="get">
      <input type="text" name="adres"/>
      <input type="submit" />
    </form>

Aby to zrobić, musimy minimalnie zmodyfikować nasz plik index.php, nadając mu taką wartość:

1
2
3
4
5
6
7
8
9
10
11
12
    <?php  
    require_once 'includes/google_pagerank.class.php';
    define('CACHE_DIR', './cache/');
    if(isset($_GET['adres'])) {
        $pagerank = new GooglePageRank($_GET['adres']);
        echo $pagerank->pagerank; 
    }
    echo '<form action="index.php" method="get">
      <input type="text" name="adres"/>
      <input type="submit" />
    </form>';
    ?>

Skrypt zwraca wartość -1 dla stron, które jeszcze nie posiadają PageRank ( taki przypadek można zauważyć tylko u bardzo młodych stronach, które jeszcze “nie przeżyły” odświeżenia PR ), a dla reszty zwróci wartość z zakresu 0-10.

Mankamentem może być to, że po 1000 pobraniach PageRank’u w ciągu dnia, nasze IP może zostać zablokowane, więc radzę rozsądnie korzystać z tego, jednak pamiętajmy, że PR odświeża się conajmniej co miesiąc, więc ważność cache’u możemy ustawić na jakieś 10 dni.

~Łukasz Rutkowski