(PS:有读者指出那个代码可能在别的服务器上会有问题,他本人提供的修改方案是改掉tempnam这个函数和删掉unlink这部分)
腾讯过于坑爹,看起来叫开放平台,其实有很多限制,为了实现某些功能,本人也只好铤而走险,从此踏上了模拟登陆的不归路,百度了无数的网页,不过由于腾讯的不停调整,那些代码都已经失效了,看过了不少风格迥异的代码,渐渐有了清晰的思路,在这里以微信公众平台为例写一篇思路详细的模拟登陆技术文章,希望可以让后来者少走弯路,这些代码实在各位大神的代码基础上修改完成的,其实也要感谢微信公众平台的加密并没弄得很复杂,要不然还真搞不定。
先来介绍浏览器浏览网页的大致原理,浏览器发送一段请求给服务器,服务器收到请求后,发送网页给浏览器,浏览器记下Cookie,并解析网页,最后展示给电脑前的你。所以所谓的模拟登陆就是模仿浏览器向服务器骗取信息。
首先打开mp.weixin.qq.com进入微信公众平台首页,按下F12调出开发者工具,(IE的优势更体现在这里,真心觉得ie的开发者工具很强大,而且再装个HttpWatch插件就无敌了),这里以遨游为例(因为简单),按下f12,进入源代码选项,打开按ctrl+O选择名字为wxm2-loginform1ec5f7.js的源代码,
然后找到这段代码
t.post("/cgi-bin/login?lang=zh_CN", {<span style="font-family: Arial, Helvetica, sans-serif;">username: e.account,pwd: t.md5(e.password.substr(0, 16)),imgcode: c.data("isHide") ? "" : e.verify,f: "json"}</span>这段就是登陆的时候用来提交账号和密码用的,username就是用户名,pwd就是密码,而且使用MD5加密过的,imgcode就是验证码,不过由于只要账号密码正确,就不需要输入验证码,接下来就是把这段东西稍作处理发给微信服务器就能登陆了。
function cookie(){ $post['username']=$this->username; //账号,这个是类的成员,具体声明见完整的源代码,后面的也是</span> $post['pwd']=md5($this->password); //密码</span> $post['f']='json'; $post['imgcode']=''; $login_url = 'https://mp.weixin.qq.com/cgi-bin/login?lang=zh_CN'; //见下文解释 $cookie_file = tempnam('./temp','cookie'); //设置cookie保存地址 $ch = curl_init($login_url); //php是curl技术,需要服务器支持(新浪sae支持这个技术) curl_setopt($ch,CURLOPT_REFERER,'https://mp.weixin.qq.com/'); //跳转地址,见下文 curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5'); //浏览器代理,可以理解为模仿某种浏览器向服务器发送消息 curl_setopt($ch, CURLOPT_HEADER, 0); //不注释的大家可以自行百度,我个人没他们讲的专业 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_MAXREDIRS, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_AUTOREFERER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); //这里就是提交账号密码的地方 curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file); //cookie $contents = curl_exec($ch); /*获取返回内容,如果将其输出得到{"base_resp":{"ret":0,"err_msg":"ok"},"redirect_url":"\/cgi-bin\/home?t=home\/index&lang=zh_CN&token=1563478266"}字样就是成功了,具体信息可对照刚才的源代码
switch (n) { case "-1":i = "系统错误,请稍候再试。";break; case "-2":i = "帐号或密码错误。";break; case "-23":i = "您输入的帐号或者密码不正确,请重新输入。";break; case "-21":i = "不存在该帐户。";break; case "-7":i = "您目前处于访问受限状态。";break; case "-8":i = "请输入图中的验证码", r();return; case "-27":i = "您输入的验证码不正确,请重新输入", r();break; case "-26":i = "该公众会议号已经过期,无法再登录使用。";break; case "0":i = "成功登录,正在跳转...", location.href = t.redirect_url;return; case "-25":i = '海外帐号请在公众平台海外版登录,<a href="http://admin.wechat.com/">点击登录</a>';break; default:i = "未知的返回。";return;}*/
preg_match('/[\?\&]token=(\d+)"/',$contents,$t); //这个是正则表达式,具体用法我也不是很熟悉,可参照网上的语法规则和检测工具来验证自己 // 写的正不正确,这里是为了从刚才返回的信息中吧token的值抠出来 self::$token=$t[1]; curl_close($ch); //其实到这里模拟登陆就已经结束了,不过不搞搞其他东西就一点也不好玩 /*下面的一段就是用正则表达式把cookie里面有用的信息提取出来,以备后面使用,因为登陆完成后公众平台的其他网页需要通过cookie验证 用户是否已经登录,或者登陆是否超时*/ $cookie_wenjain = file_get_contents($cookie_file); $cookie_wenjain = str_replace("\n","",$cookie_wenjain); $cookie_wenjain = str_replace("\t","",$cookie_wenjain); $cookie_wenjain = str_replace("\r","",$cookie_wenjain); preg_match_all('/data_bizuin(.*)mp/isU',$cookie_wenjain,$slave_user_lists); $data_bizuin = $slave_user_lists[1][0]; preg_match_all('/data_ticket(.*)mp/isU',$cookie_wenjain,$slave_user_lists); $data_ticket = $slave_user_lists[1][0]; preg_match_all('/slave_user(.*)mp/isU',$cookie_wenjain,$slave_user_lists); $slave_user = $slave_user_lists[1][0]; preg_match_all('/slave_sid(.*)=/isU',$cookie_wenjain,$slave_sid_lists); $slave_sid = $slave_sid_lists[1][0]; $cookie = "data_ticket=".$data_ticket.";data_bizuin=".$data_bizuin.";slave_user=".$slave_user.";slave_sid=".$slave_sid."="; unlink($cookie_file); self::$cookie = str_replace("#HttpOnly_","",$cookie);//将信息保存在$cookie里 }
接下来讲解上面的$login_url和refer网址是怎么找出来的了,进入开发者工具网络选项卡,然后在微信网页上用错误的密码登陆,找到post的那条信息,
那个请求url和refer就分别对应上面的两项,因为当初那个大神的代码上refer是另一个网址,因此我不禁怀疑是不是因为我没有登录,跳转的网页才不正确,可是如果登陆了,浏览器就调到另一个网页上去了,那些监控的信息就会被清空,这个时候就要靠HttpWatch,它可以记录所有的信息,这次正常登陆,结果显示这个refer是正确的,
之所以与大神不同可能是,腾讯的调整或者refer根本就不是这么找的。接下来介绍怎么用这玩样发送信息,每个微信都有一个独一无二的fakeid,微信公众平台就是根据这个来给大家发送信息的,所以我们就来搜集所有收听者的fakeid,
function getUserFakeid() { //ini_set('max_execution_time',600); //$pageSize = 1000000; //$this->referer = "https://mp.weixin.qq.com/cgi-bin/home?t=home/index&lang=zh_CN&token=".self::$token; //$url = "https://mp.weixin.qq.com/cgi-bin/contactmanage?t=user/index&pagesize={$pageSize}&pageidx=0&type=0&groupid=0&t//oken=".self::$token."&lang=zh_CN"; //$user = $this->vget($url); //$preg = "/\"id\":(\d+),\"name\"/"; //preg_match_all($preg,$user,$b); //$i = 0; //foreach($b[1] as $v){ //$url = 'https://mp.weixin.qq.com/cgi-bin/contactmanage?t=user/index&pagesize=10&pageidx=0&type=0&groupid='//.$v.'&token='.$this->token.'&lang=zh_CN'; $j=1;$page=15;//因为对代码进行了不小的修改,上面那些有些利用价值的注释就不删了,上面可以获取订阅者的分组信息,以便搜集到所有的fakeid,以下 是以默认分组进行举例 //$mysql = new SaeMysql(); //这个是新浪sae的mysql,因为大家也许都不用就把它注释掉了,如果需要可解除注释 for($i=0;$i<=$page;$i++) //$page表示页码,这个页码我没找到,所以大家可以根据自己的实际情况尽量设得大一点 { //$i表示当前页码 $url = 'https://mp.weixin.qq.com/cgi-bin/contactmanage?t=user/index&pagesize=10&pageidx='.$i.'&type=0&groupid=0&token='.self::$token.'&lang=zh_CN'; $header = array( //这个头后面讲解 'Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Encoding:gzip,deflate', 'Accept-Language:zh-CN', 'Cache-Control:max-age=0', 'Connection:keep-alive', 'DNT:1', 'Host:mp.weixin.qq.com', 'User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0' ); $curl = curl_init(); // 启动一个CURL会话 curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址 curl_setopt($curl, CURLOPT_HTTPHEADER, $header); //设置HTTP头字段的数组 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); // 从证书中检查SSL加密算法是否存在 curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0'); // 模拟用户使用的浏览器 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转 curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer curl_setopt($curl, CURLOPT_HTTPGET, 1); // 发送一个常规的GET请求 curl_setopt($curl, CURLOPT_COOKIE, self::$cookie); // 读取上面所储存的Cookie信息 curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate'); curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环 curl_setopt($curl, CURLOPT_HEADER,0); // 显示返回的Header区域内容 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回 $user = curl_exec($curl); //echo $user; //echo curl_errno($curl); //返回0时表示程序执行成功 如何从curl_errno返回值获取错误信息 $preg = "/\"id\":(\d+),\"nick_name\":\"([^\"]*)\"/"; //这部分正则表达式下面讲解 preg_match_all($preg,$user,$a); for($k=0;$k<10;$k++) { /* $b=$a[1][$k]; //echo $j." ".$b[0].'<br />'; $c=$a[2][$k]; $sql="INSERT INTO `app_wlmnzf`.`GZ` (`NUM`, `ID`, `NAME`) VALUES ($j, '$b', '$c');"; $mysql->runsql( $sql ); if( $mysql->errno() != 0 ) { echo "输入失败".'<br/>'.'<br/>'; }*/ $info[$j]=array("id"=>$a[1][$i],"name"=>$a[2][$i]); $j++; } } echo "完毕"; return $info; }
请求url和请求Http报头就是上面的$url和$header,因为cookie要另行设置,所以把cookie那项删掉
那个正则表达式部分很坑爹,首先是要在茫茫文件中人工找fakeid到底是在哪个地方,最后还是找到了,首先打开总用户数的网页,然后查看源代码,在最底下(看到最后终于找到了,内牛满面)找到所有有用信息,接下来就是用正则表达式把这些信息整理成数组(具体用法还是麻烦各位百度,我也花了好多时间才看懂,也讲不明白),id是fakeid
nick_name是昵称
<script type="text/javascript"> wx.cgiData={ isVerifyOn: "0"*1, pageIdx : 0, pageCount : 9, pageSize : 10, groupsList : ({"groups":[{"id":0,"name":"默认组","cnt":90},{"id":1,"name":"屏蔽组","cnt":0},{"id":2,"name":"星标组","cnt":0},{"id":102,"name":"闲杂人等","cnt":0},{"id":103,"name":"队员","cnt":0}]}).groups, friendsList : ({"contacts":[{"id":1068263504,"nick_name":"Livia","remark_name":"","group_id":0},{"id":1584342404,"nick_name":"王立敏","remark_name":"","group_id":0},{"id":460078235,"nick_name":"零距离-思念","remark_name":"","group_id":0},{"id":2454542763,"nick_name":"徐仁达","remark_name":"","group_id":0},{"id":1484309520,"nick_name":"水蒸气","remark_name":"","group_id":0},{"id":2988121915,"nick_name":"凝烟","remark_name":"","group_id":0},{"id":2340551701,"nick_name":"W、","remark_name":"","group_id":0},{"id":2880414606,"nick_name":"快乐天使","remark_name":"","group_id":0},{"id":1699244625,"nick_name":"心若动则万痛","remark_name":"","group_id":0},{"id":2697365613,"nick_name":"掌上大学","remark_name":"","group_id":0}]}).contacts, currentGroupId : "0" * 1, type : "0" * 1 || 0, userRole : '1' * 1, verifyMsgCount : '0' * 1, totalCount : '90' * 1 }; seajs.use("user/index"); </script>最后就是发送信息的函数
function singlesendpage($content,$tofakeid){ $url="https://mp.weixin.qq.com/cgi-bin/singlesend"; $ch=curl_init($url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); curl_setopt($ch, CURLOPT_COOKIE, self::$cookie); curl_setopt($ch,CURLOPT_REFERER,'https://mp.weixin.qq.com/cgi-bin/message?t=message/list&count=20&day=7&token='.self::$token.'&lang=zh_CN'); curl_setopt($ch,CURLOPT_USERAGENT,'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0'); curl_setopt($ch,CURLOPT_RETURNTRANSFER,true); $post['random']=rand(1,999999999999999)/10000000000000000; $post['lang']='zh_CN'; $post['type'] =1; $post['content']=$content; $post['tofakeid']=$tofakeid; $post['imgcode']=''; $post['token']=self::$token; $post['f'] =json; $post['ajax'] =1; $post['t']="ajax-response"; curl_setopt($ch,CURLOPT_POST,1); curl_setopt($ch,CURLOPT_POSTFIELDS,$post); $html=curl_exec($ch); curl_close($ch); $html_json = json_decode($html); if($html_json->base_resp->ret == 0){ return true; }else{ return false; } } //这个用上面的方法分析就可以了,不过因为是模拟登陆,无法突破腾//讯的订阅者48小时内不跟你联系,你无法主动发送信息给它的限制,因此原先想突破每日一群发的限制的美梦破灭了