Скрипт загрузки скриншотов с Google

Сейчас я расскажу о том, как, используя сведения из предыдущей статьи, можно автоматически загружать скриншоты нужных страниц.

Как я выяснил ранее, уникальный трехзначный хэш для каждого результата поиска передается в html-коде страницы, следовательно алгоритм работы скрипта должен быть приблизительно таким:

  1. Выполняем поиск по заданному ключевому слову — если требуется скриншот конкретной страницы, то используется конструкция «site:http://example.com»
  2. Находим все пары url-хэш в полученном html.
  3. Посылаем запрос на http://clients1.google.com/webpagethumbnail для каждого результата
  4. Обрабатываем полученный JSON
  5. Собираем картинку
На выходе скрипта получаем набор скриншотов — по одному на каждый результат поиска (количество нужных превью можно сделать настраиваемым). 
Что будет использовано в процессе написания: PHP 5.3, cUrl, JSON, base64, DOMDocument.

Для начала установим рабочее окружение

  1. ////
  2. // Setup environment
  3. date_default_timezone_set('GMT');
  4. setlocale(LC_ALL, 'en_US.utf-8');
  5.  
  6. if ( php_sapi_name() == 'cli' ) {
  7.     define('IS_CLI', TRUE);
  8. }
  9.  
  10. error_reporting(E_ALL | E_NOTICE | E_STRICT | E_WARNING);
  11. ini_set('display_errors', 1);
  12.  
  13. // Working directories
  14. define('DIR', dirname(__FILE__) . '/');
  15. define('UPLOAD_DIR', DIR . 'images/');
////
// Setup environment
date_default_timezone_set('GMT');
setlocale(LC_ALL, 'en_US.utf-8');

if ( php_sapi_name() == 'cli' ) {
	define('IS_CLI', TRUE);
}

error_reporting(E_ALL | E_NOTICE | E_STRICT | E_WARNING);
ini_set('display_errors', 1);

// Working directories
define('DIR', dirname(__FILE__) . '/');
define('UPLOAD_DIR', DIR . 'images/');

Некоторый функционал удобно оформить в виде отдельных функций. Google возвращает разный результат в зависимости от user-агента пользователя. В коде я использую тот же user-агент, что и в используемом мной браузере.

  1. // Helper functions
  2. function debug($msg = '') {
  3.     // correct output both in browser and command-line interface
  4.     $eol = defined('IS_CLI') ? PHP_EOL : '<br />';
  5.     echo $msg . $eol;
  6.     flush(); // do not buffer the output
  7. }
  8.  
  9. // Send HTTP GET request to given url
  10. function http_request($url) {
  11.     $ch = curl_init();
  12.     curl_setopt($ch, CURLOPT_HEADER, FALSE);
  13.     curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
  14.     curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  15.  
  16.     curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.77 Safari/535.7');
  17.     curl_setopt($ch, CURLOPT_URL, $url);
  18.     $response = curl_exec($ch);
  19.     $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  20.     curl_close($ch);
  21.  
  22.     return array(
  23.         'code' => $code,
  24.         'body' => $response,
  25.     );
  26. }
  27.  
  28. function decode_image($base64_encoded_img) {
  29.     $data = substr($base64_encoded_img, strlen('data:image/jpeg;base64,'));
  30.     return base64_decode($data);
  31. }
// Helper functions
function debug($msg = '') {
	// correct output both in browser and command-line interface
	$eol = defined('IS_CLI') ? PHP_EOL : '<br />';
	echo $msg . $eol;
	flush(); // do not buffer the output
}

// Send HTTP GET request to given url
function http_request($url) {
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_HEADER, FALSE);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

	curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.77 Safari/535.7');
	curl_setopt($ch, CURLOPT_URL, $url);
	$response = curl_exec($ch);
	$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	curl_close($ch);

	return array(
		'code' => $code,
		'body' => $response,
	);
}

function decode_image($base64_encoded_img) {
	$data = substr($base64_encoded_img, strlen('data:image/jpeg;base64,'));
	return base64_decode($data);
}

