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