Моя проблема заключается в том, что он требует не просто базового файла cookie, а скорее просит cookie сеанса и для произвольно сгенерированных идентификаторов. Я думаю, это означает, что мне нужно использовать эмулятор веб-браузера с банкой cookie?
Я попытался использовать Snoopy, Goutte и пару других эмуляторов веб-браузера, но пока еще не смог найти учебники о том, как получать файлы cookie. Я немного отчаялся!
Может ли кто-нибудь дать мне пример того, как принимать файлы cookie в Snoopy или Goutte?
Заранее спасибо!
Мы максимально используем вышеупомянутый ответ в одном классе под названием « Browser
который должен обеспечивать обычные функции навигации.
Тогда мы сможем в очень простой форме поместить код, специфичный для сайта, в новый производный класс, который мы называем, например, FooBrowser
, который выполняет очистку сайта Foo
.
Браузер, вызывающий класс, должен предоставить определенную функцию для конкретного сайта, такую как функция path()
позволяющая хранить информацию, относящуюся к конкретному сайту, например
function path($basename) { return '/var/tmp/www.foo.bar/' . $basename; } abstract class Browser { private $options = []; private $state = []; protected $cookies; abstract protected function path($basename); public function __construct($site, $options = []) { $this->cookies = $this->path('cookies'); $this->options = array_merge( [ 'site' => $site, 'userAgent' => 'Mozilla/5.0 (Windows NT 5.1; rv:16.0) Gecko/20100101 Firefox/16.0 - LeoScraper', 'waitTime' => 250000, ], $options ); $this->state = [ 'referer' => '/', 'url' => '', 'curl' => '', ]; $this->__wakeup(); } /** * Reactivates after sleep (eg in session) or creation */ public function __wakeup() { $this->state['curl'] = curl_init(); $this->config([ CURLOPT_USERAGENT => $this->options['userAgent'], CURLOPT_ENCODING => '', CURLOPT_NOBODY => false, // ...retrieving the body... CURLOPT_BINARYTRANSFER => true, // ...as binary... CURLOPT_RETURNTRANSFER => true, // ...into $ret... CURLOPT_FOLLOWLOCATION => true, // ...following redirections... CURLOPT_MAXREDIRS => 5, // ...reasonably... CURLOPT_COOKIEFILE => $this->cookies, // Save these cookies CURLOPT_COOKIEJAR => $this->cookies, // (already set above) CURLOPT_CONNECTTIMEOUT => 30, // Seconds CURLOPT_TIMEOUT => 300, // Seconds CURLOPT_LOW_SPEED_LIMIT => 16384, // 16 Kb/s CURLOPT_LOW_SPEED_TIME => 15, ]); } /** * Imports an options array. * * @param array $opts * @throws DetailedError */ private function config(array $opts = []) { foreach ($opts as $key => $value) { if (true !== curl_setopt($this->state['curl'], $key, $value)) { throw new \Exception('Could not set cURL option'); } } } private function perform($url) { $this->state['referer'] = $this->state['url']; $this->state['url'] = $url; $this->config([ CURLOPT_URL => $this->options['site'] . $this->state['url'], CURLOPT_REFERER => $this->options['site'] . $this->state['referer'], ]); $response = curl_exec($this->state['curl']); // Should we ever want to randomize waitTime, do so here. usleep($this->options['waitTime']); return $response; } /** * Returns a configuration option. * @param string $key configuration key name * @param string $value value to set * @return mixed */ protected function option($key, $value = '__DEFAULT__') { $curr = $this->options[$key]; if ('__DEFAULT__' !== $value) { $this->options[$key] = $value; } return $curr; } /** * Performs a POST. * * @param $url * @param $fields * @return mixed */ public function post($url, array $fields) { $this->config([ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($fields), ]); return $this->perform($url); } /** * Performs a GET. * * @param $url * @param array $fields * @return mixed */ public function get($url, array $fields = []) { $this->config([ CURLOPT_POST => false ]); if (empty($fields)) { $query = ''; } else { $query = '?' . http_build_query($fields); } return $this->perform($url . $query); } }
Теперь, чтобы очистить FooSite:
/* WWW_FOO_COM requires username and password to construct */ class WWW_FOO_COM_Browser extends Browser { private $loggedIn = false; public function __construct($username, $password) { parent::__construct('http://www.foo.bar.baz', [ 'username' => $username, 'password' => $password, 'waitTime' => 250000, 'userAgent' => 'FooScraper', 'cache' => true ]); // Open the session $this->get('/'); // Navigate to the login page $this->get('/login.do'); } /** * Perform login. */ public function login() { $response = $this->post( '/ajax/loginPerform', [ 'j_un' => $this->option('username'), 'j_pw' => $this->option('password'), ] ); // TODO: verify that response is OK. // if (!strstr($response, "Welcome " . $this->option('username')) // throw new \Exception("Bad username or password") $this->loggedIn = true; return true; } public function scrape($entry) { // We could implement caching to avoid scraping the same entry // too often. Save $data into path("entry-" . md5($entry)) // and verify the filemtime of said file, is it newer than time() // minus, say, 86400 seconds? If yes, return file_get_content and // leave remote site alone. $data = $this->get( '/foobars/baz.do', [ 'ticker' => $entry ] ); return $data; }
Теперь фактический код очистки:
$scraper = new WWW_FOO_COM_Browser('lserni', 'mypassword'); if (!$scraper->login()) { throw new \Exception("bad user or pass"); } foreach ($entries as $entry) { $html = $scraper->scrape($entry); // Parse HTML }
Обязательное уведомление: используйте подходящий синтаксический анализатор для получения данных из необработанного HTML .
Вы можете сделать это в cURL без использования внешних эмуляторов.
Приведенный ниже код извлекает страницу в переменную PHP, подлежащую анализу.
Существует страница (назовем ее HOME), которая открывает сеанс. Серверная сторона, если она находится в PHP, является первой ( любой на самом деле), вызывающей session_start()
в первый раз. На других языках вам нужна специальная страница, которая будет выполнять всю настройку сеанса. С клиентской стороны это страница, в которой содержится файл cookie идентификатора сеанса. В PHP все сеансовые страницы; в других языках целевая страница будет делать это, все остальные будут проверять, есть ли файл cookie, а если нет, вместо создания сеанса вы попадете в HOME.
Существует страница (LOGIN), которая генерирует регистрационную форму и добавляет важную информацию в сеанс – «Этот пользователь вошел в систему». В приведенном ниже коде это страница, запрашивающая идентификатор сеанса.
И, наконец, есть N страниц, где хранятся лакомства.
Таким образом, мы хотим поразить ДОМАШНУЮ, затем ВХОД, а потом ПОЛНОСТЬЮ один за другим. В PHP (и на других языках), опять же, HOME и LOGIN вполне могут быть одной и той же страницей. Или все страницы могут иметь один и тот же адрес , например, в приложениях с одной страницей.
$url = "the url generating the session ID"; $next_url = "the url asking for session"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); // We do not authenticate, only access page to get a session going. // Change to False if it is not enough (you'll see that cookiefile // remains empty). curl_setopt($ch, CURLOPT_NOBODY, True); // You may want to change User-Agent here, too curl_setopt($ch, CURLOPT_COOKIEFILE, "cookiefile"); curl_setopt($ch, CURLOPT_COOKIEJAR, "cookiefile"); // Just in case curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $ret = curl_exec($ch); // This page we retrieve, and scrape, with GET method foreach(array( CURLOPT_POST => False, // We GET... CURLOPT_NOBODY => False, // ...the body... CURLOPT_URL => $next_url, // ...of $next_url... CURLOPT_BINARYTRANSFER => True, // ...as binary... CURLOPT_RETURNTRANSFER => True, // ...into $ret... CURLOPT_FOLLOWLOCATION => True, // ...following redirections... CURLOPT_MAXREDIRS => 5, // ...reasonably... CURLOPT_REFERER => $url, // ...as if we came from $url... //CURLOPT_COOKIEFILE => 'cookiefile', // Save these cookies //CURLOPT_COOKIEJAR => 'cookiefile', // (already set above) CURLOPT_CONNECTTIMEOUT => 30, // Seconds CURLOPT_TIMEOUT => 300, // Seconds CURLOPT_LOW_SPEED_LIMIT => 16384, // 16 Kb/s CURLOPT_LOW_SPEED_TIME => 15, // ) as $option => $value) if (!curl_setopt($ch, $option, $value)) die("could not set $option to " . serialize($value)); $ret = curl_exec($ch); // Done; cleanup. curl_close($ch);
Прежде всего, мы должны получить страницу входа в систему.
Мы используем специальный User-Agent, чтобы представить себя, чтобы быть узнаваемыми (мы не хотим антагонизировать веб-мастера), а также обманывать сервер, отправляя нам определенную версию сайта с учетом браузера. В идеале мы используем тот же User-Agent, что и любой браузер, который мы собираемся использовать для отладки страницы, плюс суффикс, чтобы дать понять, кто бы ни проверял, что это автоматический инструмент, на который они смотрят ( см. Комментарий Halfer ) ,
$ua = 'Mozilla/5.0 (Windows NT 5.1; rv:16.0) Gecko/20100101 Firefox/16.0 (ROBOT)'; $cookiefile = "cookiefile"; $url1 = "the login url generating the session ID"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url1); curl_setopt($ch, CURLOPT_USERAGENT, $ua); curl_setopt($ch, CURLOPT_COOKIEFILE, $cookiefile); curl_setopt($ch, CURLOPT_COOKIEJAR, $cookiefile); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, True); curl_setopt($ch, CURLOPT_NOBODY, False); curl_setopt($ch, CURLOPT_RETURNTRANSFER, True); curl_setopt($ch, CURLOPT_BINARYTRANSFER, True); $ret = curl_exec($ch);
Это позволит получить страницу с запросом пользователя / пароля. Просматривая страницу, мы находим необходимые поля (в том числе скрытые) и можем их заполнять. Тег FORM
указывает нам, нужно ли нам использовать POST или GET.
Возможно, нам захочется проверить код формы, чтобы настроить следующие операции, поэтому мы просим cURL вернуть содержимое страницы как-в $ret
и вернуть тело страницы. Иногда для CURLOPT_NOBODY
установленного в True
, все еще достаточно, чтобы инициировать создание сеанса и подачу CURLOPT_NOBODY
cookie, и если да, то это быстрее. Но CURLOPT_NOBODY
(«no body») работает, выдавая запрос HEAD
вместо GET
; и иногда запрос HEAD
не работает, потому что сервер будет реагировать только на полный GET
.
Вместо того, чтобы получать тело таким образом, можно также войти в систему с помощью реального Firefox и обнюхать содержимое формы, которое будет отправлено Firebug (или Chrome с помощью Chrome Tools); некоторые сайты будут пытаться заполнять / изменять скрытые поля с помощью Javascript, так что представленная форма не будет той, что вы видите в HTML-коде.
Веб-мастер, который хотел, чтобы его сайт не царапался, может отправить скрытое поле с меткой времени. Человек (которому не помогает слишком умный браузер) – есть способы сказать, что браузеры не должны быть умными, а в худшем случае каждый раз, когда вы меняете имя поля пользователя и пароля) требуется не менее трех секунд, чтобы заполнить форму. Сценарий cURL принимает нуль. Конечно, можно моделировать задержку. Это все тень …
Мы также можем захотеть найти внешний вид формы. Например, веб-мастер может создать форму, запрашивающую имя, адрес электронной почты и пароль; а затем, используя CSS, переместите поле «электронная почта», где вы ожидаете найти имя, и наоборот. Таким образом, реальная форма будет иметь «@» в поле под названием username
, в поле «E- email
нет ни одного. Сервер, ожидающий этого, просто инвертирует два поля. «Скребок», созданный вручную (или спамботом), будет делать то, что кажется естественным, и отправить электронное письмо в поле email
. И тем самым он предает себя. Работая через форму один раз с помощью реального браузера с поддержкой CSS и JS, отправляя значимые данные и обнюхивая то, что действительно отправляется, мы могли бы преодолеть это конкретное препятствие. Возможно , потому что есть способы сделать жизнь трудной. Как я уже сказал, shadowboxing .
Вернемся к делу, в этом случае форма содержит три поля и не имеет наложения JavaScript. У нас есть cPASS
, cUSR
и checkLOGIN
со значением 'Check login'.
Поэтому мы готовим форму с соответствующими полями. Обратите внимание, что форма должна быть отправлена как application/x-www-form-urlencoded
, что в PHP cURL означает две вещи:
CURLOPT_POST
multipart/form-data
, которые могут работать … или не могут). Поля формы – это, как говорится, urlencoded; для этого есть функция.
Мы читаем поле действия формы; это URL-адрес, который мы должны использовать для отправки нашей аутентификации (что мы должны иметь).
Итак, все готово …
$fields = array( 'checkLOGIN' => 'Check Login', 'cUSR' => 'jb007', 'cPASS' => 'astonmartin', ); $coded = array(); foreach($fields as $field => $value) $coded[] = $field . '=' . urlencode($value); $string = implode('&', $coded); curl_setopt($ch, CURLOPT_URL, $url1); //same URL as before, the login url generating the session ID curl_setopt($ch, CURLOPT_POST, True); curl_setopt($ch, CURLOPT_POSTFIELDS, $string); $ret = curl_exec($ch);
Мы ожидаем теперь «Привет, Джеймс – как насчет хорошей игры в шахматы?» стр. Но более того, мы ожидаем, что сессия, связанная с файлом cookie, сохраненным в $cookiefile
, была снабжена важной информацией – «пользователь аутентифицирован» .
Таким образом, все последующие запросы страниц, сделанные с использованием $ch
и той же банки cookie, получат доступ, что позволяет нам легко «очищать» страницы – просто не забудьте установить режим запроса обратно в GET
:
curl_setopt($ch, CURLOPT_POST, False); // Start spidering foreach($urls as $url) { curl_setopt($ch, CURLOPT_URL, $url); $HTML = curl_exec($ch); if (False === $HTML) { // Something went wrong, check curl_error() and curl_errno(). } } curl_close($ch);
В цикле у вас есть доступ к $HTML
– HTML-коду каждой отдельной страницы.
Большой соблазн использовать регулярные выражения. Сопротивляй это нужно. Чтобы лучше справляться с постоянно меняющимся HTML, а также быть уверенным, что не появляйте ложные срабатывания или ложные негативы, когда макет остается таким же, но изменяется контент (например, вы обнаруживаете, что у вас есть прогнозы погоды в Ницце, Турретте-Левенс, Кастаньеры, но никогда не Asprémont или Gattières, и не так ли это?), Лучший вариант – использовать DOM:
Схват атрибута href элемента A