昨天偶然间发现了一个爬虫的闯关网站,就赶紧来用它练一练爬虫,今天总算是将五个关卡都通关了,其中也碰到不少坑,通过百度借鉴别人的思路代码也都慢慢解决了,学到了很多,下面就附上五个关卡的思路和代码

Pass-1

网址:http://www.heibanke.com/lesson/crawler_ex00/

首先页面给的提示是要我们在网址后面输入数字73618

访问http://www.heibanke.com/lesson/crawler_ex00/73618/

页面又提示要输入数字53825

访问http://www.heibanke.com/lesson/crawler_ex00/53825/

又提示要输入数字,于是推测这关要一直输入到某个数字后才能得到最后结果

那么我们可以将每个提示页面的数字,即下一个要输入的数字爬取下来,与原来的url构成一个下一个要访问的url,再继续爬取数字,假如爬取不到数字,就猜测可能得到了最后的结果

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import re

url = 'http://www.heibanke.com/lesson/crawler_ex00/'
num = ''
while True:
next_url = url+num
print(next_url)
r = requests.get(next_url)
match = re.search(r'数字.*(\d{5})',r.text)
if not match:
print('You have found the last number!')
print('The last number is:',str(num))
break
num = match.group(1)

这里通过正则匹配出下一个要输入的数字,然后与网页原本的url拼成下一个要访问的url,当匹配不到数字时退出循环

运行结果:

猜测69428是最后输入的数字,我们访问一下http://www.heibanke.com/lesson/crawler_ex00/69428/

果然,页面提示了通关,点击下一关按钮,进入到第二关的页面

Pass-2

网址:http://www.heibanke.com/lesson/crawler_ex01/

首先根据页面提示,我们需要在表单中输入一个用户名和密码,用户名可以随机输入,密码则是1到30之间的数字

我们先来看一下源代码中的表单部分

可以看出表单是以POST形式提交的,提交的参数为username和password,还会提交一个隐藏的token值,看到token值,我们的第一反应都是要先获取到页面的token值,与数据一起提交,但是经过测试,这关后台服务器并没有对token值进行检测,也就是说我们在程序中并不需要提交这个token值,也可以提交成功,既然不用管token,那么这关就很简单了

我们先随意提交一个用户名aaa和密码1看看提示是什么

可以看到页面提示”您输入的密码错误”,说明这是输入错误的提示信息,那么思路就很清晰了,我们依次提交数值为1-30之间的密码值,然后根据页面的提示信息是否包含”您输入的密码错误”,如果没有则可能是正确的密码

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

url = 'http://www.heibanke.com/lesson/crawler_ex01/'
for i in range(1,31):
data = {
'username':'aaa',
'password':i,
'submit':'提交'
}
r = requests.post(url,data=data)
if '您输入的密码错误' not in r.text:
print('Correct password:',str(i))
break
else:
print('Wrong password:',str(i))

运行结果:

说明20可能是正确的密码,我们在浏览器中提交

闯关成功,继续点击下一关按钮进入第三关

Pass-3

网址:http://www.heibanke.com/lesson/crawler_ex02/

我们会发现自动跳转到了登录页面,也就是说这一关需要模拟登录

我们先注册一个账号和密码登录进去

看起来和上一关没什么区别,一样是输入用户名和密码,用户名是随意的,密码还是1-30之间的数字,唯一区别在于页面提示比上一关多了两层保护,光看这句话还是很懵逼的,没事,我们看一看页面源代码,就可以大致猜到到底是什么保护了

可以看出表单提交了隐藏的token值,猜测这关服务器会对token值进行检测

再回到登录页面,查看源代码的表单部分

登录页面也有一个token值

所以思路是这样的,我们首先需要模拟登录,这就需要用到requests库的Session方法保持一个会话记录,否则我们再次访问这关的页面依旧会跳转到登录页面,然后我们还需要爬取页面信息的token值,也就是提交token的input标签的value值

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import requests
from bs4 import BeautifulSoup

