最近准备玩耍一下Python,因为发现语言这个东西,总不用的话就会生疏了。所以打算用Python写点小东西,一方面练练手,一方面还可以深入了解Python的一些机制,何乐而不为呢。
PS:这里要推荐一个新手练习Python的好网站:http://www.checkio.org/,边玩游戏边编码,而且完成一个任务之后可以看其他人的解法,在这个网站上学到了很多有用的东西。目前追求就是用最少的代码完成任务,至少我目前level 9,做过的很多任务都是可以一句话搞定的 – 尽显Python的魅力。
这次做的是一个百度贴吧的自动签到工具,其实这个东西网上已经有了好多了。而且成品也不少。当然在看之前先自己想一下思路。
其实说白了这个工具就是要模拟用户的行为进行签到,那么第一步肯定是要模拟用户登录;用户登录之后呢,我们肯定要到要签到的贴吧进行签到,但是程序自己是不知道用户要签到的贴吧在哪里的,好在百度有个很贴心的贴吧关注功能,这样我们就可以把要签到的贴吧添加关注,这样我们就可以通过获取关注贴吧来知道用户要签到的贴吧,第二步就是获取“关注”贴吧;第三步贴吧签到。到此整个流程就完成了。
下面具体讲每一步是如何实现的:
(1)模拟用户登录
这算是整个程序的难点了,因为登录涉及到的东西比较多,首先还是需要看看登录的POST请求到底长什么样子。因为目前来说,网页的登录都是需要POST一个表单给服务器端,服务器端再做鉴权返回结果。所以我们要先用工具来看一下我们登录的时候到底发生了什么。打开Chrome,输入baidu.com,然后打开Chrome的调试工具,登录一下之后我们就可以看到一个POST请求了。如下图:
点开查看细节,主要关注的是Form表单里面传了什么,我们可以看到表单里面传了很多的值,接下来我们就分析一下这些值都是怎么来的。
这些值里面有大部分一看就能够确定是传固定的值就可以了,不需要特别关注,等写的时候copy一份过来就可以。其中有几个字段是需要关注的:
token:看格式就像是一个MD5加密的32位字段,这个要伪造有点困难,要找一下有没有什么获得的方法。
username:用户名
password:密码
callback:看起来也不像是个固定的值,查一下是如何生成的
下面先看一下token,首先我们想这个值不可能是我们这边自己生成的,肯定是服务器端生成然后传给我们的。那既然是传给我们的,那肯定是在登录之前的某次请求中返回的。想清楚这个问题就好办了,在登录之前请求的url就那么几个,一个一个试过去,果然在请求
https://passport.baidu.com/v2/api/?getapi&tpl=mn&apiver=v3&tt=1397637562619&class=login&logintype=dialogLogin&callback=bd__cbs__pksb6x
这个url的时候,看到了我们要找的token。
最后经过尝试,发现只要请求https://passport.baidu.com/v2/api/?getapi&tpl=mn&apiver=v3就可以获得token了,不用后面那一串参数。
接下来是callback,之前在找token的时候请求的url里面有callback,但是我们把callback字段去掉之后请求依然能够返回结果,是不是说明callback也是个非必要的字段呢?我们试试就知道了。有了获取token的方法,我们就可以编写模拟登录的代码了。代码如下:
# -*- coding=utf-8 -*- import urllib import urllib.request import json import http import http.cookiejar import re import os import zlib import time from urllib.parse import urlencode TOKEN_URL = "https://passport.baidu.com/v2/api/?getapi&tpl=mn&apiver=v3" INDEX_URL = "http://www.baidu.com/" LOGIN_URL = "https://passport.baidu.com/v2/api/?login" reg_token = re.compile("\"token\"\s+:\s+\"(\w+)\"") bdHeaders = { "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Encoding":"gzip,deflate,sdch", "Accept-Language":"en-US,en;q=0.8,zh;q=0.6", "Host":"passport.baidu.com", "Origin":"http://www.baidu.com", "Referer":"http://www.baidu.com/", "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36", } bdData = { "staticpage":"https://passport.baidu.com/static/passpc-account/html/V3Jump.html", "token":"", "tpl":"mn", #重要,需要跟TOKEN_URL中的相同 "username":"", "password":"", } class bdLogin: def __init__(self): self._cookie = http.cookiejar.LWPCookieJar() self._opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self._cookie)) def login(self, user = "", psw = ""): print ("User:" + user) self._initial() self._getToken() #取得token,必要 bdData["username"] = user bdData["password"] = psw bdData["token"] = self._token # print ("Token:" + self._token) request = urllib.request.Request(LOGIN_URL, headers = bdHeaders) result = self._opener.open(request, urlencode(bdData).encode("utf-8")) #登录 # decompressed_data=zlib.decompress(result.read(), 16+zlib.MAX_WBITS) # print (decompressed_data) result = json.loads(self._opener.open("http://tieba.baidu.com/f/user/json_userinfo").read().decode("utf-8")) # print (self._opener.open("http://tieba.baidu.com/f/user/json_userinfo").read().decode("utf-8")) if(result["no"] == 0): print ("OK, login succes!") #判断是否登录成功 return self._opener else: print("WTF, there is something wrong...") return None def _getToken(self): self._token = reg_token.findall(str(self._opener.open(TOKEN_URL).read()))[0] def _initial(self): self._opener.open(INDEX_URL) def main(): robot = bdLogin() #传入用户名和密码 for line in open("user.conf"): user, password = str(line).strip('\n').split(",") robot.login(user, password) if __name__ == "__main__": main()
运行之后,可以从log中看到已经登录成功了,而且通过不断的精简参数,发现提交的form表单里面,只需要staticpage,token,tpl,username,password这几项就可以了。
(2)获取“关注”贴吧
OK,登录已经搞定了。下面就是要获取到用户关注了哪些贴吧。先正常操作,可以看到当我们访问“我的i贴吧”的时候会显示我关注的贴吧。但是看url是http://tieba.baidu.com/i/179045774/forum,明显中间那串数字是类似用户的id的东东,应该是可以通过某种方式获得,但是有点显得麻烦了。那怎么办呢,再次用调试工具看一看请求的资源,发现请求这个页面的时候并没有返回我关注的贴吧的具体内容,那说明是在其他的请求中请求的这个内容,在里面找一下就发现有这样一个请求http://tieba.baidu.com/f/like/mylike,从url的格式上看就比较靠谱,发到浏览器里试一下果然是
到此,我们已经完成了大部分的工作了。
(3)贴吧签到
好,下面还是正常的用浏览器到贴吧里面签到一下,记得打开调试工具关注发送的请求。然后我们在签到的时候就又发现了一个POST请求http://tieba.baidu.com/sign/add。打开看一下详情。
这回的form表单里面只有三项:
ie:编码格式
kw:贴吧名称(好吧,我随便找的“头像吧”= =)
tbs:这个猜测应该是贴吧的编号之类的,查了下可以从当前贴吧首页的源码PageData.tbs字段中拿到
这时候可能有人会问,这样请求服务器怎么知道是谁签到呢?这就要用到cookie了。因为之前登陆成功之后会生成一个cookie,在签到的时候只要把这个cookie带上就可以了。
OK,下面的工作和登陆一样了,码代码了:
# -*- coding=utf-8 -*- import baidu_autologin import re import urllib import urllib.request import multiprocessing import json import pickle import os import time from urllib.parse import urlencode TIEBA_URL = "http://tieba.baidu.com" GETLIKE_URL = "http://tieba.baidu.com/f/like/mylike" SIGN_URL = "http://tieba.baidu.com/sign/add" reg_likeUrl = re.compile("<a href=\"([^\"]+)\" title=\"([^\"]+)\">") reg_getTbs = re.compile("PageData.tbs = \"(\w+)\"") def getTbTbs(opener, url): return reg_getTbs.findall(opener.open(TIEBA_URL + url).read().decode("gbk"))[0] #获取喜欢的贴吧列表 def getList(opener): return reg_likeUrl.findall(opener.open(GETLIKE_URL).read().decode("gbk")) signHeaders = { "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36 LBBROWSER", "Host":"tieba.baidu.com", "Origin":"http://tieba.baidu.com", "Referer":"http://tieba.baidu.com", } #要post的表格 signData = { "ie":"utf-8", "kw":"", "tbs":"", } class autoSign: def __init__(self, user = "", psw = ""): login = baidu_autologin.bdLogin() self._opener = login.login(user, psw) self.user = user def getList(self): self._likeList = getList(self._opener) def sign(self): self.getList() list = [] for url in self._likeList: list.append(self._signProcess(url)) time.sleep(2) for ret in list: #取回结果 print (ret) def _signProcess(self, url): signData["kw"] = url[1] signData["tbs"] = getTbTbs(self._opener, url[0]) #获取tbs signHeaders["Referer"] = signHeaders["Origin"] + url[0] request = urllib.request.Request(SIGN_URL, headers = signHeaders) result = json.loads(self._opener.open(request, urlencode(signData).encode("utf-8")).read().decode("utf-8")) if(result["no"] == 0): #签到成功 return "{0}吧签到成功,今天是第{1}个签到!".format(url[1], result["data"]["uinfo"]["user_sign_rank"]) elif(result["no"] == 1101): #已签过 return "{0}吧之前已经签到过了哦!".format(url[1]) else: #出错 return "未知错误!" + "\n" + url + "\n" + result def main(): ntime = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) print (ntime) for line in open("user.conf"): user, password = str(line).strip('\n').split(",") asRobot = autoSign(user, password) #传入用户名和密码 asRobot.sign() if(__name__ == "__main__"): main()
码完代码之后测试一下:
OK,上部分就说到这里了,下半部分重点说一下这次玩耍的时候遇到的各种问题。我觉得遇到问题的时候才是最能学到东西的时候。