爬虫代码已复制

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

打开Python爬虫实战练习C04页面 爬虫实战练习C03,页面上只有一个类似“我不是爬虫”的提示及勾选框,这种页面有点类似我们经常在网站上遇到的 Cloudflare Verify you are human 页面,勾选后会判断当前用户是否为机器人爬虫。

Cloudflare 这个验证码之所以流行是跟 Cloudflare 免费服务有关的,Cloudflare 是一家提供云服务器、VPS、数据存储、CDN加速、VPN、SSL证书、网络安全、DDos 防御和域名服务等服务的美国公司,而且它有很多服务是免费提供的,所以很多网站都接入了 Cloudflare 的服务,就连 DeepSeek 都接入了 Cloudflare 的反爬虫服务,特别适合个人开发者及创业团队白嫖。很多时候在阿里云、腾讯云、华为云等注册域名时提示已经被注册的域名也可以到 Cloudflare 碰碰运气。

回到正题,先看看这一关的目标数据是什么,点击“立即验证”,发现是要求计算并提交阅读数 + 评论数的平均值。我们点击一下页面的勾选框,发现出来的数据是模拟微博的内容,里面有目标的阅读数跟评论数。

这时候先按快捷键 F12 打开浏览器开发者工具,切换到 Network 标签,记得先清空掉 Network 里的内容避免干扰,这时候再点击我不是爬虫的勾选框,发现在加载数据的时候浏览器并没有对外发送请求。这说明数据已经在页面加载的时候一起加载了,我们需要把数据部分找出来,然后再看看是否有数据加密等等数据安全验证措施。

回到页面上,右键 -> 查看页面源码,把 html 都浏览一遍都没发现数据的标签,也没有见到有加密的数据,这就说明数据放在 JavaScript 文件里面。顺着这个思路在 html 源码的第 166 行发现了一个文件名为 lhY3nm7.min.js 的 JavaScript 文件的引用,直接点击打开它。

在这个 JavaScript 文件中发现了一大片疑似 Base64 编码的字符串,按照以往的经验这里应该就是加密后的数据。先复制出来放到 Base64 编码解码工具 里尝试解码,注意复制的时候区分成对的单引号。发现数据解码成功了。

那这种情况下只需要使用 Python 编写爬虫代码,获取 lhY3nm7.min.js 文件,然后通过字符串截取的方式获得加密的数据,然后通过 Base64 解码就可以得到目标数据了。

以上是一种解法,我们接着上难度,祭出我们的爬虫大杀器:Selenium 。当然,模拟浏览器你也可以用微软家的 PlayWright、Puppeteer、Chromedp等等。

先尝试 Selenium 来爬取页面,先找到勾选框的 ID:captcha,因为我们等一下需要通过 Selenium 自动化点击。这里既然有勾选框的方式判断是否为爬虫,那就应该想到大概率是有检测 WebDriver 的代码的,所以要尽可能隐藏 Selenium 自动化浏览器的特征。

隐藏 Selenium 浏览器指纹的方式我在 《初识浏览器指纹:Selenium是如何被反爬的》 的文章中有讲过,这里就不再重复了,有兴趣的自行跳转到那篇文章进行研究。

基于 Selenium 的爬虫核心代码如下:

options = webdriver.ChromeOptions()
options.add_argument('disable-infobars')
options.set_capability('goog:loggingPrefs', {'browser': 'ALL'})
options.add_argument('--disable-blink-features=AutomationControlled')
client = webdriver.Chrome(options=options)
print('Getting page...')
client.get(url)
checkbox = client.find_element(By.ID, 'captcha')
checkbox.click()
print('Checkbox clicked...')
time.sleep(2)
html = client.page_source
print(html)

以上爬虫代码使用 Selenium 打开页面后输出 html 源码并进行解析,然而,运行后并没有出现我们想要的数据。说明除还做了其它的反爬虫检测,结合上面分析出来的点击勾选框后到数据出现时浏览器没有发起新的请求,说明检测是在浏览器进行的,有可能是还检测了其它的浏览器指纹信息,也有可能是检测了用户行为。

因为暂时无法判断出是否检测了其它的浏览器指纹信息,所以我们先模拟一下用户行为试试。

用户在页面的行为多数为鼠标移动,因为我们现在不知道是在页面哪个元素的事件中检测了用户行为,所以我们要尽量把光标在页面上的每一个元素上滑过,这里就要先取得“我不是爬虫”这个区域在网页上的位置,以便把光标在这个位置滑过。而且页面的 mousemove 事件捕捉到的光标坐标是非常多的,所以我们也多做几次移动。

回到浏览器开发者工具 -> 元素(Elements),选中“我不是爬虫”这个元素,在右边切换到属性面板,找到 offsetLeft、offsetTop、offsetWidth、offsetHeight 这4个数值,这就是这个元素在页面上的位置及长宽。

取到的值分别如下:

  • offsetHeight: 45
  • offsetLeft: 435
  • offsetTop: 337
  • offsetWidth: 40

这样算出来“我不是爬虫”的的坐标是 X = (435,435 + 40), Y = (337, 337 + 45),我们要保证在这个区域内触发不少于10次的光标移动。因为正常来说人在网页上随便移动一下鼠标就会被捕获到不少于10次的移动。

于是,在上面的 Selenium 代码基础上加入移动光标的代码(注意:要加在点击之前,因为是点击才触发加载数据的 JavaScript 代码的):

actionChains = ActionChains(client)
actionChains.move_by_offset(430,330)
for i in range(20):
    step = random.randint(1, 10)
    actionChains.move_by_offset(step,step).perform()

以上模拟用户行为的代码我们用一个20的循环进行循环触发,然后通过随机函数来设定每次移动的位移。执行优化后的 Selenium 爬虫代码,发现数据已经能够加载出来了。然后找到阅读数与评论数的 xpath 表达式,使用 lxml 提取出来,再算平均值就可以了。

这一关到这里其实我们已经用了两种方法来获得数据了,如果抱着学习的心态可以去逆向一下 JS 代码。这个逆向也简单,不需要把所有加密混淆的代码及匿名函数都分析梳理出来,因为这里不涉及到 API 数据接口的加密解密,在 lhY3nm7.min.js 代码文件里面很容易就能找到明文的 decode 函数,只需要通过浏览器开发者工具源码那里使用 Overrides 替换,在 decode 函数 return 前加一句 console.log 把要 return 的数据打印到控制台就能看到了。

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