用程序模拟提交表单登录百度。
从实用意义上说,这种问题其实意义不大,并且也并不适合写成博客。百度网页在不断变化,而此博客内容却不会相应更新,无法保证内容的正确性。 从学习知识方面说,这种问题适合作为学习课题。这几天学了下python,感触良多。python确实比java灵活,语法也有许多漂亮的特性。比如多行字符串,raw字符串(无需转义的字符串),在java中都没有,好难受。 这种问题需要耐心,像破解密码一样,需要去尝试,去理解,去猜想,耗费时间和精力,性价比较低,有这功夫就不如多学点别的。还是应该多多学习,孔子曰:终日而思,不如须臾之所学也。意思是说:思考一天不如学习半晌。
chrome浏览器,ctrl+u打开源代码,f12打开开发者工具。重点监测network,设置成preserve模式,实验之前清空过去的cookie和缓存等信息,排除干扰。剩下的任务就是盯着network的同时,执行登录,登出,发帖,评论等动作,然后查看cookie的变化及返回结果。从数据中发挥想象力,大胆猜测,寻找规律。
三步走,登陆成功
1. 首先,访问百度的任何一个页面,都会获得一个百度id(BAIDUID),这是一个cookie;
2. 其次,访问https://passport.baidu.com/v2/api/?getapi&tpl=mn&apiver=v3&class=login页面获取token
3. 最后,对https://passport.baidu.com/v2/api/?login页面提交表单
v2表示version2。此页面get请求后面的参数不同返回的结果也不同。 一开始抓包时,在浏览器中看到的参数是这样的:
- getapi:
- tpl:mn
- apiver:v3
- tt:1461752974956 登录时间
- class:login行为是登录,而非其它行为
- gid:36400D4-3078-460D-ABD8-9DEFBA99604B
- logintype:dialogLogin 登录类型,通过对话框登录
- callback:bdcbsgyljq1 回调
返回的结果是这样的:
这是一个不纯的json串,有些冗余。errInfo结果为0表示一切顺利,data是一个json串,里面唯一有用的信息是token。 这个get请求参数有许多是多余的,请求参数不同,返回的结果不同,可以直接在浏览器地址栏中测试。去掉callback参数之后,最后变成:getapi&apiver=v3参数,返回的json串就变得十分周正了。
再去掉apiver=v3(apiversion=3)属性,参数变为:getapi&class=login时,返回值就变成了键值对的方式:
所以,version2需要getapi和class=longin两个属性,version3需要getapi和apiver=v3两个属性,其中没有tpl=mn属性登录会失败,虽然可以返回json串,但是用于登录时,必须要有tpl=mn属性。 version2好像log4j得配置文件有没有。如何解析出来呢?可以用正则表达式,也可以用json解析version3的返回结果,还可以用属性解析version2的返回结果。正则表达式效果应该最好。 对于这种问题有两个原则(虽然矛盾,要寻找一个平衡): * 能删的参数尽量删掉 * 没有必要费精力试参数,直接全弄上,多了总不会错(但可能格式会难看一些)
在浏览器中看到的表单数据项很多,其中有许多是毫无用处的,没有它们照样登陆成功。经过删了测,测了删,发现只有如下表单有用:
再删就要出错了,tpl=mn这个属性还是必不可少。在这一步里,用到了第二步获取的token。 登录百度,不需要设置浏览器头部伪装成浏览器,也不需要伪装referer等头部,直接get,get,post三步走就登陆成功了。cookie也不需要管,因为api自己处理了cookie。本次请求会自动带上上次获得的cookie。 登录百度首页之后,就可以访问百度的各个部分了(包括贴吧,知道等)。 如何验证有没有登陆成功呢?有两种方法: 0. 访问www.baidu.com,看看页面里面有没有自己的名字 1. 查看cookie里面有没有PTOKEN和STOKEN等关键cookie
百度返回值说明,no表示错误码(0为正常),errorcode也表示错误码,error表示错误信息,data表示数据:
用到第三方库fastjson进行json解析,apache httpclient进行网络请求。
终于登陆成功了,下一步就要发帖了。百度贴吧的数据类型十分重要,这个层次不分清楚就不好办。
* 贴吧forum,forum是论坛的意思,贴吧是一个大话题,是一个比较大的类型。它下面是许多分支,是大话题的细化。比如:金庸吧包括很多thread,武功最高的人是谁?扫地僧和独孤求败谁厉害?....每一个thread都会引发很多post(帖子),而每一个帖子又会引来人们的评论。
* 话题thread,提出话题就相当于准备盖个楼。
* 帖子post,每一个post都为thread添加了一层楼。
* 评论comment,每一个post下面可以有评论,这样针对性才强,盖楼是表达自己的观点犹如重武器,长枪大戟发前人之所未发,评论就像小匕首短兵刃一样更直接。
常见错误类型:
* 没有tbs,返回230308,其中错误码是308,230是前缀
* 265是错误码,230是固定的前缀,这个大概是发帖太频繁,禁止发帖
* 40是验证码:发帖太频繁,需要验证码,如果能破解验证码,那自然是大大的好,下面就是返回的json串,str_reason表示“请点击验证码完成发贴”。
tbs从http://tieba.baidu.com/dc/common/tbs获得,只需要get一下,解析出json传中的tbs即可。tbs相当于贴吧通行证,你发的每一个评论,盖的每一层楼提交时都需要提交tbs,它们的tbs可以相同,这个tbs是你最近一次获得的tbs,服务器上维持着一份hashmap记录用户id和tbs值。
许多属性是没必要的,一次成功之后删繁就简,删了测测了删,发现header是没有用的,许多表单域也是可有可无的。 表单属性介绍如下:
* kw:thread名称,也就是话题名称
* tid: threadId,也就是话题id,在地址栏中就可以看见tid。
* fid:一个thread好像fid都是一样的,大概跟tid差不多吧,反正据我观察,在一个话题下发了很多贴它是不变的。 打开一个话题主页,比如:http://tieba.baidu.com/p/4195311174,ctrl+u查看源代码,ctrl+f查找关键词fid,很容易发现整页上的fid都是一模一样的。 百度用什么做主键:用long做主键!百度也不用uuid,博客园也不用uuid,很明显可以从网页上看出来。
java确实很长,还是看看python吧,简短有力更适合描述问题。java并不是总是冗长,它要想简单也很容易设计出简洁的API。
java的复杂来源于三个个方面:
* 库设计的不合理,”每次只做一件事,每步只做一件事“的哲学有点像汇编,有些冗长,并且java不屑于设计语法糖
* 问题本身就很复杂,需要进行许多配置,更灵活,python虽短,可能有些事情没法办,因为封装地太严密了,留下的接口太少了。
* 库设计的不合理+问题本身就复杂。这里面有一个概率问题,复杂问题不常用,你却让人们用大量的时间去考虑它们,这就不如预先设计一种简单不完善的接口。宁可简单的缺憾,也不要复杂的完善。举一个例子,选中多行按下tab键之后是应该缩进还是应该替换,当然是缩进了,如果我要替换我是不会这么操作的,缩进带来的简捷性非常大。
把上面的代码串联起来,是这样子的:
关键是tbs只需要获取一次,然后作为全局变量存在就可以了,不需要反复获取。
tieba.baidu.com/f/index/feedlist?tagid=all&limit=2000000&offset=0 这个链接十分重要。有些链接需要复制到地址栏才能访问而不能直接跳转过去,因为服务器可能不允许跨域访问。 它的参数 tagid=like | all 表示请求的标签列表的类型,like表示只返回我喜欢的,all表示返回全部。 limit表示条数,offset表示偏移量。它还有许多其他参数,比如last_tid最后一条的时间(用于加载更多),&_表示 这个链接是怎么知道的,访问tieba.baidu.com加载更多就会向这个feedlist发出请求。 通过jsoup解析html就可以得到好多贴吧及它们的tid了,然后点进去就可以获得fid了,有了tid和fid就可以盖楼了。
apache的httpClient组件包含多个部分,比如httpAsycClient是带回调函数的请求服务器;fluent部分是流畅版的httpclient,写起来简直溜溜溜。不信请看:
Request.Get("http://somehost/") .connectTimeout(1000) .socketTimeout(1000) .execute().returnContent().asString(); // Execute a POST with the 'expect-continue' handshake, using HTTP/1.1, // containing a request body as String and return response content as byte array. Request.Post("http://somehost/do-stuff") .useExpectContinue() .version(HttpVersion.HTTP_1_1) .bodyString("important stuff", ContentType.DEFAULT_TEXT) .execute().returnContent().asBytes(); // Execute a POST with a custom header through the proxy containing a request body // as an HTML form and save the result to the file Request.Post("http://somehost/some-form") .addHeader("X-Custom-header", "stuff") .viaProxy(new HttpHost("myproxy", 8080)) .bodyForm(Form.form().add("username", "vip").add("password", "secret").build()) .execute().saveContent(new File("result.dump"));
这代码确实是写得好
本文地址:http://lanlanwork.gawce.com/quote/10712.html 阁恬下 http://lanlanwork.gawce.com/ , 查看更多