最近越来越喜欢自动化了,因为在量小的时候,自动化真的太舒服了,轻轻松松就解决了滑块。和道友讨论自动化的时候,道友竟然不知道 Chromium 有 –disable-blink-features=AutomationControlled 这个参数, 于是就写一下这个参数。

我是今年才正经搞了下自动化,问了 AI 怎么隐藏自动化特征,问到了这个参数,AI 真的越来越舒服了。问一下AI这个 –disable-blink-features=AutomationControlled 参数主要干啥的,可以知道这个是 Chromium 启动参数,它的核心作用是:禁用 Blink 渲染引擎中名为 AutomationControlled 的功能特性,从而隐藏浏览器正在被自动化工具(如 Selenium、Puppeteer 等)控制的痕迹,最主要的功能就是 隐藏 navigator.webdriver 属性。

也就是说加上这个参数后,可以解决一些基础检测。这就很舒服了,不用自己去写 hook 解决 navigator.webdriver 检测。

目前测试下来很好用,强的离谱,国内反爬目前还没遇到过不去的,国外倒是挺多过不去的,还需努力。

此时耳边响起嘲讽金角,理解金角,成为金角,超越金角。

联系作者

目标站点 aHR0cHM6Ly9jbG91ZC50ZW5jZW50LmNvbS9wcm9kdWN0L2NhcHRjaGE=

最近遇到一个滑块,打开一看是某讯滑块。看了下加密参数,有 pow_answer 和 collect , 主要难点是 collect,在 vmp 里绕来绕去生成的。本来想用浏览器补环境生成这个参数,但后面想想反正量不大,直接自动化好了。

找同事要了一份滑块自动化的代码,他之前也搞过某讯滑块,只是和这个版本不一样。代码是用Python 的 Playwright 库写的,就着代码让 AI 帮忙修改 XPath, 写等待某个元素出现的代码,让 AI 写等待某个请求出现的代码,最后让用 FastAPI 和 uvicorn 写了个 API 服务,就跑起来了。

量不大的情况下,滑块还是自动化来得舒服。

联系作者

目标站点 aHR0cHM6Ly93d3cudmFwdGNoYS5jb20vI2RlbW8=

之前写过一篇某Vaptcha手势验证逆向,能拿到 token, 但真的跑起来,还是会遇到很多问题。

比如能返回 token, 但拿着 token 去请求的时候,会返回参数校验失败。这是因为站点会拿着token去做二次校验,二次校验通不过就会提示参数校验失败,所以能获取到 token 并不代表真的成功。

比如Linux上 Canvas 生成服务容器跑通几次就一直获取不到 token, 本地 Mac 的 Canvas 生成
服务
容器却没问题。

比如 Windows 机器上的 Canvas 生成服务跑着跑着会变慢,本地 Mac 上的 Canvas 生成服务却一直没问题。

还有就是有的站点 50~55 分钟的时候会跑不动,因为版本发生了变化,得适配。

还有就是轨迹,看着两个轨迹加工的方法没什么大的区别,但真的跑起来效果却相差很大。

最后发现 Canvas 指纹其实随机也能行,不一定要浏览器生成的。但浏览器生成的好的一点是 token 没遇到过 00000000 的情况,随机生成会遇到。

很多时候,还是看请求量。请求量少的时候,怎么方便怎么来,但请求量多的时候,就真的考验人。

联系作者

目标站点: aHR0cHM6Ly9ldGF4LmNoaW5hdGF4Lmdvdi5jbi8=

听道友说某数又更新了,很多补环境方案不能用了,于是测试了下,以前基于 jsdom 的方案果然不能用了。之前就在当jsdom补环境被针对里写过,如果非得用 jsdom,就只能悄咪咪的用,别声张了。但你得时刻提防着被检测,因为 jsdom 可以检测的点真的是太多了。

继续测试了之前自己搞的补环境方案,还能过,又测试了下今年研究的浏览器补环境方案,也能过,这就稳妥了,有两个方案在手就还行,不会被打的措手不及。

花了点时间看了下这个版本,毕竟这是22年以来,针对补环境最大的一次更新了。可以看到 rs 安全人员花了很多精力在补环境检测上,几乎把市面上开源的方案都检测过去了, V佬可以的。其中 sdenv 是重灾区。毕竟有了sdenv, 新手都能过了,这可不行。

