前言

其实我不经常上v2ex,更何况那边现在都快成Python的社区了,这就更让我一个写php的格格不入。本程序对于我而言实用价值很小,写其目的也就为了巩固curl的用法和丰富一些意外情况时的应对策略(针对有防御机制的网站),最主要的还是供一些脚本爱好者们在实现思路上的一些参考,要如果在看这篇文章的你正好满足我的要求,那你可真是太幸运了,当初怎么没人像现在我所做的一样给我指点迷津呢,唉。

登录的实现

v2ex不仅对登陆设备的UA以及来源做了验证以外,登录时还需要提交一个被隐藏了的once值,该值由服务器随机生成并储存在session这个全局数组当中,然后输出在html隐藏域,用户执行登陆操作时会连同用户名密码一同携带提交给服务器,服务器会根据客户端cookie当中的唯一用户标识符相对应的SESSION数组里的once与post传过来的once进行比对,如果一样则通过验证实现登陆,否则就会认为你是非人为操作而禁止登陆,提交错误的次数有上限,过多会临时全站封锁IP的访问请求,我也正是因为这样被封了2个ip的……
v2ex-sign-once
v2ex-sign-code

签到的实现

在模拟登陆成功后一定要携带cookie信息并GET网站首页,一是为了更新cookie新增TAB字段,二是获取新的once值以供签到跟退出操作时使用,为什么没有用前面已经获取到的once值进行签到呢?因为登陆成功后得到once值跟未登录状态下得到的once是不一样的,这也是网站防止机器操作做了点小小的手脚。在确认前面条件都已经具备的情况下就可以执行签到入口的GET操作了,在这里还得注意,签到入口必须得GET两次,网站做了中转验证,第一次GET返回的报文为302临时转移状态码,如果在这时候中断GET操作是不会成功签到的,眼看就要成功了,现在又卡在最后一步了,一般人可能死活找不到突破口就这么放弃了,其实知道了执行流程的话这个问题还是很容易突破的,这时候就得需要我们的抓包工具出马了,见下图,这就是在正常状态下点击签到后所发出的http请求,一下就看出端倪来了吧 哈哈!解决办法:在close签到的句柄之前更改请求的URL后再次进行GET操作,从封包中就能看出第二遍所请求的url不需要携带once参数的。至此,流程上没有出意外的话,你就已经成功的实现了签到,欢呼吧!又可以去装X了。
v2ex-sign-netpack
v2ex-sign-html

CODE

入口文件


$loginUrl = 'http://cn.v2ex.com/signin';
//登陆入口
$v2ex = new v2ex('otokaze','');
//你的账号密码,请符合v2ex的用户名规范
if(!is_file($v2ex->CookiePath)){
    //对已有登陆状态cookie的账号,程序会自动跳过登陆
    file_put_contents($v2ex->CookiePath,'');
    $v2ex->getOnceAndSession($loginUrl);
    //获取初始状态的once值以及SESSIONID
    $v2ex->login();
}
$v2ex->getOnce('http://cn.v2ex.com',$v2ex->CookiePath);
//获得最新Once值,用于签到
$v2ex->sign('http://cn.v2ex.com/mission/daily/redeem',$v2ex->CookiePath);
//执行签到步骤
$v2ex->update();
function __autoload($className){
    //自动引入类库
    include './class/'.$className.'.class.php';
}

v2ex类


class v2ex{
    public $once;
    public $user;
    public $password;
    public $session;
    public $CookiePath;
    //初始化变量
    public function __construct($user,$password){
        if(!preg_match('/^\w+$/',$user.$password)) exit('请输入合法用户名及密码');
        $this->user = $user;
        $this->password = $password;
        $this->CookiePath = dirname($_SERVER['SCRIPT_FILENAME']).'/cookie/'.$this->user.'.cookie';
    }
    //如果cookie文件的创建时间已经超过12小时,则自动删除
    public function update(){
        $ctime = filectime($this->CookiePath);
        if(($ctime + 12*60*60) < time()) unlink($this->CookiePath);
    }
    //获取初始状态的Once值SESSIONID
    public function getOnceAndSession($url){
        $preg_once = '/<input\stype="hidden"\svalue="(\d+)"\sname="once" ';="" $preg_session="/PB3_SESSION="(.*?)";/" ;="" $session="curl_init($url);" curl_setopt($session,curlopt_header,1);="" curl_setopt($session,curlopt_returntransfer,1);="" $str="curl_exec($session);" $result="preg_match($preg_session,$str,$sessionArr);" if($result){="" preg_match($preg_once,$str,$oncearr);="" $this-="">session = $sessionArr[1];
            $this->once = $onceArr[1];
            curl_close($session);
        }
    }
    //模拟header头,执行登陆并更新cookie文件
    public function login(){
        $url = 'http://cn.v2ex.com/signin';
        $post = "u={$this->user}&p={$this->password}&once={$this->once}&next=%2F";
        $header = array(
                        'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36',
                        'Content-Type: application/x-www-form-urlencoded',
                        'Origin: http://cn.v2ex.com',
                        'Referer: http://cn.v2ex.com/signin'
                    );
        $login = curl_init($url);
        curl_setopt($login,CURLOPT_HTTPHEADER,$header);
        curl_setopt($login,CURLOPT_HEADER,1);
        curl_setopt($login,CURLOPT_RETURNTRANSFER,1);
        curl_setopt($login,CURLOPT_POST,1);
        curl_setopt($login,CURLOPT_POSTFIELDS,$post);
        curl_setopt($login,CURLOPT_COOKIE,'PB3_SESSION="'.$this->session.'"');
        curl_setopt($login,CURLOPT_COOKIEJAR,$this->CookiePath);
        curl_exec($login);
        curl_close($login);
    }
    //更新登陆成功状态下的once值,以用于签到
    public function getOnce($url,$CookiePath){
        $once = curl_init($url);
        curl_setopt($once,CURLOPT_HEADER,1);
        curl_setopt($once,CURLOPT_COOKIEFILE,$CookiePath);
        curl_setopt($once,CURLOPT_COOKIEJAR,$CookiePath);
        curl_setopt($once,CURLOPT_RETURNTRANSFER,1);
        $str = curl_exec($once);
        $preg = '/once=(\d+)/';
        curl_close($once);
        preg_match($preg,$str,$onceArr);
        $this->once = $onceArr[1];
    }
    //模拟header头,携带上cookie以及once值进行两个GET操作。
    public function sign($url,$path){
        $sign = curl_init($url.'?once='.$this->once);
        $header = array(
                        'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36',
                        'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                        'Referer: http://cn.v2ex.com/mission/daily'
                    );
        curl_setopt($sign,CURLOPT_HEADER,1);
        curl_setopt($sign,CURLOPT_HTTPHEADER,$header);
        curl_setopt($sign,CURLOPT_COOKIEFILE,$path);
        curl_setopt($sign,CURLOPT_RETURNTRANSFER,0);
        curl_exec($sign);
        curl_setopt($sign,CURLOPT_URL, 'http://cn.v2ex.com/mission/daily');
        curl_exec($sign);
        curl_close($sign);
    }
}
</input\stype="hidden"\svalue="(\d+)"\sname="once">

总结

改实现方案至始至终都是个人原创,在遇到难题一筹莫展的时候,作为一名程序员,只要你还留有对技术探索的兴趣,那就不要轻易放弃,多想想还有什么解决方案,然后多去尝试,指不定真的就成功了,到那时你所收获到的满足与喜悦我相信你一定会喜欢上这种感觉,这也是许多技术爱好者所追求着的东西。祝君好运


生きて生きて生きて生きて生きて。