Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
49
Help us understand the problem. What is going on with this article?
@mpyw

シリアライズ可能なcURLのラッパークラス

More than 5 years have passed since last update.

Cookie がシリアライズ可能なcURLのラッパークラスがあんまり無いような気がしたので作ってみました。

Source

BSD2条項ライセンス でお願いします。

<?php

/**
 * cURL
 * 
 * @author CertaiN
 * @github https://github.com/Certainist/cURL
 * @license BSD 2-Clause
 */
class cURL implements Serializable {

    /**
     * Default User-Agent.
     * 
     * @static
     * @access public
     */
    public static $defaultUserAgent = 'Chrome';

    private $ch;
    private $fp;
    private $userAgent;
    private $cookie;

    /**
     * You have to call parent::__construct() on your extended method.
     * 
     * @magic
     * @access public
     * @param string [$user_agent = null]
     */
    public function __construct($user_agent = null) {
        $list = static::getUserAgents();
        if (!func_num_args()) {
            $user_agent = static::$defaultUserAgent;
        }
        if (!is_string($user_agent)) {
            throw new InvalidArgumentException('User-Agent value type must be string.');
        }
        if (!is_array($list)) {
            throw new DomainException('static::getUserAgents() must return 1 dimentional assoc.');
        }
        if (!array_key_exists($user_agent, $list)) {
            throw new InvalidArgumentException('Unknown User-Agent.');
        }
        if (!is_string($list[$user_agent])) {
            throw new DomainException('static::getUserAgents() return array must contain string values.');
        }
        $this->userAgent = $list[$user_agent];
        $this->init();
    }

    /**
     * For GET requests.
     * 
     * @access public
     * @param string $url
     * @param mixed [&$info = null] Set result of curl_getinfo().
     * @return string Response body.
     */
    public function get($url, &$info = null) {
        if (!is_string($url)) {
            throw new InvalidArgumentException('URL value type must be string.');
        }
        if (!is_resource($this->ch)) {
            throw new BadMethodCallException('cURL resource is not initialized');
        }
        curl_setopt_array($this->ch, array(
            CURLOPT_URL => $url,
            CURLOPT_HTTPGET => true,
        ));
        return $this->exec($info);
    }

    /**
     * For POST requests.
     * 
     * @access public
     * @param string $url
     * @param mixed $params Query string or associative array.
     * @param mixed [&$info = null] Set result of curl_getinfo().
     * @return string Response body.
     */
    public function post($url, $params, &$info = null) {
        if (!is_string($url)) {
            throw new InvalidArgumentException('URL value type must be string.');
        }
        if (!is_resource($this->ch)) {
            throw new BadMethodCallException('cURL resource is not initialized');
        }
        curl_setopt_array($this->ch, array(
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $params,
        ));
        return $this->exec($info);
    }

    /**
     * Clear cookies.
     * 
     * @access public
     */
    public function clearCookies() {
        if (!is_resource($this->ch)) {
            throw new BadMethodCallException('cURL resource is not initialized');
        }
        ftruncate($this->fp, 0);
    }

    /**
     * Return the list of User-Agents.
     * You can extend this method.
     * 
     * @static
     * @access protected
     * @return array Associative array.
     */
    protected static function getUserAgents() {
        return array(
            'Chrome' =>
                'Mozilla/5.0 (Windows NT 6.1) ' .
                'AppleWebKit/537.36 (KHTML, like Gecko) ' .
                'Chrome/28.0.1500.63 Safari/537.36'
            ,
            'Firefox' =>
                'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:9.0.1) ' .
                'Gecko/20100101 Firefox/9.0.1'
            ,
            'Android' =>
                'Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03S) ' .
                'AppleWebKit/535.19 (KHTML, like Gecko) ' .
                'Chrome/18.0.1025.166 Safari/535.19'
            ,
            'iOS' =>
                'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) ' .
                'AppleWebKit/536.26 (KHTML, like Gecko) ' .
                'Version/6.0 Mobile/10A403 Safari/8536.25'
            ,
            'Windows Phone' =>
                'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; ' .
                'Trident/5.0; IEMobile/9.0; ' .
                'FujitsuToshibaMobileCommun; IS12T; KDDI)'
            ,
            'Internet Explorer' =>
                'Mozilla/5.0 (Windows NT 6.3; WOW64; ' . 
                'Trident/7.0; Touch; rv:11.0) like Gecko'
            ,
        );
    }

    /**
     * Serialize your own properties.
     * You can extend this method.
     * 
     * @access protected
     * @return mixed
     */
    protected function userSerialize() { return null; }

    /**
     * Unserialize your own properties.
     * You can extend this method.
     * 
     * @param anything $data
     * @access protected
     * @return mixed
     */
    protected function userUnserialize($data) { }

    /**
     * You have to call parent::__destruct() on your extended method.
     * 
     * @magic
     * @access public
     */
    public function __destruct() {
        if (is_resource($this->ch)) {
            curl_close($this->ch);
        }
        if (is_resource($this->fp)) {
            $this->cookie = stream_get_contents($this->fp);
            fclose($this->fp);
        }
    }

    final public function serialize() {
        $this->__destruct();
        $this->init($this->cookie);
        return serialize(array(
            $this->userAgent,
            $this->cookie,
            $this->userSerialize(),
        ));
    }

