打开Python爬虫实战练习页面初识浏览器指纹:Selenium是如何被反爬的_H06_Spiderbuf,可以看到页面内容是由NordPass发布的2022年全球最常用密码列表,只是一个简单的表格,数据也只有10条。
在网页上点击右键 > 显示网页源代码,可以看到网页结构也并不复杂。HTML代码并不多,总共就88行,但在源码当中没有看到页面上显示的内容,这种情况一般是由JavaScript动态加载并渲染内容的。
F12打开谷歌浏览器开发者工具,刷新一下页面,看到network(网络)这里加载了几个文件,其中有一个JavaScript文件是值得注意的,因为其它几个文件名就能猜到它的作用,唯独有一个是看似无意义的。
HfPro9C.js
还有一个很长的字符串,我们点击它,右边就会展开详情,Headers里有api这样的关键字,api通常是后台返回数据的。我们切换到Response(响应)标签,能够看到后台返回了一个json。
[
{
"ranking": 1,
"passwd": "password",
"time_to_crack_it": "\u003c 1 Second",
"used_count": 4929113,
"year": 2022
},
{
"ranking": 2,
"passwd": "123456",
"time_to_crack_it": "\u003c 1 Second",
"used_count": 1523537,
"year": 2022
},
...
找到了数据返回的接口,而且拿到了明文json,那按理说我们这次的爬虫分析工作就差不多了,但当我们把刚才分析的api请求链接放到浏览器地址栏打开时,发现获取不到数据(中间出了Bug,导致有些人可以继续用这个链接,这里就当用不了吧^_^),也就说,我们要么继续分析这个请求链接是怎么构造的,要么就使用selenium这样的模拟浏览器。
先不管三七二十一,丢到selenium里运行一下再说。
client = webdriver.Chrome()
client.get('https://spiderbuf.cn/playground/h06')
print(client.page_source)
client.quit()
结果发现打印出来的HTML源码里也没有我们想要的数据,很多人这时候就不知道怎么办了,明明selenium就是一个浏览器,结果连selenium都获取不到,不知道该如何处理了。
此路不通,那我们就退回来,分析请求参数的构造,这时候就要想起我们刚才提到的HfPro9C.js文件的。
点击打开这个文件,发现居然是明文(实际爬虫过程中几乎没有明文js这样的好事,这里为了说明问题及思路,先不搞这么复杂)。
var user_agent = navigator.userAgent;
if ((!navigator.webdriver) & (navigator.plugins.length > 0) & (user_agent.indexOf('headless') < 0)) {
console.log(navigator.webdriver);
console.log(navigator.plugins.length);
console.log(user_agent.indexOf('headless'));
var timeStamp = Math.trunc(new Date().getTime() / 1000);
var _md5 = md5(timeStamp);
var s = btoa(`${timeStamp},${_md5}`);
fetch("./h06/api/" + s).then(function (response) {
return response.json();
}).then(function (data) {
var dataContent = document.getElementById('dataContent');
data.forEach((value, index) => {
var row = dataContent.insertRow();
var rankingCell = row.insertCell();
rankingCell.innerText = value.ranking;
var passwdCell = row.insertCell();
passwdCell.innerText = value.passwd;
var time_to_crackItCell = row.insertCell();
time_to_crackItCell.innerText = value.time_to_crack_it;
var used_countCell = row.insertCell();
used_countCell.innerText = value.used_count;
})
});
}
我们来逐行分析一下这个js文件代码,建议大家要往爬虫方面研究的话就要多熟悉javascript,要不然碰上混淆后的代码就无从下手了。
- 第1行只是把浏览器的user agent 放在了一个变量里面;
- 第2行开始做了判断,判断了webdriver、浏览器插件数量、headless关键字;
- 第3行到第5行就是在浏览器控制台输出日志,这个可以利用起来,在普通浏览器里面刷新网页看看输出了什么,再到selenium里面刷新日志,对比一下两者输出有什么区别;
- 第6行把时间戳放到了变量timeStamp里面;
- 第7行把刚才的时间戳生成了一个MD5哈希值;
- 第8行把时间戳跟md5值组装成了一个字符串,并使用了Base64编码,btoa就是对字符串进行Base64编码;
- 再往下fetch这里,就是把刚才生成的Base64字符串组装成一个请求链接了;
到了这里,我们就可以用Python按照上面这个思路构造出合法的请求了。
url = 'http://spiderbuf.cn/h06/api/'
timestamp = str(int(time.time()))
md5_hash = hashlib.md5()
md5_hash.update(timestamp.encode('utf-8'))
md5 = md5_hash.hexdigest()
s = ('%s,%s' % (timestamp, md5))
print(s)
payload = str(base64.b64encode(s.encode('utf-8')), 'utf-8')
print(payload)
html = requests.get(url + payload, headers=myheaders).text
print(html)
我们已经通过分析JavaScript代码逻辑来编写Python代码写出我们的爬虫了,但为了弄清楚为什么selenium没有数据,我们再继续分析。
在上面分析JavaScript代码的时候,我们已经在第2行看到了判断,整个JavaScript文件里面也就这一个if语句,那selenium没有数据也肯定跟它有关。
真想就是selenium是浏览器,但有些默认的标识是与普通浏览器不一样的,而这些标识是可以通过JavaScript代码获取出来的,有些是会发送到后台的。
上面的JavaScript代码是最常见的检测模拟浏览器(也就是判断是否为爬虫)的代码,针对上面的检测,我们只需要在写爬虫代码的时候改几个选项即可。
# 创建一个webdriver选项变量
options = webdriver.ChromeOptions()
options.set_capability('goog:loggingPrefs', {'browser': 'ALL'})
# 改变navigator.webdriver 属性值
options.add_argument('--disable-blink-features=AutomationControlled')
# 把自定义的选项传递给webdriver
client = webdriver.Chrome(options=options)
这样处理之后,我们就绕过了selenium被检测到的机制,从而通过selenium正常获取网页内容。
selenium之类的模拟浏览器,被检测到的选项远远不止以上几个值,实际上浏览器有很多特征是可以用JavaScript获取到的,这些特征一般被称为浏览器指纹。
这里有可以检测浏览器大部分特征的网页链接,大家可以去看看浏览器都有哪些指纹特征 浏览器指纹检测
这里附上有最新版本的 Chrome WebDriver 的下载链接 WebDriver下载链接
完整示例代码:示例代码