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
-
-
class GoogleSearchTestController extends Controller {
-
var $name = 'GoogleSearchTest';
-
var $uses = null;
-
}
-
-
class GoogleSearchTest extends CakeTestCase {
-
var $name = 'GoogleSearch';
-
var $controller = null;
-
var $component = null;
-
-
function setUp() {
-
$this->controller =& new GoogleSearchTestController();
-
-
@$this->controller->_initComponents();
-
-
$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->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->assertPattern('/yahoo\.com/i', $result['sites'][0]['url']);
-
$this->assertPattern('/yahoo/i', $result['sites'][0]['title']);
-
}
-
-
}
-
?>
Вот, кстати, и обнаружилась полезность тестов. Если компонент А использует компонент Б, то в контроллере, вызывающем компонент А, нужно обязательно указать компонент Б, даже если он сам по себе не используется. Я об этом постоянно забываю.
Тест также показывает как правильно использовать компонент. Для поиска с помощью GoogleSearch надо в контроллере написать
-
$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 $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;
-
-
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'.
-
'&btnG=Search'.
-
(($start>0) ? '&start='.$start.'&sa=N' : '');
-
-
-
$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', $s, 2));
-
}
-
-
preg_match_all('|<h2\s+class=.?r.?><a href="(.+)".+>(.+)</a></h2>.+<font size=-1>(.+)<br>|iU', $s, $matches, PREG_SET_ORDER);
-
foreach ($matches as $match) {
-
'url' => $match[1],
-
);
-
}
-
-
$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) {
-
}
-
}
-
-
?>
Disclaimer: Используйте этот код только в учебных целях. Google запрещает себя парсить роботами.
Кстати, респект программерам из Google. До того как я поставил случайную задержку между запросами, банили через 300 запросов.
" , , , , . ."
A::startup loadCoponent()
@: . , startup.
,
//$this->controller->Browser->startup($this->controller); //
loadComponent('Browser'); //
$this->component = $this->controller->GoogleSearch;
$this->component->startup($this->controller);
startup . , , startup, - - startup , .
[...] Парсим результаты поиска в Google (Не тестировал) [...]
кстати, а где лучше прописывать set_time_limit ? в контроллере?
Обычно долгие действия выполняются в контроллере. А set_time_limit надо прописывать до долгих действий.
Соответственно, да