Как я могу очистить содержимое веб-сайта на PHP с веб-сайта, для которого требуется вход в систему с использованием файлов cookie?

Моя проблема в том, что он не просто требует базового файла cookie, а скорее запрашивает файл cookie сеанса и случайно сгенерированные идентификаторы. Я думаю, это означает, что мне нужно использовать эмулятор веб-браузера с банкой cookie?

Я пытался использовать Snoopy, Goutte и несколько других эмуляторов веб-браузеров, но пока не смог найти руководства по получению файлов cookie. Я немного впадаю в отчаяние!

Может ли кто-нибудь привести пример того, как принимать файлы cookie в Snoopy или Goutte?

Заранее спасибо!


person Forest    schedule 03.11.2012    source источник
comment
Просто используйте curl с опциями CURLOPT_COOKIEJAR и CURLOPT_COOKIEFILE   -  person Lawrence Cherone    schedule 03.11.2012
comment
Сохранит ли Curl файлы cookie в течение всего сеанса? Мне нужно посетить несколько страниц на одном сайте под одним логином?   -  person Forest    schedule 03.11.2012
comment
Сервер отправляет заголовок Set-Cookie в ответ, когда он хочет, чтобы вы установили, обновили или удалили файлы cookie. Клиент отправляет заголовок Cookie в запросах, чтобы сообщить серверу, какие файлы cookie в данный момент активны у клиента.   -  person Esailija    schedule 03.11.2012


Ответы (2)


Объектно-ориентированный ответ

Мы максимально реализуем предыдущий ответ в одном классе с именем Browser, который должен предоставлять обычные функции навигации.

Затем мы должны иметь возможность поместить специфичный для сайта код в очень простой форме в новый производный класс, который мы назовем, скажем, FooBrowser, который выполняет очистку сайта Foo.

Класс, производный от Browser, должен предоставлять некоторую функцию, специфичную для сайта, такую ​​как функция 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 (e.g. 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");
    }
    // www.foo.com is a ticker site, we need little info for each
    // Other examples might be much more complex.
    $entries = [
        'APPL', 'MSFT', 'XKCD'
    ];
    foreach ($entries as $entry) {
        $html = $scraper->scrape($entry);
        // Parse HTML
    }

Обязательное уведомление: используйте подходящий анализатор для получения данных из необработанного HTML.

person LSerni    schedule 15.12.2016

Вы можете сделать это в cURL без необходимости использования внешних «эмуляторов».

Приведенный ниже код извлекает страницу в переменную PHP для анализа.

Сценарий

Есть страница (назовем ее ГЛАВНАЯ), которая открывает сеанс. Серверная сторона, если она написана на PHP, — это та сторона (фактически любая), которая вызывает session_start() в первый раз. На других языках вам нужна определенная страница, которая будет выполнять всю настройку сеанса. Со стороны клиента это страница, предоставляющая файл cookie идентификатора сеанса. В PHP это делают все сессионные страницы; на других языках это сделает лендинг, все остальные проверят, есть ли куки, а если нет, то вместо создания сеанса перебросит вас на ГЛАВНУЮ.

Существует страница (ВХОД), которая генерирует форму входа и добавляет в сеанс важную информацию - «Этот пользователь вошел в систему». В приведенном ниже коде это страница, запрашивающая идентификатор сеанса.

И, наконец, есть N страниц, на которых находятся полезные вещи.

Итак, мы хотим нажать HOME, затем LOGIN, затем GOODIES один за другим. Опять же, в 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, что и любой браузер, который собираемся использовать для отладки страницы, плюс суффикс, чтобы тот, кто проверяет, понял, что это автоматизированный инструмент, на который они смотрят (см. комментарий Халфер).

    $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, по-прежнему достаточно для запуска создания сеанса и отправки файлов cookie, и если это так, это происходит быстрее. Но CURLOPT_NOBODY ("нет тела") работает, выдавая запрос HEAD вместо GET; а иногда запрос HEAD не работает, потому что сервер будет реагировать только на полный GET.

Вместо того, чтобы извлекать тело таким образом, также можно войти в систему с помощью настоящего Firefox и обнюхать содержимое формы, публикуемое с помощью Firebug (или Chrome с инструментами Chrome); некоторые сайты пытаются заполнить/изменить скрытые поля с помощью Javascript, чтобы отправляемая форма была не той, которую вы видите в HTML-коде.

Веб-мастер, который хотел, чтобы его сайт не был очищен, может отправить скрытое поле с отметкой времени. Человеку (без помощи слишком умного браузера - есть способы сказать браузерам не быть умными; в худшем случае, каждый раз, когда вы меняете имя пользователя и поля ввода) требуется не менее трех секунд, чтобы заполнить форму. Сценарий cURL принимает ноль. Конечно, задержку можно смоделировать. Это все бой с тенью...