Задаем входные значения (в дальнейшем эти данные можно брать из строки запроса, например).

  1. // Emulate input
  2.  
  3. $query = 'etretat'; // could easily be 'site:http://lenta.ru'
  4. $results_needed = 2; // how many thumbnails we gonna steal
// Emulate input

$query = 'etretat'; // could easily be 'site:http://lenta.ru'
$results_needed = 2; // how many thumbnails we gonna steal

Теперь к основной функциональности. Во-первых нам нужно собрать трёхзначные хэши..

  1. // Main process
  2.  
  3. $url = 'http://www.google.com/search?q=' . urlencode($query);
  4. $response = http_request($url);
  5.  
  6. // Parse response and find all SIG keys
  7. $dom = new DOMDocument;
  8. @$dom->loadHTML($response['body']);
  9. $xpath = new DOMXPath($dom);
  10.  
  11. $elements_li_g = $xpath->query('//div[@id="ires"]//li[@class="g"]');
  12. $sites = array();
  13.  
  14. if ($elements_li_g->length > 0)  {
  15.     $index = 0;
  16.     for ($i = 0; $i < $elements_li_g->length; $i++) {
  17.         if ($results_needed <= $index)
  18.             break;
  19.         $element_div = $xpath->query('div[@class="vsc"]', $elements_li_g->item($i));
  20.         if ($element_div->length == 0) {
  21.             continue;
  22.         }
  23.         $sig = $element_div->item(0)->getAttribute('sig');
  24.         $element_a = $xpath->query('h3/a', $element_div->item(0));
  25.         if ($element_a->length == 0) {
  26.             continue;
  27.         }
  28.         $href = $element_a->item(0)->getAttribute('href');
  29.         $sites[] = array(
  30.             'sig' => $sig,
  31.             'url' => $href,
  32.         );
  33.         $index++;
  34.     }
  35. }
  36. unset($dom, $xpath);
  37.  
  38. if (count($sites) == 0) {
  39.     debug('No results found');
  40.     exit;
  41. }
// Main process

$url = 'http://www.google.com/search?q=' . urlencode($query);
$response = http_request($url);

// Parse response and find all SIG keys
$dom = new DOMDocument;
@$dom->loadHTML($response['body']);
$xpath = new DOMXPath($dom);

$elements_li_g = $xpath->query('//div[@id="ires"]//li[@class="g"]');
$sites = array();

if ($elements_li_g->length > 0)  {
	$index = 0;
	for ($i = 0; $i < $elements_li_g->length; $i++) {
		if ($results_needed <= $index)
			break;
		$element_div = $xpath->query('div[@class="vsc"]', $elements_li_g->item($i));
		if ($element_div->length == 0) {
			continue;
		}
		$sig = $element_div->item(0)->getAttribute('sig');
		$element_a = $xpath->query('h3/a', $element_div->item(0));
		if ($element_a->length == 0) {
			continue;
		}
		$href = $element_a->item(0)->getAttribute('href');
		$sites[] = array(
			'sig' => $sig,
			'url' => $href,
		);
		$index++;
	}
}
unset($dom, $xpath);

if (count($sites) == 0) {
	debug('No results found');
	exit;
}