尝试去找找之前基于 jsdom 的补环境不能通过的原因,发现还是因为检测点太多了,_ast, _runScripts等等。不想继续找了,这补环境错在哪里真难找。还是浏览器补环境来的舒服,啥也不用改,无脑渲染完事。不用去想着怎么把浏览器特性在Node环境里实现,舒服得很。而且这浏览器补环境也就比自己写的补环境方案慢了50%,这性能能接受了。不过需要处理的是,当自动化被检测时,要怎么把这些检测点找出来。至少目前还不需要,目前能过。

遗憾的是,加密算法还是没有太多的改变,估计还是因为改加密算法涉及的东西太多了,阻力太大。如果加密算法大变,再加上这 jsvmp,算法方案就又有得搞了。

不管怎样,等这个版本大规模铺开,人均某数的时代就过去了。

联系作者

Node服务内存泄漏问题排查一文中,我们介绍了排查内存泄漏的方法。其实最初我教道友的并不是这个方法,所以道友没有找到内存泄漏原因,后来道友把他的代码发我排查之后,给他找到了解决办法。

后来为了写Node服务内存泄漏问题排查一文,更深入的研究了下这个内存泄漏,才知道主要是看 Retained Size (指的是一个对象被垃圾回收后,能实际释放的内存量),从上往下找就行,简单易懂。所以还是得多写文章才行啊。

写完Node服务内存泄漏问题排查一文后,用新的方法,一下子就找到了是console内存泄漏了。困惑的是最右边Retained Size对不上,没搞明白。

至于解决的办法也很简单,加上 global.console = undefined 就行了,代码如下

