打开Python爬虫实战练习C06页面 爬虫实战练习C06,页面上有一部分电影数据,把页面滚动到底部,发现有一个“加载更多”的按钮,点击之后页面会新加载一部分数据。
先看看这一关的目标数据是什么,点击 立即验证 ,发现是要求计算并提交豆瓣评分的总值。
鼠标右键查看一下页面源码,发现数据都是明晃晃地摆在那里。这种数据结构简单、又有 AJAX API 数据接口的爬虫,我们首先 Selenium。
先通过浏览器开发者工具获取到“加载更多”按钮的 XPath 路径,再获取豆瓣电影评分的 XPath 路径,然后开始写 Python 爬虫代码来调用 Selenium。
上代码:
import requests
from lxml import etree
from selenium import webdriver
from selenium.webdriver.common.by import By
client = webdriver.Chrome()
client.get('https://spiderbuf.cn/web-scraping-practice/scraper-practice-c06')
client.find_element(By.XPATH, '//*[@class="btn-page"]').click()
html = client.page_source
root = etree.HTML(html)
spans = root.xpath('//div[@class="detail"]/p[1]/span[2]')
total = 0.00
for span in spans:
total += float(span.text)
print(f"{total:.2f} ")
以上爬虫代码的执行结果是92.30,我们把这个结果填入到验证框中提交,发现是错的。这说明这个页面是有反爬虫的代码的,并且检测到了 Selenium ,于是就返回了一些假的 API接口数据。
这个时候我们就只能回去分析这个页面的 JavaScript 代码了。
在浏览器开发者工具那里,切换到网络面板,发现有一个 https://spiderbuf.cn/web-scraping-practice/scraper-practice-c06 的 POST 请求,看一下这个请求的响应数据,就是我们想要的豆瓣电影评分数据,并且数据没有加密。
看一下这个请求的载荷,发现就3个参数,而且从参数名字就能看出现是随机数、时间戳、签名,并且签名看起来像是MD5, 我们就可以试着把随机数跟时间戳以字符串的形式组合,然后计算 MD5,发现结果不对,说明这个猜测是错误的,碰不了运气就只能拼实力了,进去分析 JavaScript 代码。
在刚才的开发者工具界面,切换到右边的启动器,有个请求堆栈,page 这里直接点击进去,就会发现已经定位到了这个请求发起的代码行,即第 334 行。
可以看到这行有一个明文的 fetch 函数,也就是说请求参数的生成肯定是在这一行的前面,往前看一下代码,就会在 326 - 328 行代码中发现疑似生成请求参数的代码。而且代码中大量出现 _0xe95a 这样的关键字。
第 328 行代码出现了 MD5 这样的关键字,而且明显是前面两个参数中间再加了一个未知字符串进行计算。那么我们在这里下个断点,刷新页面,点击“加载更多”按钮。
代码停在这里的时候,我们查看一下右边的堆栈,发现变量 _0x4ff96e 与 _0x516bd4 的值分别对应随机数与时间戳,三个请求参数中我们找到两个了。然后就会发现 _0xe95a 出现了一个字符串数组。
结合 const _0x410a9d = _0x5d9ea9[_0xe95a('0x25')](md5, _0x4ff96e + _0xe95a('0x26') + _0x516bd4)_0xe95a('0x45'); 这行代码我们就知道,只要找出 _0xe95a('0x26') 就可以知道参数签名加了什么内容。
上面发现了 _0xe95a 带了一个字符串数组,而且 0x26 又好像是一个十六进制数字,把它转换成十进制,发现是 38,在 _0xe95a 的数组中找到第 38 个字符串:spiderbuf。
也就是说请求参数的签名是 md5(随机数 + spiderbuf + 时间戳)。
接下来就写 Python 爬虫代码验证码一下这个分析结果。
random_value = 3006
timestamp = int(time.time())
md5_hash = hashlib.md5()
md5_hash.update(f'{random_value}spiderbuf{timestamp}'.encode('utf-8'))
hash = md5_hash.hexdigest()
payload = {
'random': random_value,
'signture': hash,
'timestamp': timestamp,
}
print(payload)
json_response = requests.post(base_url, headers=myheaders,json=payload).text
print(json_response)
以上代码中随机数直接复用了浏览器发起请求的值,运行以上代码,发现返回了 Bad request。但把浏览器请求成功的随机数与时间戳赋值到以上代码时,算出来的签名是一致的。这种情况就有可能少了些什么,而且这些参数不在 POST 的 Payload 里面,那最有可能的就是 Cookie 了。
刷新页面,果然看到了 Set-Cookie 这样的请求头,把这个 Cookie 值代入到以上代码中,再执行,发现数据就请求下来了。
接下来就是把页面的豆瓣评分取出来,再把“加载更多”的数据中的豆瓣评分也取出来,然后全部累加就可以了。
完整的爬虫案例代码:爬虫案例代码