GoogleSearch: компонент для CakePHP, который парсит результаты поиска Google

Google - классная поисковая система, но почему то они закрыли доступ к поиску в нём через SOAP. Как говорила в далёком детстве школьная учительница английского "Shame on you", ведь даже у Yandex есть Yandex.XML. Мы же не спамеры, нам 1000 поисков в день с головой достаточно.

Но, пока можно искать через браузер, можно искать через Browser :)
Попробуем применить наш компонент в боевых условиях.

Что нужно от GoogleSearch (так затейливо назвал я этот компонент)

  • показывать информацию о сайтах по заданному запросу: url, title, snippet
  • показывать количество результатов поиска
  • максимально эмулировать человека: делать паузу между запросами, не просить сразу 1000 результатов. Бонус от компонента Browser: автоматически поставится правильный referer

Помня о заветах Ильича Test-Driven development, напишем сначала тест. Пусть он ищет "google" и "yahoo". Угадайте, какие сайты будут на первом месте? Вот их наличие и проверим.

PHP:
  1. <?php
  2.  
  3. class GoogleSearchTestController extends Controller {
  4.     var $name = 'GoogleSearchTest';
  5.     var $uses = null;
  6.     var $components = array('Browser', 'GoogleSearch');
  7. }
  8.  
  9. class GoogleSearchTest extends CakeTestCase {
  10.     var $name = 'GoogleSearch';
  11.     var $controller = null;
  12.     var $component = null;
  13.  
  14.     function setUp() {
  15.         $this->controller =& new GoogleSearchTestController();
  16.  
  17.         restore_error_handler();
  18.         @$this->controller->_initComponents();
  19.         set_error_handler('simpleTestErrorHandler');
  20.  
  21.         $this->controller->Browser->startup($this->controller);
  22.         $this->component = $this->controller->GoogleSearch;
  23.         $this->component->startup($this->controller);
  24.         ClassRegistry::addObject('view', new View($this->controller));
  25.     }
  26.  
  27.     function testSearch() {
  28.         $this->component->maxResults = 10; // no need to bother google a lot
  29.  
  30.         $result = $this->component->query('google');
  31.         $this->assertTrue($result['count']> 1000000);
  32.         $this->assertWithinMargin(count($result['sites']), $this->component->maxResults, 3);
  33.  
  34.         $this->assertNotNull($result['sites'][0]);
  35.         $this->assertNotNull($result['sites'][0]['url']);
  36.         $this->assertNotNull($result['sites'][0]['title']);
  37.         $this->assertNotNull($result['sites'][0]['snippet']);
  38.  
  39.         $this->assertPattern('/google\.com/i', $result['sites'][0]['url']);
  40.         $this->assertPattern('/google/i', $result['sites'][0]['title']);
  41.  
  42.  
  43.         $result = $this->component->query('yahoo');
  44.         $this->assertTrue($result['count']> 1000000);
  45.         $this->assertWithinMargin(count($result['sites']), $this->component->maxResults, 3);
  46.  
  47.         $this->assertPattern('/yahoo\.com/i', $result['sites'][0]['url']);
  48.         $this->assertPattern('/yahoo/i', $result['sites'][0]['title']);
  49.     }
  50.  
  51. }
  52. ?>

Вот, кстати, и обнаружилась полезность тестов. Если компонент А использует компонент Б, то в контроллере, вызывающем компонент А, нужно обязательно указать компонент Б, даже если он сам по себе не используется. Я об этом постоянно забываю.

PHP:
  1. var $components = array('Browser', 'GoogleSearch');

Тест также показывает как правильно использовать компонент. Для поиска с помощью GoogleSearch надо в контроллере написать

PHP:
  1. $this->GoogleSearch->query('test');

Можно перед этим его настроить

PHP:
  1. $this->GoogleSearch->maxResults = 100; // по умолчанию - 500
  2. $this->GoogleSearch->lang = 'ru'; // по умолчанию - 'en'
  3. $this->GoogleSearch->server = 'http://www.google.ru'; // без слеша в конце. по умолчанию 'http://www.google.com'

А вот и сам код.