1
2
3
4
5
6
7
app.post("/generateCookies", (req, res) => {
let url = req.body.url;
let html = req.body.html;
let cookies = sdk.generateCookies(url, html);
global.console = undefined
res.json({ code: 0,'data': {'cookies': cookies}})
}

问题是为啥 console 会引发内存泄漏?看了代码后,可以知道它重定义了 console 里的方法,像log, info这些方法。对于这个问题有两种解决办法,一种是把这些重定义代码删掉,如果重定义代码在控制流或者vmp里,就不好找到重定义代码并删除。

另一种是不让它重定义这些方法。而不让它重定义这些方法,就可以用到Object.freeze 在JavaScript逆向时的妙用一文中介绍的 Object.freeze 方法。把console给冻结了,不让它修改,也就不会有内存泄漏。最终代码如下

1
2
3
4
5
6
7
Object.freeze(console)
app.post("/generateCookies", (req, res) => {
let url = req.body.url;
let html = req.body.html;
let cookies = sdk.generateCookies(url, html);
res.json({ code: 0,'data': {'cookies': cookies}})
}

后来发现,这个禁用console 是JavaScript Obfuscator 里的逻辑,剥离业务代码后,将测试代码放在https://github.com/dengshilong/js_reverse/tree/main/disable_console 这里,有兴趣的话可以去测试下 console 引发的内存泄漏。

联系作者

在使用Express搭建Node服务时,有一个非常值得关注的问题,那就是内存泄漏。内存泄漏会导致服务越来越慢,直至服务崩溃。

最近有个道友反馈,他的服务会内存泄漏,我的服务和他的功能一样,那么也一样会内存泄漏。于是我在Express服务里再加一个接口,用于dump出服务的内存,主要就是使用v8模块的writeHeapSnapshot函数,代码如下。

1
2
3
4
5
6
7
8
9
10
const v8 = require("v8")
app.get("/dump", (req, res) => {
try {
const fileName = v8.writeHeapSnapshot();
console_log(`Heap snapshot written to: ${fileName}`);
res.status(200).send(`Heap snapshot written);
} catch (err) {
res.status(500).send("Internal Server Error");
}
});

接下来就是请求服务接口,等它内存泄漏之后,调用这个dump内存接口,生成如Heap.20250710.170608.4721.0.001.heapsnapshot 这种文件。之后打开Chrome 开发者调试工具,在Memory里, 导入内存镜像,开始分析内存泄漏原因。

我们可以看到Retained Size(指的是一个对象被垃圾回收后,能实际释放的内存量) 几乎都在global里, 于是重点排查这里。

点开global这里,我们能看到,Retained Size都集中在fetch这个变量,于是我们在代码的最后添加global.fetch = undefined 来释放内存,最终代码如下。之后重启服务,继续测试,内存泄漏问题不再出现, 收工。

1
2
3
4
5
6
7
8
9
10
11
12
13
function executeJs(code, cookies, initParam) {
// 拼接新的 JS 代码
const newHeadJs = headJs + ";;;\n" + "window.param=" + JSON.stringify(initParam) + ";;;;\n" + code;
// 执行拼接的 JS 代码
eval(newHeadJs);
// 构造返回结果
const result = {
cookies: utils.changeToCookies(window.document.cookie)
};
// 清除全局 fetch
global.fetch = undefined;
return result;
}

联系作者

声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请在公众号 【静夜随想】 联系作者立即删除!

在看了土木佬的 某数反爬方案讨论 后,也想搞一个浏览器渲染的方案。之前同事研究cef的时候,就想让他搞一个,但他没继续研究了,这次正好补上。

https://github.com/dengshilong/browser_server/blob/main/node_playwright_server/simple_server.js 代码的基础上,继续问AI,

Playwright的配置参数里,有解决自动化检测的参数吗?AI又哼哧哼哧给了一大堆建议,其中建议有以下一条,加上之后进行测试。

1
2
3
4
5
6
7
browser = playwright.chromium.launch(
args=[
"--disable-blink-features=AutomationControlled", # 关键参数,隐藏自动化控制标识
"--no-sandbox",
"--disable-setuid-sandbox"
]
)

会遇到获取cookies时还未生成的情况,于是又让AI加上等待cookies不为空之后再返回结果功能,之后就可以测试了。

先试了加速乐,搞定。再试了下vmp + wasm里生成的某乎__zse_ck,搞定。再试了下某数,也搞定。

浏览器用来补环境是真舒服了, 就是生成速度比纯js补环境慢一些。

联系作者

声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请在公众号 【静夜随想】 联系作者立即删除!

用Playwright和Flask写了个浏览器渲染服务后,测试下来会有内存泄漏的现象,同时也发现它最终是调用 Node 的 Playwright库来实现浏览器渲染。那么我还不如直接用Node 的 Playwright 库来写一个渲染服务,这样应该速度会更快,也可能不会有内存泄漏。

于是又让AI吭哧吭哧的写了一段代码,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
const express = require('express');
const bodyParser = require('body-parser');
const {
chromium
} = require('playwright');
// 初始化 Express 应用
const app = express();
const PORT = process.env.PORT || 3000;
// 中间件
app.use(bodyParser.json({
limit: '100mb'
}));
app.use(bodyParser.urlencoded({
limit: '100mb',
extended: true
}));
// 保持一个持久的浏览器实例以提高性能
let browser;
// 初始化 playwright 浏览器
async function initializeBrowser() {
try {
browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
]
});
console.log('playwright browser initialized successfully');
} catch (error) {
console.error('Failed to initialize playwright browser:', error);
throw error;
}
}
// 创建延迟函数 - 强制等待指定毫秒数
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 将cookies数组转换为字典(键值对)
function convertCookiesToDict(cookies) {
const cookieDict = {};
if (Array.isArray(cookies)) {
cookies.forEach(cookie => {
// 使用cookie的name作为键,value作为值
if (cookie.name && cookie.value !== undefined) {
cookieDict[cookie.name] = cookie.value;
}
});
}
return cookieDict;
}
app.post('/get-cookies', async (req, res) => {
const {
url,
html,
user_agent
} = req.body;
if (!url || !html) {
return res.status(400).json({
error: '请同时提供url和html参数'
});
}
let page;
try {
// 配置页面选项,包括可选的userAgent
const pageOptions = {};
if (user_agent) {
pageOptions.userAgent = user_agent;
console.log(`使用自定义User-Agent: ${user_agent}`);
}
// 使用配置选项创建新页面
page = await browser.newPage(pageOptions);
// 设置路由拦截 - 拦截所有请求
await page.route('**/*', async (route, request) => {
// 获取当前请求的URL
const requestUrl = request.url();
// 检查当前请求是否匹配目标URL
if (requestUrl == url) {
// 对目标URL返回自定义HTML
await route.fulfill({
status: 200,
content_type: 'text/html',
body: html
});
console.log(`已处理目标URL请求: ${requestUrl}`);
} else {
// 其他所有请求都直接终止
await route.abort('aborted');
console.log(`已终止非目标URL请求: ${requestUrl}`);
}
});
// 导航到目标URL,此时会被我们的路由拦截处理
await page.goto(url, {
timeout: 30000
});
// 获取并返回Cookie
let cookies = await page.context().cookies();
cookies = convertCookiesToDict(cookies);
res.json({
success: true,
cookies: cookies,
});
} catch (error) {
console.error('处理请求时出错:', error);
res.status(500).json({
success: false,
error: error.message
});
} finally {
if (page) {
await page.close();
}
}
});
// 启动服务器并初始化浏览器
async function startServer() {
try {
await initializeBrowser();
app.listen(PORT, () => {
console.log(`JavaScript execution service running on port ${PORT}`);
console.log(`API endpoints:`);
console.log(`- POST /get-cookies: Execute JavaScript code`);
});
} catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
}
// 处理进程退出,确保浏览器正确关闭
process.on('SIGINT', async () => {
console.log('Shutting down...');
if (browser) {
await browser.close();
}
process.exit(0);
});
// 启动服务
startServer();

代码都放在Github仓库https://github.com/dengshilong/browser_server里了,有兴趣的可以看看。

联系作者

声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码。抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请在公众号 【静夜随想】 联系作者立即删除!
最近对浏览器自动化热情越来越高,于是想用浏览器来取代之前的补环境方案。借助越来越强的AI能力,写写提示词,让它写了一个渲染服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import json
import time
from playwright.sync_api import sync_playwright
from flask import Flask, jsonify, request
app = Flask(__name__)
playwright = sync_playwright().start()
browser = playwright.chromium.launch(
headless=True,
args=[
"--no-sandbox",
])
def generate_cookies(url, html, user_agent=None):
default_user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
if user_agent:
default_user_agent = user_agent
context = browser.new_context(
user_agent=default_user_agent,
)
page = context.new_page()
def handle_request(route, request):
print('hererer ', request.url, url)
if url in request.url:
# 返回自定义的 HTML 内容
route.fulfill(status=200, content_type='text/html', body=html)
else:
route.abort()
page.route("**/*", handler=handle_request)
page.goto(url)
# time.sleep(1)
cookies = context.cookies()
result = {}
for item in cookies:
result[item['name']] = item['value']
# 关闭页面
page.close()
return result
@app.route("/get-cookies", methods=["POST"])
def get_cookies():
data = request.json
url = data.get('url', '')
html = data.get('html', '')
user_agent = data.get('user_agent', '')
cookies = generate_cookies(url, html, user_agent=user_agent)
result = {}
result['cookies'] = cookies
return jsonify(result)
if __name__ == "__main__":
app.run(host="0.0.0.0", threaded=False, processes=1, port=3000, debug=False)

测试代码

1
2
3
4
5
6
7
8
9
10
import requests
headers = {
'Content-Type': 'application/json',
}
json_data = {
'url': 'https://www.baidu.com',
'html': '<html><head><title>Test Page</title></head><body><script>document.cookie = "test=123; path=/"; document.cookie = "user=testuser; path=/";</script></body></html>',
}
response = requests.post('http://localhost:3000/get-cookies', headers=headers, json=json_data)
print(response.status_code, response.text)

简简单单,有点意思。当然要运用到生产环境中,还需要解决很多问题,比如内存泄漏,性能等等。
代码都放在Github仓库https://github.com/dengshilong/browser_server里了,有兴趣的可以看看。

联系作者

前段时间,为了排查问题,在Hive表里各种查询,最后发现数据清洗真是太重要了,数据不给你清洗好,爬虫采集的再好也是白搭。

我们知道在数据仓库体系中一般有ODS、DWD、DWS 和 ADS 四层。数据清洗的时候,会先把爬虫采集的数据先存到ODS,之后经过处理到DWD,DWS,ADS,这其中每一步出问题都有可能导致数据丢失。一般情况,存入ODS不会出问题,因为这一步没有做什么特别操作,就把数据从HDFS文件写到ODS表中。

清洗容易挖坑的情况是表设计不好,比如ODS里会有来自很多个地方的数据,不给你加上数据来源字段,排查问题就很麻烦。比如不记录数据采集时间,也会造成排查问题麻烦。

更坑的是ODS到DWD,DWD到DWS或者DWS到ADS,因为清洗规则写的太复杂,把数据给清洗丢了,可能丢的不多,但长时间累积就很多了,正常应该保证一条都不丢。

还有一些很细的清洗情况。比如一个数值字段,之前都是正常阿拉伯计数,如1400000,后面突然改成科学计数法,如1.4E6,如果刚开始清洗时没考虑到这个情况,会导致这个字段清洗出错,此时得加一些告警才能发现问题。

还有一些情况是一条记录的发布时间字段,刚开始是有的,后面发布时间字段又变成空了,此时应该保留原来的发布时间。

数据清洗是个精细活,得慢慢来。

联系作者