Имеем массив, содержащий url страницы и ее трехзначный хэш. То есть в наличии все данные для получения превью.

  1. $thumbnails = array();
  2. $half_encoded_query = str_replace(array('%3A', '%2F', '%2C'), array(':', '/', ','), urlencode($query));
  3.  
  4. foreach ($sites as $site) {
  5.     $filename_prefix = md5($site['url']);
  6.     $thumb = array(
  7.         'url' => $site['url'],
  8.         'img' => '',
  9.     );
  10.  
  11.     $url = 'http://clients1.google.com/webpagethumbnail?r=4&f=3&s=400:585&query='.$half_encoded_query.'&hl=en&gl=us&c=29&d='.urlencode($site['url']).'&b=1&a='.$site['sig'];
  12.     $response = http_request($url);
  13.     if ($response['code'] != '200')
  14.         continue;
  15.  
  16.     $data = $response['body'];
  17.     unset($response);
  18.     $json = preg_replace('/[^\{]*(\{.*\})[^\}]*/sim', '\1', $data);
  19.     $json = str_replace(array("\n", "\r"), '', $json);
  20.     $js_object = json_decode($json);
  21.  
  22.     $img_files = array();
  23.     if (isset($js_object->shards) && is_array($js_object->shards)) {
  24.         $index = 1;
  25.         foreach ($js_object->shards as $shard) {
  26.             if (isset($shard->imgs) && is_array($shard->imgs)) {
  27.                 foreach ($shard->imgs as $encoded_img) {
  28.                     $filename = $filename_prefix . '_' . $index . '.jpg';
  29.                     file_put_contents(UPLOAD_DIR . $filename, decode_image($encoded_img));
  30.                     $img_files[] = $filename;
  31.                     $index++;
  32.                 }
  33.             }
  34.         }
  35.  
  36.         if (count($img_files) > 0) {
  37.             $files = implode('" "', $img_files);
  38.             $filename = $filename_prefix . '.jpg';
  39.             shell_exec('cd '.UPLOAD_DIR.'; convert "' . $files . '" -append ' . UPLOAD_DIR . $filename);
  40.             $thumb['img'] = $filename;
  41.  
  42.             unset($files);
  43.             // commented for debug purposes
  44.             /*
  45.             foreach ($img_files as $file_to_delete) {
  46.                 if (file_exists(UPLOAD_DIR . $file_to_delete) && is_readable(UPLOAD_DIR . $file_to_delete)) {
  47.                     unlink(UPLOAD_DIR . $file_to_delete);
  48.                 }
  49.             }*/
  50.         }
  51.  
  52.     }
  53.     unset($json, $js_object, $img_files);
  54.     $thumbnails[] = $thumb;
  55. }
$thumbnails = array();
$half_encoded_query = str_replace(array('%3A', '%2F', '%2C'), array(':', '/', ','), urlencode($query));

foreach ($sites as $site) {
	$filename_prefix = md5($site['url']);
	$thumb = array(
		'url' => $site['url'],
		'img' => '',
	);

	$url = 'http://clients1.google.com/webpagethumbnail?r=4&f=3&s=400:585&query='.$half_encoded_query.'&hl=en&gl=us&c=29&d='.urlencode($site['url']).'&b=1&a='.$site['sig'];
	$response = http_request($url);
	if ($response['code'] != '200')
		continue;

	$data = $response['body'];
	unset($response);
	$json = preg_replace('/[^\{]*(\{.*\})[^\}]*/sim', '\1', $data);
	$json = str_replace(array("\n", "\r"), '', $json);
	$js_object = json_decode($json);

	$img_files = array();
	if (isset($js_object->shards) && is_array($js_object->shards)) {
		$index = 1;
		foreach ($js_object->shards as $shard) {
			if (isset($shard->imgs) && is_array($shard->imgs)) {
				foreach ($shard->imgs as $encoded_img) {
					$filename = $filename_prefix . '_' . $index . '.jpg';
					file_put_contents(UPLOAD_DIR . $filename, decode_image($encoded_img));
					$img_files[] = $filename;
					$index++;
				}
			}
		}

		if (count($img_files) > 0) {
			$files = implode('" "', $img_files);
			$filename = $filename_prefix . '.jpg';
			shell_exec('cd '.UPLOAD_DIR.'; convert "' . $files . '" -append ' . UPLOAD_DIR . $filename);
			$thumb['img'] = $filename;

			unset($files);
			// commented for debug purposes
			/*
			foreach ($img_files as $file_to_delete) {
				if (file_exists(UPLOAD_DIR . $file_to_delete) && is_readable(UPLOAD_DIR . $file_to_delete)) {
					unlink(UPLOAD_DIR . $file_to_delete);
				}
			}*/
		}

	}
	unset($json, $js_object, $img_files);
	$thumbnails[] = $thumb;
}

Проходим по каждому элементу массива и выполняем запрос на http://clients1.google.com/webpagethumbnail. Полученные части скриншота декодируем и сохраняем в директорию UPLOAD_DIR. Для объединения нескольких картинок в одну я использовал утилиту convert из набора ImageMagick. Операция объединения довольно простая — картинки имеют одинаковую ширину и их необходимо лишь расположить друг по дружкой. После получения большого скриншота, временные файлы можно удалять.