login_url = 'http://www.heibanke.com/accounts/login/?next=/lesson/crawler_ex02/'
pass_url = 'http://www.heibanke.com/lesson/crawler_ex02/'
s = requests.Session()

def getToken(html):
soup = BeautifulSoup(html,'html.parser')
token = soup.find_all('input')[0]['value']
return token

def login():
r = s.get(url=login_url)
login_token = getToken(r.text)
login_data = {
'csrfmiddlewaretoken':login_token,
'username':'xxx', #替换成自己的用户名
'password':'xxx' #替换成自己的密码
}
login = s.post(url=login_url,data=login_data)
if '这里是黑板客爬虫闯关的第三关' in login.text:
print('login successfully!')
pass_token = getToken(login.text)
return pass_token

pass_token = login()
for i in range(31):
data = {
'csrfmiddlewaretoken':pass_token,
'username':'aaa',
'password':i
}
result = s.post(url=pass_url,data=data)
if '您输入的密码错误' not in result.text:
print('------Correct password:',str(i))
break
else:
print('fail to pass')

这里用了BeautifulSoup库的find_all方法获得所有input标签组成的列表,观察出token值所在的input标签是列表的第一个元素,就可以通过索引获得token值,登录成功后还要继续抓取关卡页面的token值,与用户名和密码一起提交

最后的运行结果如下:

在浏览器中提交用户名:aaa,密码:4

闯关成功,此处后台代码有误,点击下一关还是第三关的页面,但是我们可以根据前面几关的url推测出第四关的url:http://www.heibanke.com/lesson/crawler_ex03/

Pass-4

网址:http://www.heibanke.com/lesson/crawler_ex03/

跟第三关一样,还是需要先登录

登录后来到第四关关卡页面

页面提示”密码很长,是试不出来的,需要找出来”,看一下源代码,还是跟第二关第三关一样,提交username和password参数,服务器有对token值进行检测

但是还是没有关于密码的提示部分信息

怎么办呢,我们先试一下随便输入一个密码1,看看页面返回什么

惊喜的发现提示来了,我们点击”在这里耐心找”访问到提示页面(PS:这个页面有点慢,需要耐心等待加载)

仔细观察,会发现这里提示信息是给出了一共100位的密码,其中每一位的值,而且每一页显示的位数还会变化,所以这里要访问所有的页数获取每一位数和对应的值是行不通的,所以我们干脆就一直访问第一页,爬取每一位的位数和对应的值,直到爬取位数共有100为止

直接附上代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import requests
import re
from bs4 import BeautifulSoup

login_url = 'http://www.heibanke.com/accounts/login/?next=/lesson/crawler_ex03/'
pass_url = 'http://www.heibanke.com/lesson/crawler_ex03/'
hint_url = 'http://www.heibanke.com/lesson/crawler_ex03/pw_list/'
s = requests.Session()

def getToken(html):
soup = BeautifulSoup(html,'html.parser')
token = soup.find_all('input')[0]['value']
return token

def login():
r = s.get(login_url)
login_token = getToken(r.text)
login_data = {
'csrfmiddlewaretoken':login_token,
'username':'xxx',
'password':'xxx'
}
login = s.post(url=login_url,data=login_data)
if '这里是黑板客爬虫闯关的第四关' in login.text:
print('login successfully')
pass_token = getToken(login.text)
return pass_token

def getDict():
total_pos = []
passwordDict = {}
while True:
hint = s.get(url=hint_url)
pos = re.findall(r'title="password_pos">(\d*)',hint.text)
value = re.findall(r'title="password_val">(\d*)',hint.text)
for i in range(len(pos)):
if pos[i] not in total_pos:
total_pos.append(pos[i])
passwordDict[pos[i]] = value[i]
if len(total_pos) == 100:
break
else:
print(len(total_pos))
print(passwordDict)
return passwordDict