Мы также можем обратить внимание на внешний вид формы. Веб-мастер может, например, создать форму, запрашивающую имя, адрес электронной почты и пароль; а затем с помощью CSS переместите поле «электронная почта» туда, где вы ожидаете найти имя, и наоборот. Таким образом, отправляемая настоящая форма будет иметь "@" в поле с именем username и ни одного в поле с именем email. Сервер, который ожидает этого, просто снова инвертирует два поля. Созданный вручную "скребок" (или спам-бот) сделает то, что кажется естественным, и отправит электронное письмо в поле email. И тем самым выдает себя. Проработав форму один раз в реальном браузере с поддержкой CSS и JS, отправив значимые данные и проанализировав, что на самом деле отправляется, мы возможно сможем преодолеть это конкретное препятствие. Может, потому что есть способы усложнить жизнь. Как я уже сказал, бой с тенью.

Вернемся к рассматриваемому случаю. В данном случае форма содержит три поля и не имеет наложения Javascript. У нас есть cPASS, cUSR и checkLOGIN со значением «Проверить логин».

Итак, мы готовим форму с соответствующими полями. Обратите внимание, что форма должна быть отправлена ​​как application/x-www-form-urlencoded, что в PHP cURL означает две вещи:

  • мы должны использовать CURLOPT_POST
  • параметр CURLOPT_POSTFIELDS должен быть строкой (массив будет сигнализировать cURL о необходимости отправки как multipart/form-data, что может работать... а может и нет).

Поля формы, как говорится, urlencoded; для этого есть функция.

Читаем поле action формы; это 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-кодом, а также не допускать ложных срабатываний или ложных отрицательных результатов, когда макет остается прежним, но содержание меняется (например, вы обнаружите, что у вас есть прогнозы погоды из Ниццы, Турретт-Левенс, Кастанье, но никогда не Аспремон или Гатьер, и разве это не любопытно?), лучший вариант — использовать DOM:

Захват атрибута href элемента A

person LSerni    schedule 03.11.2012
comment
Страница, на которую мне нужно войти, — это ссылка. Не могли бы вы привести небольшой пример с этим? Извините, если я прошу слишком много... У меня нет опыта работы с curl. - person Forest; 03.11.2012
comment
Начал так делать, но что-то вышло. Вернусь через несколько часов, чтобы завершить ответ, извините. - person LSerni; 03.11.2012
comment
Только что проверил код! Работает как мечта! Это делает именно то, что я хотел: D - person Forest; 04.11.2012
comment
Обновление: я практиковался в использовании этого кода на нескольких веб-сайтах и ​​преуспел в создании API и кода входа для всех из них! Большое спасибо за этот урок! - person Forest; 09.11.2012
comment
Отличный ответ, но в Интернете считается дурным тоном объявлять сканер обычным браузером. OP следует поощрять быть честным в строке агента, например GoogleBot, и использовать что-то вроде MyScraper v0.1 (подробности см. на www.myscraperinfo.com). Таким образом, он может быть выборочно заблокирован веб-мастером, если он того пожелает, поскольку это его право. - person halfer; 17.02.2013
comment
Очень хорошее предложение, спасибо @halfer! На самом деле я основывался на этом, потому что несколько серверов и фреймворков будут отправлять различный код HTML/JS в ответ на User-Agent, что может привести к некоторой головной боли, если кто-то будет автоматизировать проверяемую вручную «навигацию». - person LSerni; 18.02.2013
comment
Откуда берутся $urls? Я думаю, что это должно быть с возвращенной страницы. - person user2906838; 01.12.2016
comment
Привет. я хочу, чтобы вы ответили на вопрос для меня. Что такое URL-адрес, генерирующий идентификатор сеанса, и URL-адрес, запрашивающий сеанс, и URL-адрес входа, генерирующий идентификатор сеанса. Спасибо. - person Văn Tuấn Phạm; 15.12.2016
comment
Хорошая точка зрения. Я немного накосячил с номенклатурой. В ответ добавлен «Сценарий», чтобы он был понятнее. - person LSerni; 15.12.2016
comment
Вы можете учить шаг за шагом для меня. Спасибо большое. я не понимаю :( - person Văn Tuấn Phạm; 15.12.2016
comment
Я хочу войти в систему с помощью curl и получить контент на другой странице, требующей входа в систему. - person Văn Tuấn Phạm; 15.12.2016
comment
Могу, но не сейчас - попробую поболтать часов через пять. В зависимости от ваших знаний о PHP я добавил другой взгляд на тот же ответ «Объектно-ориентированный ответ» ниже. Вы можете посмотреть на это. - person LSerni; 15.12.2016