Магия с $half_encoded_query в том, что параметр «query»  в запросе Google кодирует несколько иначе, чем urlencode. Поэтому и был применён такой грязный хак.

Выводим результат работы скрипта на экран

  1. $count = count($thumbnails);
  2.  
  3. debug($count . ' thumbnail' . ($count > 1 ? 's' : '') . ' downloaded');
  4. if ($count == 0) {
  5.     exit;
  6. }
  7.  
  8. foreach ($thumbnails as $thumb) {
  9.     debug();
  10.     debug($thumb['url']);
  11.     debug($thumb['img']);
  12.  
  13.     if (!defined('IS_CLI')) {
  14.         debug('<img src="images/'.$thumb['img'].'" alt="'.$thumb['img'].'" />');
  15.     }
  16. }
$count = count($thumbnails);

debug($count . ' thumbnail' . ($count > 1 ? 's' : '') . ' downloaded');
if ($count == 0) {
	exit;
}

foreach ($thumbnails as $thumb) {
	debug();
	debug($thumb['url']);
	debug($thumb['img']);

	if (!defined('IS_CLI')) {
		debug('<img src="images/'.$thumb['img'].'" alt="'.$thumb['img'].'" />');
	}
}

 

Заключение

На основе этого скрипта мной был создан небольшой сервис thumbtool.phpotdel.ru, где любой желающий может «проэксплуатировать» Google.

Спасибо за внимание!

P. S. Исходный код приведённого примера можно скачать

Скрипт загрузки скриншотов с Google: 13 комментариев

  1. Marco

    Hi, i download the script but the «sig» parameter doesn’t work and the second call to the webpagethumbnail return error 400

    1. phpotdel Автор записи

      sig keys are site + query specific — so be sure that you are using sig for the site specified in «d» attribute. also SIG keys are not permanent — its a hash and it is changed often.

      1. Marco

        ok, i’ve found a solution.

        The first call to http://www.google.com returns a sig that doesn’t match with the second call to clients1.google.com/webpagethumbnail. To make this script working from italy i have to call http://www.google.it and then clients1.google.com/webpagethumbnail
        with hl=it and gl=it parameters.
        Calling .com and then with hl=en&gl=us doesn’t work!

        Probably the redirect is the problem…

    1. phpotdel Автор записи

      да, такое бывает, когда гугл считает запрос подозрительным (может быть слишком часто пытались слать запросы? )

      есть несколько способов как с этим бороться:
      1. распознавать, что гугл просит ввести капчу и решать ее с помощью спец сервисов — в сети куча всяких декапчеров. Цена вопроса невысока — порядка $2 за 1000 распознанных капч. Это если вы активно пинаете google и вам очень-очень надо от него получить ответы и очень часто
      2. поступить хитро. подменить куки, с которыми осуществляется запрос. просто откройте настройки любимого браузера и найдите cookie, которые сохранились от сайта google.ru (или google.com — смотря, чем пользуетесь). Возьмите эти значение и запишите в файл, который будет использовать cUrl. В своей статье об установке нужного количества результатов в поиске для автоматических запросов я использовал cookie атрибуты для cUrl. http://phpotdel.ru/2012/06/30/kolichestvo-rezultatov-v-poiske-google.html

      если сделать все правильно, то гугл больше не будет показывать капчу (пока правильные куки находятся в файле). можно для верности поставить очень большой expire для всех кук в файле.

      1. Emil

        Спасибо, будем пробовать.
        Кстати было бы неплохо еще рассмотреть как бороться с «безопасным» поиском, который не дает получить некоторые скриншоты. Спасибо.

    1. phpotdel Автор записи

      Я в процессе обновления/переписывания кучи вещей сейчас, так что я очень надеюсь, что до конца года разгребу все что накопилось. Обязательно вышлю доступ, если это еще актуально

      Спасибо за интерес к тулзе

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code lang=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" extra="">