44
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2013-12-05

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 をスロー
44
50
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?