def getPassword(dic):
passList = []
for i in range(1,101):
passList.append(dic[str(i)])
password = ''.join(passList)
return password

pass_token = login()
passwordDict = getDict()
print(passwordDict)
password = getPassword(passwordDict)
print(password)

一开始还是跟第三关一样,先获取登录页面的token值,然后登录,页面如果有返回”这里是黑板客爬虫闯关的第四关”的信息说明登录成功,登录成功再获取关卡页面的token值,接下来就获取位置数和对应的值,这里定义了一个列表total_pos用来存放总的位置数和一个字典 passwordDict用来存放位置数及其对应的值,然后不断的访问提示页面的第一页,通过re模块的findall方法匹配出pos和value列表,每次匹配完检查pos列表的每一个元素,如果不在total_pos中,则添加进total_pos中,并将‘pos[i]’:’value[i]’键值对添加进字典中,直到匹配出100位为止,此时就获得了每一位和对应的值,再从第一位开始从字典中取出value值,添加进passList列表中,最后将passList列表元素拼成一个长字符串,就是最后获得的密码

这里过程实在是很慢,总之看脸…脸好很快就爬到了100位

最后获得的字典:

1
2
3
passwordDict = {
'73': '8', '76': '6', '28': '5', '72': '1', '61': '4', '32': '3', '57': '6', '75': '3', '90': '0', '53': '7', '74': '1', '56': '2', '83': '1', '34': '9', '44': '4', '70': '6', '92': '4', '18': '5', '93': '8', '37': '8', '85': '3', '67': '8', '17': '3', '62': '3', '84': '6', '24': '5', '8': '8', '15': '3', '100': '2', '55': '3', '1': '3', '10': '7', '20': '7', '79': '3', '12': '4', '91': '6', '36': '4', '43': '6', '60': '8', '48': '0', '25': '0', '22': '8', '4': '1', '16': '5', '94': '9', '54': '4', '50': '9', '66': '4', '64': '1', '5': '8', '47': '9', '51': '4', '80': '4', '69': '7', '71': '6', '7': '0', '96': '3', '46': '9', '2': '3', '27': '9', '49': '3', '13': '5', '99': '0', '89': '7', '39': '4', '26': '6', '63': '7', '29': '2', '52': '8', '65': '3', '33': '2', '30': '4', '9': '6', '86': '2', '19': '7', '38': '9', '40': '6', '98': '7', '77': '9', '59': '4', '41': '1', '42': '3', '21': '4', '45': '7', '58': '0', '6': '3', '81': '4', '23': '9', '3': '8', '11': '4', '97': '6', '82': '9', '14': '1', '87': '6', '78': '6', '31': '4', '95': '9', '68': '7', '88': '4', '35': '5'
}

整理后的密码:

1
password = 3381830867445135357748950695244329548946136479903948743260484371348776618136963449163264706489936702

第五关的url依旧要靠猜

Pass-5

网址:http://www.heibanke.com/lesson/crawler_ex04/

依然要登录,很明显这关要提交验证码,观察一下源代码

可以发现这个验证码其实是一个url,点击进去能看到验证码的图片,跟token一样,每次刷新页面,验证码的url都会变化,因此验证码就会一直变化

所以,我们要通这最后一关,就必须要想办法获取这个验证码的值

在Python中,有一个库叫pytesseract,这个库里的image_to_string方法具有识别图片中信息的功能

由于这个库跟requests一样,是第三方库,因此我们需要先安装后才能使用

除了pytesseract库之外,我们还依赖PIL库的Image方法打开一个图片

安装这两个库都可以用pip命令安装

比较关键的是pytesseract库识别验证码还依赖于一个插件Tesseract-OCR ,这个插件我们可以百度搜索tesseract-ocr-setup-3.02.02.exe 下载,这里我使用的是3.02版本,没有碰到什么问题,按照安装提示一步步下载下来,唯一要注意的是要按照默认路径下载,Windows的默认安装路径是C:\Program Files (x86)\Tesseract-OCR\

