玩耍Python -- 百度贴吧自动签到(上)

最近准备玩耍一下Python,因为发现语言这个东西,总不用的话就会生疏了。所以打算用Python写点小东西,一方面练练手,一方面还可以深入了解Python的一些机制,何乐而不为呢。

PS:这里要推荐一个新手练习Python的好网站:http://www.checkio.org/,边玩游戏边编码,而且完成一个任务之后可以看其他人的解法,在这个网站上学到了很多有用的东西。目前追求就是用最少的代码完成任务,至少我目前level 9,做过的很多任务都是可以一句话搞定的 – 尽显Python的魅力。

这次做的是一个百度贴吧的自动签到工具,其实这个东西网上已经有了好多了。而且成品也不少。当然在看之前先自己想一下思路。
其实说白了这个工具就是要模拟用户的行为进行签到,那么第一步肯定是要模拟用户登录;用户登录之后呢,我们肯定要到要签到的贴吧进行签到,但是程序自己是不知道用户要签到的贴吧在哪里的,好在百度有个很贴心的贴吧关注功能,这样我们就可以把要签到的贴吧添加关注,这样我们就可以通过获取关注贴吧来知道用户要签到的贴吧,第二步就是获取“关注”贴吧第三步贴吧签到。到此整个流程就完成了。
下面具体讲每一步是如何实现的:

(1)模拟用户登录

这算是整个程序的难点了,因为登录涉及到的东西比较多,首先还是需要看看登录的POST请求到底长什么样子。因为目前来说,网页的登录都是需要POST一个表单给服务器端,服务器端再做鉴权返回结果。所以我们要先用工具来看一下我们登录的时候到底发生了什么。打开Chrome,输入baidu.com,然后打开Chrome的调试工具,登录一下之后我们就可以看到一个POST请求了。如下图:
QQ20140418-1@2x

点开查看细节,主要关注的是Form表单里面传了什么,我们可以看到表单里面传了很多的值,接下来我们就分析一下这些值都是怎么来的。

QQ20140418-2@2x

这些值里面有大部分一看就能够确定是传固定的值就可以了,不需要特别关注,等写的时候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。

QQ20140418-3@2x

最后经过尝试,发现只要请求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这几项就可以了。

QQ20140418-4@2x

(2)获取“关注”贴吧

OK,登录已经搞定了。下面就是要获取到用户关注了哪些贴吧。先正常操作,可以看到当我们访问“我的i贴吧”的时候会显示我关注的贴吧。但是看url是http://tieba.baidu.com/i/179045774/forum,明显中间那串数字是类似用户的id的东东,应该是可以通过某种方式获得,但是有点显得麻烦了。那怎么办呢,再次用调试工具看一看请求的资源,发现请求这个页面的时候并没有返回我关注的贴吧的具体内容,那说明是在其他的请求中请求的这个内容,在里面找一下就发现有这样一个请求http://tieba.baidu.com/f/like/mylike,从url的格式上看就比较靠谱,发到浏览器里试一下果然是

QQ20140418-5@2x

到此,我们已经完成了大部分的工作了。

(3)贴吧签到

好,下面还是正常的用浏览器到贴吧里面签到一下,记得打开调试工具关注发送的请求。然后我们在签到的时候就又发现了一个POST请求http://tieba.baidu.com/sign/add。打开看一下详情。

QQ20140418-6@2x

这回的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()
码完代码之后测试一下:

QQ20140418-7@2x

OK,上部分就说到这里了,下半部分重点说一下这次玩耍的时候遇到的各种问题。我觉得遇到问题的时候才是最能学到东西的时候。