    final public function unserialize($data) {
        if (
            !$data = @unserialize($data) or
            !array_key_exists(0, $data) or
            !array_key_exists(1, $data) or
            !array_key_exists(2, $data) or
            !in_array($data[0], static::getUserAgents(), true) or
            !is_string($data[1])
        ) {
            throw new UnexpectedValueException('Invalid serial');
        }
        $this->userAgent = $data[0];
        $this->init($data[1]);
        $this->userUnserialize($data[2]);
    }

    private function exec(&$info) {
        $ret = curl_exec($this->ch);
        $info = curl_getinfo($this->ch);
        return $ret;
    }

    private function init($data = '') {
        $this->fp = tmpfile();
        if ($data !== '') {
            fwrite($this->fp, $data);
            rewind($this->fp);
        }
        $info = stream_get_meta_data($this->fp);
        $cookie_uri = $info['uri'];
        $this->ch = curl_init();
        curl_setopt_array($this->ch, array(
            CURLINFO_HEADER_OUT => true,
            CURLOPT_AUTOREFERER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => 15,
            CURLOPT_MAXREDIRS => 5,
            CURLOPT_COOKIEFILE => $cookie_uri,
            CURLOPT_COOKIEJAR => $cookie_uri,
            CURLOPT_ENCODING => 'gzip, deflate',
            CURLOPT_USERAGENT => $this->userAgent,
            CURLOPT_HTTPHEADER => array(
                'Accept: ' .
                    'text/html,' . 
                    'application/xhtml+xml,' .
                    'application/xml' .
                    ';q=0.9,*/*;q=0.8'
                ,
                'Accept-Language: ' .
                    'ja,en-us;q=0.7,en;q=0.3'
                ,
            ),
        ));
    }

}

Example

ニコニコ動画にログインして履歴を取得する例

class NicoNico extends cURL {

    public function __construct($mail_tel, $password) {
        parent::__construct('Chrome');
        $this->get('http://www.nicovideo.jp/login');
        $this->post(
            'https://secure.nicovideo.jp/secure/login?site=niconico',
            array(
                'next_url' => '',
                'mail_tel' => $mail_tel,
                'password' => $password,
            ),
            $info
        );
        if ($info['url'] !== 'http://www.nicovideo.jp/') {
            throw new RuntimeException('ログインに失敗しました');
        }
    }

    public function getHistory() {
        $regex = implode('.*?', array(
            '<div class="outer" id="outer_sm(\d*+)">',
            '<img src="([^"]*+)" alt="([^"]*+)" class="video" />',
            '<span class="videoTime">([^<]*+)</span>',
            '<p class="posttime">(\d*+年\d*+月\d*+日 \d*+:\d*+)',
            '<span>視聴回数(\d*+)回</span>',
            '<li class="play">再生:([^<]*+)</li>',
            '<li class="comment">コメント:([^<]*+)</li>',
            '<li class="mylist">マイリスト:<a href="[^"]*+">([^<]*+)</a></li>',
            '<li class="posttime">(\d*+年\d*+月\d*+日 \d*+:\d*+) 投稿</li>',
        ));
        $regex = "@{$regex}@s";
        $str = $this->get('http://www.nicovideo.jp/my/history');
        if (!preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) {
            throw new RuntimeException('履歴取得に失敗しました');
        }
        foreach ($matches as $match) {
            $ret[] = array(
                'id' => $match[1],
                'url' => "http://www.nicovideo.jp/watch/sm{$match[1]}",
                'thumb_url' => $match[2],
                'title' => $match[3],
                'duraction' => $match[4],
                'watched_at' => $match[5],
                'watched_count' => $match[6],
                'meta_watched_count' => $match[7],
                'meta_comment_count' => $match[8],
                'meta_mylist_count' => $match[9],
                'meta_created_at' => "20{$match[10]}", 
            );
        }
        return $ret;
    }

}
$nico = new NicoNico('aaaaa@bbbbb.cc.jp', 'xxxxxxx');
var_dump($nico->getHistory());
array(30) {
  [0]=>
  array(11) {
    ["id"]=>
    string(8) "21280725"
    ["url"]=>
    string(40) "http://www.nicovideo.jp/watch/sm21280725"
    ["thumb_url"]=>
    string(45) "http://tn-skr2.smilevideo.jp/smile?i=21280725"
    ["title"]=>
    string(69) "【オツキミリサイタル】歌ってみた @ゆいこんぬ"
    ["duraction"]=>
    string(4) "3:45"
    ["watched_at"]=>
    string(23) "2013年12月05日 14:10"
    ["watched_count"]=>
    string(1) "1"
    ["meta_watched_count"]=>
    string(7) "316,137"
    ["meta_comment_count"]=>
    string(5) "4,322"
    ["meta_mylist_count"]=>
    string(6) "14,802"
    ["meta_created_at"]=>
    string(23) "2013年07月05日 17:57"
  }
  [1]=>
  ....

このままシリアライズしてCookieを維持することも可能

file_put_contents('NicoNico.dat', serialize($nico));

間違った継承のしかたをすると例外をスロー

  • parent::__construct() をコールせずに使おうとすると BadMethodCallException をスロー
  • static::getUserAgents() が正しい配列を返さないと DomainException をスロー
49
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
mpyw
古い記事はそのまま参考にしないようにご注意ください
synapse
Synapseは、オンラインサロンサービスにおけるパイオニアとして、かつて存在していたスタートアップです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
49
Help us understand the problem. What is going on with this article?