Автор Владимир Лучанинов
Google — классная поисковая система, но почему то они закрыли доступ к поиску в нём через SOAP. Как говорила в далёком детстве школьная учительница английского Shame on you, ведь даже у Yandex есть Yandex.XML. Мы же не спамеры, нам 1000 поисков в день с головой достаточно.
Но, пока можно искать через браузер, можно искать через Browser :)
Попробуем применить наш компонент в боевых условиях.
Что нужно от GoogleSearch (так затейливо назвал я этот компонент)
Помня о заветах Ильича Test-Driven development, напишем сначала тест. Пусть он ищет google и yahoo. Угадайте, какие сайты будут на первом месте? Вот их наличие и проверим.
<?phpclass 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->maxResults, 3);
$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->maxResults, 3);
$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(100, 2000)); // 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', $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) {
$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 запросов.