Python 爬虫入门(四)—— 验证码上篇(主要讲述验证码验证流程,不含破解验证码)
Posted on 2016-01-29 10:36 不剃头的一休哥 阅读(13543) 评论(9) 编辑 收藏 举报本篇主要讲述验证码的验证流程,包括如何验证码的实现、如何获取验证码、识别验证码(这篇是人来识别,机器识别放在下篇)、发送验证码。同样以一个例子来说明。目标网址 http://icp.alexa.cn/index.php(查询域名备案信息)
1.验证码的实现:
简单的说,验证码就是一张图片,图片上有字符串。网站是如何实现的呢?有WEB基础的人可能会知道,每个浏览器基本都有cookie,作为这次回话的唯一标示。每次访问网站,浏览器都会把这个cookie发送给服务器。验证码就是和这个cookie绑定到一起的。如何理解呢?举个例子,现在有网站W,有A和B两个人,同时访问W,W给A返回的验证码是X,给B返回的验证码是Y,这两个验证码都是正确的,但是如果A输入了B的验证码,肯定验证不通过。那服务器是怎么区分A和B呢,就是用到的cookie。再举个例子,有些网站你登录一次之后,下次继续访问可能就自动登陆了,也是用cookie来标示唯一身份的,如果清除了cookie也就无法自动登陆了。cookie具体是什么生成的,我们不必关心,只需要知道是一长串字符串就行了,你的和别人的都不一样。(例子中的目标网址并不是用cookie,而是用的其他方式,所以可能会存在一些BUG)
服务器后台生成验证码的流程就很容易理解了:首先,生成一个随机字符串,然后和cookie绑定,然后写到图片上返回给你。那么,如何生成一个图片验证码呢?下面是一个简单的生成验证码源码:
from PIL import Image import ImageFilter,ImageDraw,ImageFont import random width = 80 height = 40 font = ImageFont.truetype('C:\\Windows\\Fonts\\AdobeFangsongStd-Regular.otf', 28) image = Image.new("RGB",(width,height),(0,0,0)) draw = ImageDraw.Draw(image) for t in range(4): draw.text((20*t,10),`random.randint(0,9)`,font=font,fill=(255,255,255)) image.show()
代码说明:
a).PIL是python的图片库模块,需要自己安装
b).ImageFont.truetype()是选择字体
c).Image.new("RGB",(width,height),(0,0,0))新建一个Image,背景色是白色((0,0,0)就代表的颜色),如果需要别的颜色,可自己查询颜色代码。window自带的画板就可以看到:
d).random.randint(0,9)随机数 范围大于等于0,小于等于9
e).draw.text((20*t,10),`random.randint(0,9)`,font=font,fill=(255,255,255),anchor=False) 第一个参数代表位置,带二个代表内容,第三个代表字体,第四个代表字体颜色
f).image.show()显示图片,第一词会提示选择默认图片查看器。
运行结果如下图:
2).验证码的获取
a).分析目标网站,可以看到当鼠标点击验证码那个输入框时会显示验证码,如图:
那么获取验证码的请求是什么?以及请求发送的时间?(验证码显示的时间不一定是验证码获取的时间,虽然这个例子中是,以为验证码可能是页面刚开始的时候一起加载的,只是一直被隐藏)。火狐浏览器F12打开控制台,找到网络标签,刷新页面,可以看到如下图所示:
并没有发现获取验证码的请求,那么我们点击验证码的那个输入框,发现多了一个请求,没错,这就是获取验证码的请求。
b).下面我们开始分析这个请求,首先点击这个请求,可以看到如下图所示:
完整URL:http://icp.alexa.cn/captcha.php?q=sina.com.cn&sid=82&icp_host=sxcainfo。可以看到三个参数:
q=sina.com.cn:查询的域名
sid=82:这个ID暂时不知道是什么,后面查看JS源代码会看到
icp_host:这个暂时也不知道。
这三个参数,那些是必须的呢?可以一个一个测试。测试方法就是删掉某个元素,然后再发请求。测试发现,三个参数缺省都可以获取到验证码,获取到验证码,不代表验证码可用,因为,没有与某些类似cookie的值绑定到一起,它就和图片没有任何区别,不具备验证的功能。经我测试,(测试很简单,不过要用到后面的东西,看完这篇,就知道怎么测了),这三个参数都需要。在测试的过程中,我发现了sid就是一个随机数,没有什么特殊含义,基本可以确定可以随便输入:js代码如下:
icp_host的取值有很多:sccainfo、ahcainfo、jscainfo......有没有发现什么规律?首先八个字母,最后六个都是cainfo,那么前面两个代表什么?sc=四川、ah=安徽、js=j江苏。所以,我们可以猜测这个是省份的简写。那这个值有什么用呢?作用一,如果不按这个规则输入字符串(比如,aaaaaaaa),就获取不到验证码;作用二,验证码就是和这个绑定的。也就是说,你获取验证码的时候用sccainfo,那么验证的时候也要用sccainfo。
分析完参数,然后再分析请求头,方法和参数的分析方法一样,一个个删除,看能不能获取正确的结果。这个时候,可以自己写python代码测试,具体代码如下:
#encoding=utf8
import urllib2
from PIL import Image
import cStringIO
getCode_url = "http://icp.alexa.cn/captcha.php?q=163.com&sid=0&icp_host=hncainfo"
header={"Referer":"http://icp.alexa.cn/captcha.php?q=163.com&sid=0&icp_host=hncainfo"}
# header['Host']="icp.alexa.cn"
# header['User-Agent']="Mozilla/5.0 (Windows NT 6.3; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"
header['Cache-Control']="max-age=0"
request = urllib2.Request(getCode_url,headers=header)
res = urllib2.urlopen(request).read()
image = Image.open(cStringIO.StringIO(res))
image.show()
代码说明:
a).cStringIO python的流模块,无论是图片、文本、音频、视频都是流文件,可以相互转化。这里的作用是将图片流还原成图片
b).header中添加参数可直接用header['']="",这样就可以测试了。具体哪些参数必须,自己测试。
运行结果:
3).检验验证码
a).分析目标网站,寻找检验验证码的请求。我们在输入框输入正确的验证码,点击备案查询,如图所示:
可以看到控制台中多了一个请求
点击请求,查看请求详情:http://icp.alexa.cn/index.php?q=163.com&code=65a89c&icp_host=lncainfo
三个参数:
q=163.com:查询的域名,必不可少
code=65a89c:验证码,必不可少
icp_host=lncainfo:和获取验证码相对应
然后再分析header,与上面的方法一样。这两次检验不同的是:检验获取验证码时,是自己写代码获取验证码,然后放到网站上检验,验证码的正确性(必须保证icp_host一致);检验检查验证码时,是用网站获取验证码,填到代码里面,看看参数对不对。
验证代码如下:
#encoding=utf8 import urllib2 checkcode_url = "http://icp.alexa.cn/index.php?q=163.com&code=N3PE37&icp_host=hncainfo" header={} # header['Pragma']="Pragma" # header['Referer']="http://icp.alexa.cn/index.php?q=163.com&code=CUXWDV&icp_host=sccainfo" header['User-Agent']="Mozilla/5.0 (Windows NT 6.3; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0" request = urllib2.Request(checkcode_url,headers=header) res = urllib2.urlopen(request).read() print res
代码说明:icp_host=hncainfo这个参数必须和你获取验证码时候的参数一致
运行结果:
如果验证码不正确或者别的地方不一致,会返回:
到此,我们就分析完了,不过现在是把获取和验证放在两个代码中运行,怎么放在一起呢?代码如下:
#encoding=utf8 import urllib2 from PIL import Image import cStringIO import BeautifulSoup def getCode(domain): print "获取验证码...." getcode_url="http://icp.alexa.cn/captcha.php?q="+domain+"&sid=0&icp_host=hncainfo" getcode_headers = {} getcode_headers['Referer']="http://icp.alexa.cn/captcha.php?q=163.com&sid=0&icp_host=hncainfo" getcode_headers['Cache-Control']="max-age=0" getcode_request = urllib2.Request(getcode_url,headers=getcode_headers) getcode_res = urllib2.urlopen(getcode_request).read() image = Image.open(cStringIO.StringIO( getcode_res)) print "获取验证码成功" image.show() def checkcode(domain,code): # print "您输入的验证码为:"+`code` print "开始检查验证码..." checkcode_url = "http://icp.alexa.cn/index.php?q="+domain+"&code="+code+"&icp_host=hncainfo" checkcode_headers={} checkcode_headers['User-Agent']="Mozilla/5.0 (Windows NT 6.3; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0" checkcode_request = urllib2.Request(checkcode_url,headers=checkcode_headers) checkcode_res = urllib2.urlopen(checkcode_request).read() if(checkcode_res.count("主办单位名称")>0): print "验证成功" checkcode_soup = BeautifulSoup.BeautifulSoup(checkcode_res) print "所属单位名称:"+checkcode_soup.findAll("table")[0].findAll("tr")[0].findAll("td")[1].text.encode("utf8") else: print "验证失败" domain = raw_input("请输入域名:") getCode(domain) code = raw_input("请输入验证码:") checkcode(domain,code)
代码说明:
a).def getCode(domain) 声明一个函数,getCode是函数名,domain是参数
b).raw_input() 获取用户输入
c).在获取和验证的时候,我把icp_host都写成了hncainfo,这样就可以保证一致。
d).encode("utf8") 对变量以utf8格式编码
e).验证码要人工识别输入
运行结果:
到此,整个验证码的获取,验证都讲述完了,验证码的识别放在下一节。
说明:
a).代码仅供学习交流
b).如有错误,多多指教
c).转载请注明出处