Python刷票小脚本——网络人气奖?不好意思,我要了
零、前言
最近参加微软的kinect大赛,报名之后发现有一个网络投票,票数最多的项目可以得到网络人气奖。
这种事,必然是要搞一搞!
说干就干。
说明:由于本人过于懒惰,所以就不截图了,让大家失望了!
重点看一下思想就可以了。
一、探查敌情
第一步先搞清楚投票的具体流程以及可能的限制条件。
经过研究之后,总结如下:
- 投票需要登录
- 注册帐号需要验证邮箱
- 登录不需要验证码
- 每个帐号一天可以投一次票,票数可以选择,从0~10
- 投票不需要验证码
注意到红字了是吧,这就是最关键的地方了。
好了,我们的初步思路就出来了:
手动注册好帐号——代码模拟用户登录——登录之后进行投票
思路出来了,下面就是工具的选择。
二、工欲利其事。。。
语言嘛必然是我爱的Python。
工具的话,之前其实做过模拟登录,简单来说就是用一个模拟浏览器的Python插件,然后进行各种模拟用户操作,比如点击按钮啊输入信息啊之类的。
但是这类插件主要有两大问题:
- headless的很少
- 基本上对于HTML页面的操作只能应用于form
headless是什么呢,可以简单理解为后台操作。如果做不到headless,那么你运行的时候其实还是需要打开一个浏览器,只不过脚本会操作浏览器。
所以我们可以看到,做不到headless的话,不仅看起来非常低端(想象一下电脑屏幕上开着一个浏览器,然后自动输入东西,而你之只能傻坐着什么都不能做),并且使用起来很不方便,比如在没有图形界面的系统就无法使用了。
只能应用于form是什么意思呢,我们拿jQuery来对比吧。jQuery可以选择所有HTML里出现的东西,但是只能应用于form就意味着只能操作表单,对于其他元素就无能为力了。我不知道为什么会出现这种情况,可能是插件底层有一些限制吧,反正大部分插件都只能操作form。
给大家提供三个方案以供参考吧:
- Ghost.py 支持headless并且可以操作所有元素,甚至可以运行js,你就知道这个有多强大了。唯一缺点——依赖PyQt或者PySide,你装一下这俩货就知道了,简直折磨死人。所以如果你不想折腾的话,还是不要用这个了。
- splinter 半支持headless——splinter默认是不支持headless,但是可以在使用zope的一个插件的前提下headless,因为我用的是默认的,所以具体怎么做到headless没有研究,感兴趣的自己搞搞吧。不过估计八成不给力。。。splinter的功能同样很强大,可以操作所有元素。
- mechanize 支持headless但是只能操作form
大概就是这样。
三、Just Do It
重复一下我们的思路:
登录——投票
因为我决定采用headless的方案,所以就使用mechanize。
登录的话没问题,登录框本来就是form。投票嘛。。。先放着!我们先把登录搞定。
直接上代码:
# coding:utf-8
import cookielib
import mechanize
import urllib
br = mechanize.Browser()
cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)
br.open('http://k4w.cn/user/index.html')
br.select_form(nr=0)
br.form['mail'] = 'xxxx@xxx.com'
br.form['password'] = 'xxxxxxx'
br.submit()
cookielib是用来操作cookie的。因为我们登录之后需要跳转到投票页面,如果我们不保留cookie,那么网站就会把我们当做未登录——别忘了,你现在是用代码在模拟登录,所以不要以为他会自动给你保存cookie。
代码很简单吧,我就不解释了,总之,打开页面——输入用户名密码——提交
可以输出一下结果看看:
.....同上.....
response = br.submit()
print response.read()
我们可以看到输出的HTML中有“xxx,欢迎你”这样的字样,说明已经成功登录了
下面就是重头戏了——如何投票。
先具体化投票的操作:
从下拉列表里选择“10”,然后点击确定。
我们已经知道,mechanize只能操作form,对于其他元素是无能为力的,所以我们不能直接来模拟人的操作。
那么该怎么办呢?
大家先思考5秒钟
。。。。。。。。
。。。。。
。。。
。
好吧我知道你直接翻下来了。
我当时可是思考了半天才想出来的啊!!!
我们可以换个思路,投票,表面上是人的操作,但是最终发送给服务器的其实是一个POST请求!所以,我们可以跳过模拟操作,直接发送请求!
好的,这下思路清晰了。我们先——等等,我们POST什么东西?
投票啊,告诉服务器我们投票了。
但是代码是一个很严谨的东西,格式不对的话服务器是不认的!
好吧,这次不思考了,直接告诉你答案吧。
我们先手动投一次票,然后查看POST请求中的数据格式。
我用的是firebug,打开firebug,然后选择票数,按确定按钮,可以看到firebug中出来了这次POST请求的具体信息。
我们点开信息,可以看到数据的格式:
z_data : 10
id : 99
sid : 78
一下就看出来了嘛!
z_data是票数,id是项目编号,sid。。。好吧我不知道这是什么。总之就写78好了。
数据格式获取到了,下面我们回到代码中,模拟一个POST请求:
parameters = {'z_data' : '10',
'id' : '99',
'sid' : '78'
} # POST data
data = urllib.urlencode(parameters)
response = br.open('http://k4w.cn/zone/z_num.html', data)
很简单吧?
别忘了import urllib!
好了,我们和前面的代码组合起来实验一下,看看效果。
发现票数确实增加了,我们的方法是可行的。
然后呢,我们改一下,加个for循环,这样就可以按照我们设定好的用户名密码,自动登录所有用户并投票。
基本功能就是这样了,但是使用了几天之后发现了一个不爽的地方:投票完要想查看票数还得手动打开网页。如果能直接显示出来当前票数就好了!
于是我们继续踏上征程。
四、迭代好吃吗
首先还是思路:
打开项目页面——获取票数——显示
打开页面我们已经会了,br.open()就行。显示也很简单,print。那么怎么获取票数呢?
给大家介绍一个新工具——BeautifulSoup,靓汤!
我承认名字有点。。。。
不管了,继续我们编程之路。
靓汤是一个解析HTML的插件,介绍完毕。
我们可以把获取到的HTML用靓汤解析一下,然后找到我们需要的票数对应的那个元素,就可以获取票数了。
很简单是吧!我们把HTML传入靓汤。。。。
我靠怎么出错了!
Google了半天,原来是HTML中有不规范的标签,就解析失败了。
微软的页面原来也不符合标准。。。
好吧,解析不了,怎么办呢?
有人给出了解决办法:用lxml。
lxml又是个什么东西?lxml是解析xml的一个插件,但是可以解析HTML并且,注意啊,并且可以忽略不规范的标签。
正好是我们需要的!
OK,照着官方文档使用一下~
亮代码:
br = mechanize.Browser()
response = br.open('http://k4w.cn/level_search/1/78/0/0/0.html')
page = etree.HTML(response.read().lower().decode('utf-8'))
hrefs = page.xpath(u"//span[@class='number n_99']")
print "当前票数:" + hrefs[0].text
依然是很简单不解释,看看就明白了。
好的,这样我们整个刷票脚本就完工了~~
所有代码来个合影
# coding:utf-8
import cookielib
import mechanize
import urllib
from lxml import etree
all_data = [['username1', 'password1'], ['username2', 'password2']]
for i in all_data:
br = mechanize.Browser()
cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)
br.open('http://k4w.cn/user/index.html')
br.select_form(nr=0)
br.form['mail'] = i[0]
br.form['password'] = i[1]
br.submit()
response = br.open('http://k4w.cn/level_search/1/78/0/0/0.html')
parameters = {'z_data' : '10',
'id' : '99',
'sid' : '78'
} # POST data
data = urllib.urlencode(parameters)
response = br.open('http://k4w.cn/zone/z_num.html', data)
print "%s 投票成功!" % i[0]
br = mechanize.Browser()
response = br.open('http://k4w.cn/level_search/1/78/0/0/0.html')
page = etree.HTML(response.read().lower().decode('utf-8'))
hrefs = page.xpath(u"//span[@class='number n_99']")
print "当前票数:" + hrefs[0].text
搞定。
五、总结
我们使用了:
- mechanize 模拟用户操作 模拟POST请求
- firebug 获取POST数据格式
- lxml 解析HTML内容
我们实现了:
- 自动登录
- 自动投票
- 输出票数
好了,下面只要把这个脚本放到服务器上,并且设置一下每天运行就可以了。