PHP:
  1. <?
  2.  
  3. class GoogleSearchComponent {
  4.     var $components = array('Browser');
  5.  
  6.     var $maxResults = 500;
  7.     var $lang = 'en';
  8.     var $server = 'http://www.google.com';
  9.  
  10.     /**
  11.      * Main function. Searches $keyword in Google
  12.      *
  13.      * @param string $keyword Stuff you enter in the search box
  14.      * @return array (sites=>a(url=>, title=>, snippet=>), count=>)
  15.      */
  16.     function query($keyword) {
  17.         $limit = 10;
  18.         $start = 0;
  19.         $result = array('sites'=>array());
  20.  
  21.         while ($start<$this->maxResults && !(isset($matches) && count($matches)<($limit-3))) {
  22.             if ($start>=100) {
  23.                 $limit = 100;
  24.             } elseif ($start>=40) {
  25.                 $limit = 20;
  26.             }
  27.  
  28.             $url = $this->server.'/search?'.
  29.                    'num='.$limit.
  30.                    '&hl='.$this->lang.
  31.                    '&client=firefox-a'.
  32.                    '&rls=org.mozilla%3Aen-US%3Aofficial'.
  33.                    '&as_qdr=all'.
  34.                    '&q='.urlencode($keyword).
  35.                    '&btnG=Search'.
  36.                    (($start>0) ? '&start='.$start.'&sa=N' : '');
  37.  
  38.  
  39.             usleep(rand(100, 2000)); // be like humans for Google
  40.             $s = $this->Browser->get($url);
  41.             if (strpos($s, '<title>403 Forbidden</title>')!==false && preg_match('/^HTTP\/1.1 403 Forbidden/i', $this->Browser->header)) {
  42.                 die('<h3 style="color:red">Google reminds you that automatic Google parsing is prohibited. Don\'t be evil :)</h3>');
  43.             }
  44.  
  45.             if ($start==0) {
  46.                 $result['count'] = r(',', '', $this->_find('|of (about )?<b>([\d,]+)</b> for <b>|i', $s, 2));
  47.             }
  48.  
  49.             preg_match_all('|<h2\s+class=.?r.?><a href="(.+)".+>(.+)</a></h2>.+<font size=-1>(.+)<br>|iU', $s, $matches, PREG_SET_ORDER);
  50.             foreach ($matches as $match) {
  51.                 $result['sites'][] = array(
  52.                     'url'      => $match[1],
  53.                     'title'  => strip_tags($match[2], '<b>'),
  54.                     'snippet'   => strip_tags($match[3], '<b>'),
  55.                     );
  56.             }
  57.  
  58.             $start += $limit;
  59.         }
  60.  
  61.         return $result;
  62.     }
  63.  
  64.     /**
  65.      * Alias for query
  66.      *
  67.      * @param string $keyword
  68.      * @return array
  69.      */
  70.     function search($keyword) {
  71.         return $this->query($keyword);
  72.     }
  73.  
  74.     /**
  75.      * Init
  76.      *
  77.      * @param AppController $controller
  78.      */
  79.     function startup(&$controller) {
  80.  
  81.     }
  82.  
  83.     /**
  84.      * Looks for $pattern in $s and returns match no. $index
  85.      *
  86.      * @param string $pattern RegEx
  87.      * @param string $s
  88.      * @param int $index
  89.      * @return string
  90.      */
  91.     function _find($pattern, $s, $index=1) {
  92.         preg_match($pattern, $s, $out);
  93.         return (isset($out[$index])) ? $out[$index] : '';
  94.     }
  95. }
  96.  
  97. ?>

Disclaimer: Используйте этот код только в учебных целях. Google запрещает себя парсить роботами.

Кстати, респект программерам из Google. До того как я поставил случайную задержку между запросами, банили через 300 запросов.


Понравилось?

  1. Подпишись через RSS
  2. Расскажи о http://php.southpark.com.ua друзьям.
    Все способы хороши: ICQ, E-mail, свой блог, комментарий в чужом блоге или сообщение на форуме
  3. Добавь статью на news2.ru, Хабрахабр или в закладки

Огромное спасибо!

И не стесняйтесь комментировать - у меня стоит плагин, который убирает rel="nofollow" у людей, которые написали больше 5 комментариев.

RSS feed | Trackback URI

5 комментариев »

Comment by
2007-10-22 16:06:17

" , , , , . ."

A::startup loadCoponent()

 
Comment by
2007-10-22 20:33:42

@: . , startup.

,

//$this->controller->Browser->startup($this->controller); //
loadComponent('Browser'); //
$this->component = $this->controller->GoogleSearch;
$this->component->startup($this->controller);

startup . , , startup, - - startup , .

 
2008-01-25 22:03:05

[...] Парсим результаты поиска в Google (Не тестировал) [...]

 
Comment by Artur
2008-01-27 18:57:58

кстати, а где лучше прописывать set_time_limit ? в контроллере?

2008-01-27 21:46:38

Обычно долгие действия выполняются в контроллере. А set_time_limit надо прописывать до долгих действий.
Соответственно, да :)

 
 
Имя (required)
E-mail (required - never shown publicly)
URL
Текст комментария
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> in your comment.