This content originally appeared on DEV Community and was authored by drake
window.chrome = {
runtime: {},
loadTimes: function() {},
csi: function() {},
app: {}
};
window.chrome
对象概述
在真实的 Chrome 浏览器中,window.chrome
是一个全局对象,包含了 Chrome 浏览器特有的 API 和功能。自动化工具(如 Playwright)启动的浏览器可能缺少这个对象,这会被反爬虫系统识别。
各属性详解
1. runtime: {}
runtime: {}
真实作用:
- 在真实 Chrome 中,
chrome.runtime
提供扩展程序运行时的 API - 包含消息传递、存储管理、生命周期管理等功能
真实示例:
// 真实 Chrome 中的 runtime 对象包含:
chrome.runtime = {
onMessage: {...},
sendMessage: function() {...},
getManifest: function() {...},
getURL: function() {...},
id: "extension-id"
}
这里为什么是空对象:
- 简单伪装,让检测代码认为存在 chrome.runtime
- 避免实现复杂的 API,因为大多数检测只是检查对象是否存在
2. loadTimes: function() {}
loadTimes: function() {}
真实作用:
-
chrome.loadTimes()
是 Chrome 的性能监控 API - 返回页面加载的详细时间信息
真实返回数据:
chrome.loadTimes() // 真实返回:
{
requestTime: 1642680000.123,
startLoadTime: 1642680000.125,
commitLoadTime: 1642680000.150,
finishDocumentLoadTime: 1642680000.200,
finishLoadTime: 1642680000.250,
firstPaintTime: 1642680000.180,
firstPaintAfterLoadTime: 0,
navigationType: "navigate"
}
为什么是空函数:
- 防止调用时报错(如果不存在会抛出异常)
- 一些检测脚本只检查函数是否存在,不关心返回值
3. csi: function() {}
csi: function() {}
真实作用:
- CSI = Chrome Speed Index
- 提供客户端性能指标和统计信息
- 用于 Chrome 内部的性能分析
真实返回数据:
chrome.csi() // 真实返回:
{
pageT: 1234.56, // 页面加载时间
startE: 1642680000123, // 开始时间戳
onloadT: 1234.56, // onload 事件时间
tran: 15 // 传输次数
}
4. app: {}
app: {}
真实作用:
- 在 Chrome 应用环境中提供应用相关的 API
- 主要用于 Chrome 打包应用(Chrome Apps,现已弃用)
真实内容:
// 在 Chrome 应用中包含:
chrome.app = {
runtime: {
onLaunched: {...},
onRestarted: {...}
},
window: {
create: function() {...}
}
}
检测对抗原理
反爬虫检测可能的代码:
// 检测是否为真实 Chrome
if (typeof window.chrome === 'undefined') {
console.log('可能是自动化浏览器');
}
if (typeof chrome.loadTimes !== 'function') {
console.log('不是真实 Chrome');
}
// 更严格的检测
try {
const loadTimes = chrome.loadTimes();
if (!loadTimes || typeof loadTimes.requestTime === 'undefined') {
console.log('chrome.loadTimes 返回异常');
}
} catch (e) {
console.log('chrome.loadTimes 调用失败');
}
我们的对策:
window.chrome = {
runtime: {}, // 通过存在性检测
loadTimes: function() {}, // 通过函数类型检测
csi: function() {}, // 通过函数类型检测
app: {} // 通过存在性检测
};
更完善的实现
如果要更真实地伪装,可以这样写:
window.chrome = {
runtime: {
onConnect: {},
onMessage: {},
id: undefined
},
loadTimes: function() {
return {
requestTime: Date.now() / 1000 - Math.random(),
startLoadTime: Date.now() / 1000 - Math.random(),
commitLoadTime: Date.now() / 1000 - Math.random(),
finishDocumentLoadTime: Date.now() / 1000 - Math.random(),
finishLoadTime: Date.now() / 1000,
firstPaintTime: Date.now() / 1000 - Math.random(),
firstPaintAfterLoadTime: 0,
navigationType: "navigate"
};
},
csi: function() {
return {
pageT: Math.random() * 1000,
startE: Date.now() - Math.random() * 1000,
onloadT: Math.random() * 1000,
tran: Math.floor(Math.random() * 20)
};
},
app: {
isInstalled: false
}
};
不过对于大多数反检测场景,简单的空对象和空函数就足够了。
完整使用样例
async def run_local(self, proxy=None):
async with async_playwright() as p:
# 更完善的启动参数
launch_data = {
"headless": False,
"proxy": proxy,
"args": [
'--disable-blink-features=AutomationControlled',
'--disable-dev-shm-usage',
'--no-first-run',
'--no-default-browser-check',
'--disable-infobars',
'--disable-extensions',
'--disable-features=VizDisplayCompositor'
]
}
browser = await p.chromium.launch(**launch_data)
# 创建上下文时添加更多参数
context = await browser.new_context(
user_agent=user_agent,
viewport={'width': 1920, 'height': 1080},
screen={'width': 1920, 'height': 1080},
device_scale_factor=1,
is_mobile=False,
has_touch=False,
locale='en-US',
timezone_id='America/New_York',
permissions=['notifications']
)
timeout = 30
context.set_default_timeout(timeout * 1000)
page = await context.new_page()
await page.add_init_script("""
// 模拟真实的 chrome 对象
window.chrome = {
runtime: {},
loadTimes: function() {},
csi: function() {},
app: {}
};
""")
# 监听请求流
page.on('response', self.on_response)
url = 'https://gmgn.ai/meme/LXtw30LM?chain=sol'
# 先访问主页,模拟真实用户行为
await page.goto('https://gmgn.ai/')
await asyncio.sleep(2)
# 再访问目标地址
await page.goto(url)
await asyncio.sleep(3)
# 过反爬,如果不加就是被block的状态
await page.reload()
await asyncio.sleep(10)
await self.detect(page)
# 1小时后关闭浏览器
await page.evaluate("setTimeout(() => window.x = 5, 60 * 60 * 1000)")
await page.wait_for_function("() => window.x > 0", timeout=0)
This content originally appeared on DEV Community and was authored by drake