前言

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

登录的实现

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

签到的实现

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

CODE

入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
    $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类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?php
    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);
        }
    }
?>

总结

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


風前の灯になってもいいさ、残したの歳月痕跡を持ち続けて生きてゆく。