CakePHP: Code/Components/GoogleSearch

Поиск Google

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

Автор Владимир Лучанинов

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

Но, пока можно искать через браузер, можно искать через Browser :)

Попробуем применить наш компонент в боевых условиях.

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

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

<?php

class GoogleSearchTestController extends Controller {

    var $name 'GoogleSearchTest';

    var $uses null;

    var $components = array('Browser''GoogleSearch');

}

class GoogleSearchTest extends CakeTestCase {

    var $name 'GoogleSearch';

    var $controller null;

    var $component null;

    function setUp() {

        $this->controller =& new GoogleSearchTestController();

        restore_error_handler();

        @$this->controller->_initComponents();

        set_error_handler('simpleTestErrorHandler');

        $this->controller->Browser->startup($this->controller);

        $this->component $this->controller->GoogleSearch;

        $this->component->startup($this->controller);

        ClassRegistry::addObject('view', new View($this->controller));

    }

    function testSearch() {

        $this->component->maxResults 10// no need to bother google a lot

        $result $this->component->query('google');

        $this->assertTrue($result['count']> 1000000);

        $this->assertWithinMargin(count($result['sites']), $this->component->maxResults3);

        $this->assertNotNull($result['sites'][0]);

        $this->assertNotNull($result['sites'][0]['url']);

        $this->assertNotNull($result['sites'][0]['title']);

        $this->assertNotNull($result['sites'][0]['snippet']);

        $this->assertPattern('/google\.com/i'$result['sites'][0]['url']);

        $this->assertPattern('/google/i'$result['sites'][0]['title']);

        $result $this->component->query('yahoo');

        $this->assertTrue($result['count']> 1000000);

        $this->assertWithinMargin(count($result['sites']), $this->component->maxResults3);

        $this->assertPattern('/yahoo\.com/i'$result['sites'][0]['url']);

        $this->assertPattern('/yahoo/i'$result['sites'][0]['title']);

    }

}

?>

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

var $components = array('Browser', 'GoogleSearch');

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

$this->GoogleSearch->query('test');

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

$this->GoogleSearch->maxResults = 100; // по умолчанию - 500

$this->GoogleSearch->lang = 'ru'; // по умолчанию - 'en'

$this->GoogleSearch->server = 'http://www.google.ru'; // без слеша в конце. по умолчанию 'http://www.google.com'

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

<?

class GoogleSearchComponent {

    var $components = array('Browser');

    var $maxResults 500;

    var $lang 'en';

    var $server 'http://www.google.com';

    /**

     * Main function. Searches $keyword in Google

     *

     * @param string $keyword Stuff you enter in the search box

     * @return array (sites=>a(url=>, title=>, snippet=>), count=>)

     */

    function query($keyword) {

        $limit 10;

        $start 0;

        $result = array('sites'=>array());

        while ($start<$this->maxResults && !(isset($matches) && count($matches)<($limit-3))) {

            if ($start>=100) {

                $limit 100;

            } elseif ($start>=40) {

                $limit 20;

            }

            $url $this->server.'/search?'.

                'num='.$limit.

                '&hl='.$this->lang.

                '&client=firefox-a'.

                '&rls=org.mozilla%3Aen-US%3Aofficial'.

                '&as_qdr=all'.

                '&q='.urlencode($keyword).

                '&btnG=Search'.

                (($start>0) ? '&start='.$start.'&sa=N' '');

            usleep(rand(1002000)); // be like humans for Google

            $s $this->Browser->get($url);

            if (strpos($s'<title>403 Forbidden</title>')!==false && preg_match('/^HTTP\/1.1 403 Forbidden/i'$this->Browser->header)) {

                die('<h3 style="color:red">Google reminds you that automatic Google parsing is prohibited. Don\'t be evil :)</h3>');

            }

            if ($start==0) {

                $result['count'] = r(','''$this->_find('|of (about )?<b>([\d,]+)</b> for <b>|i'$s2));

            }

            preg_match_all('|<h2\s+class=.?r.?><a href="(.+)".+>(.+)</a></h2>.+<font size=-1>(.+)<br>|iU'$s$matchesPREG_SET_ORDER);

            foreach ($matches as $match) {

                $result['sites'][] = array(

                    'url'      => $match[1],

                    'title'  => strip_tags($match[2], '<b>'),

                    'snippet'   => strip_tags($match[3], '<b>'),

                );

            }

            $start += $limit;

        }

        return $result;

    }

    /**

     * Alias for query

     *

     * @param string $keyword

     * @return array

     */

    function search($keyword) {

        return $this->query($keyword);

    }

    /**

     * Init

     *

     * @param AppController $controller

     */

    function startup(&$controller) {

    }

    /**

     * Looks for $pattern in $s and returns match no. $index

     *

     * @param string $pattern RegEx

     * @param string $s

     * @param int $index

     * @return string

     */

    function _find($pattern$s$index=1) {

        preg_match($pattern$s$out);

        return (isset($out[$index])) ? $out[$index] : '';

    }

}

?>

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

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