这是因为我们下载完后需要在Python文件夹的下的\Lib\site-packages\pytesseract\pytesseract.py修改源代码中

1
tesseract_cmd = 'tesseract'

修改为:

1
tesseract_cmd = r'C:\Program Files (x86)\Tesseract-OCR\tesseract.exe'

安装完成后,我们就可以获取验证码了,除了验证码外,我们还要获取三个值:登录界面的token值,关卡界面的token值,关卡界面的captcha_1 值

需要注意的是,我们用pytesseract库虽然可以识别验证码,但并不是百分百正确,所以我们需要对验证码进行校验,经过观察本关的验证码都是四个大小写英文字母,我们可以通过正则匹配进行校验,如果校验失败,则继续识别,直到校验成功,则提交猜测的密码,这里密码从0开始递增猜测

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import requests
import pytesseract
import re
from bs4 import BeautifulSoup
from PIL import Image
from io import BytesIO

login_url = 'http://www.heibanke.com/accounts/login/?next=/lesson/crawler_ex04/'
pass_url = 'http://www.heibanke.com/lesson/crawler_ex04/'
s = requests.Session()

def getToken(html):
soup = BeautifulSoup(html,'html.parser')
token = soup.find_all('input')[0]['value']
return token

def getCaptcha_0(html):
soup = BeautifulSoup(html,'html.parser')
Captcha_0 = soup.find('input',id="id_captcha_0")['value']
return Captcha_0

def login():
r = s.get(url=login_url)
login_token = getToken(r.text)
login_data = {
'csrfmiddlewaretoken':login_token,
'username':'xxx',
'password':'xxx'
}
login = s.post(url=login_url,data=login_data)
if '这里是黑板客爬虫闯关的第五关' in login.text:
print('login successfully!')

def getImageURL(html):
soup = BeautifulSoup(html,'html.parser')
imageURL = 'http://www.heibanke.com'+soup.find('img',class_='captcha')['src']
return imageURL

def getImageCode():
while True:
print('--------------------开始识别验证码')
pas = s.get(url=pass_url)
imageURL = getImageURL(pas.text)
image = s.get(url=imageURL)
captcha_img = Image.open(BytesIO(image.content))
imageCode = pytesseract.image_to_string(captcha_img)
print('验证码识别结果:',imageCode)
print('--------------------开始校验验证码')
match = re.search(r'^[A-Z | a-z]{4}$',imageCode)
if not match:
print('验证码:',imageCode,'校验结果识别失败,继续识别')
else:
print('验证码:',imageCode,'校验成功')
mess = (imageCode,pas)
break
return mess

def guess():
password = 0
while True:
print('--------------------------------------开始猜测密码')
imageCode,pas = getImageCode()
pas_token = getToken(pas.text)
Captcha_0 = getCaptcha_0(pas.text)
data = {
'csrfmiddlewaretoken':pas_token,
'username':'aaa',
'password':password,
'captcha_0':Captcha_0,
'captcha_1':imageCode
}
g = s.post(url=pass_url,data=data)
if '验证码输入错误' in g.text:
print('验证码输入错误')
elif '您输入的密码错误' in g.text:
print('密码:',password,'错误')
password +=1
else :
print('密码:',password,'正确')
print('返回的页面结果:')
print(g.text)
break

login()
guess()

这关坑还是很多的,主要是识别校验码的正确率有点低,即使校验结果是四个英文字母,也不一定是正确的验证码,而且密码是随着时间变化的,总之耐心尝试吧,有了思路多尝试最后总是能成功的

最后我的时间段检测到的正确密码:

在浏览器提交密码19

最后附上参考的链接:https://www.jianshu.com/p/f64853b8f7e9