爬虫代码已复制

Python爬虫实战C02爬虫实战练习案例解析

打开Python爬虫实战练习C02页面 爬虫实战练习C02,看到页面上是一个拖拽式的验证码,下面有一行提示"拖动滑块验证码成功后加载数据。。。"。

这时候先按快捷键 F12 打开浏览器开发者工具,切换到 Network 标签,然后尝试着拖动验证码。此时会发现把验证码拖到最右边后,页面加载了一个数据表格出来,里面是一些广州到各大热门城市的机票价格。

我们点击一下页面上的"立即验证",先看看这一个爬虫案例是要求我们获取并计算什么数据。打开后发现是让我们计算机票的平均价格。

鉴于刚才我们拖动验证码到数据加载完毕,都没有在开发者工具中看到有新的 HTTP 请求发出,所以可以断定数据早已经随着页面加载到了浏览器,只不过验证码通过后才动态渲染到页面上。

此时就可以通过右键菜单打开网页源码查看,可以看到 html 源码从第129行开始是一些 javascript 代码,继续往下查看,在第169行的位置看到一长串字符,根据以往的经验猜测这是一串 Base64 编码的字符串,把它复制出来验证一下。

打开在线工具 Base64编码解码,把刚刚复制的字符串粘贴进去,点击"解密"按钮,可以看到解码出来的是一个 Json 格式的内容,与页面上的内容核对一下,发现这里就是页面上的内容。虽然这些内容混合在 javascript 代码里面,但我们可以通过关键字 encryptedData 等把它截取出来再使用 Python 的 Base64 库进行解码,然后取出机票价格这一列计算平均值。

这时候就可以直接编写爬虫代码了:

html = requests.get(base_url, headers=myheaders).text
# 这是17 是 encryptedData = 的长度
a = html.index('encryptedData = "') + 17
html = html[a:]
b = html.index('";')
html = html[:b]
print(html)
dic = eval(base64.b64decode(html.encode('utf-8')))
objs = dic['flights']
prices = []
for obj in objs:
    print(obj)
    prices.append(obj['price'])

print(prices)
print(np.mean(prices))

得出的值是617,回到刚才打开的验证页面,填入这个值并点击提交答案,提示答对了。

然而,这还没完。

这次是碰巧遇到 javascript 没有加密混淆,但实际上是没这么好的事的,既然这里有拖拽式验证码,那我们就尝试一下使用 selenium 来开发一个爬虫。

selenium 是可以操控 html 元素进行拖动的操作的,但我们需要先知道要拖动的元素的 ID 或者 CSS Selector。回到我们的目标页面,使用开发者工具 Elements 的箭头指向验证码滑块的位置,并在 Elements 查看它的 ID 之类的信息,并在滑块拖动成功后查看它的最终位置。

这个位置信息在 Elements 的箭头选中滑块的情况下,在开发者工具右边的 styles 就能看到。

elements styles

252px 就是我们要拖到的位置,然后调用 selenium 调试一下我们的代码。

client = webdriver.Chrome()
client.get(url)
time.sleep(10)

# 事件参数对象
actionChains = ActionChains(client)

# 捕捉滑块元素
slide_btn = client.find_element(By.ID, 'slider')
# 观察网站滑块移动的长度和位置
actionChains.click_and_hold(slide_btn)
actionChains.move_by_offset(252,0)
actionChains.release()
actionChains.perform()

html = client.page_source
print(html)

执行完上面的代码后,发现并没有像我们预期地那样出来数据,这说明页面的 javascript 代码时肯定有反爬虫逻辑,回到网页源码第129行开始分析一下这段 javascript 代码。

因为数据在第169行,我们直接找到数据所在的函数(第152 - 184行)进行分析,可以看到函数开始就是判断验证码滑块的位置,直到第165行有一个判断,回头找到这个 x_map 的变量定义,发现它是一个字典的数据结构,并且是在 mousemove 事件里面赋值的,在鼠标拖动时把光标的 X 轴坐标值存了起来。

熟悉编程的朋友们都知道,字典是由键 - 值 对组成的,而且键是唯一的,也就是说你存多次同一个键进去,尽管值不同,但字典里也只有一个键值对。

x_map < 2 就是判断了鼠标触发了多少次 mousemove 事件并被记录下来。说白了就是我们上面的代码一次拖到底,x_map 的长度只有 1,所以是小于2的。

知道了原理,我们就可以改进一下上面的代码,把鼠标拖动的事件分成多次就好了。

client = webdriver.Chrome()
client.get(url)
time.sleep(10)

# 事件参数对象
actionChains = ActionChains(client)

# 捕捉滑块元素
slide_btn = client.find_element(By.ID, 'slider')
# 观察网站滑块移动的长度和位置
actionChains.click_and_hold(slide_btn)
actionChains.move_by_offset(220,0)
# 这里要注意:
# 以下三个是以上面的坐标(220,0)为起点来计算的
# 所以最终移动的距离是220加上以下的累计
actionChains.move_by_offset(11,0)
actionChains.move_by_offset(13,0)
actionChains.move_by_offset(10,0)

actionChains.release()
actionChains.perform()

html = client.page_source
print(html)

这个时候我们就能在 selenium 界面看到数据成功加载出来了,接下来就是使用 xpath 把机票价格获取出来并计算平均值即可。

完整的爬虫案例代码:爬虫案例代码