chromedp自动操作deepseek
文章目录
Go测试代码
package test
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"slices"
"testing"
"time"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/network"
"github.com/chromedp/chromedp"
)
// userAgent 浏览器的UA头
var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0"
// fingerprintJSStr 随机浏览器指纹信息
var fingerprintJSStr = `
(() => {
try {
// === 随机生成函数(轻度安全随机) ===
const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
const randFloat = (min, max) => Math.random() * (max - min) + min;
// === 固定信息 ===
const FIXED_LANGS = ['zh-CN', 'zh'];
const FIXED_LANG = 'zh-CN';
const FIXED_PLATFORM = 'Win32';
// === 随机信息 ===
const RAND_HW_CONCURRENCY = pick([4, 6, 8, 12]);
const RAND_DEVICE_MEMORY = pick([4, 8, 12, 16]);
const RAND_GL_VENDOR = pick(['Intel Inc.', 'NVIDIA Corporation', 'ATI Technologies Inc.']);
const RAND_GL_RENDERER = pick([
'Intel(R) UHD Graphics 630',
'NVIDIA GeForce GTX ' + randInt(1050, 3090),
'AMD Radeon RX ' + randInt(5600, 7900)
]);
// 1) navigator.webdriver
Object.defineProperty(navigator, 'webdriver', { get: () => undefined, configurable: true });
// 2) 语言固定为中文
Object.defineProperty(navigator, 'languages', { get: () => FIXED_LANGS.slice(), configurable: true });
Object.defineProperty(navigator, 'language', { get: () => FIXED_LANG, configurable: true });
// 3) plugins & mimeTypes
const fakePlugins = [
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
{ name: 'Widevine Content Decryption Module', filename: 'widevinecdm', description: 'Widevine CDM' }
];
const pluginArray = { length: fakePlugins.length };
fakePlugins.forEach((p, i) => pluginArray[i] = p);
pluginArray.item = (i) => pluginArray[i];
pluginArray.namedItem = (name) => fakePlugins.find(p => p.name === name) || null;
Object.defineProperty(navigator, 'plugins', { get: () => pluginArray, configurable: true });
Object.defineProperty(navigator, 'mimeTypes', { get: () => ({ length: 0, item: () => null }), configurable: true });
// 4) permissions.query 修复
const originalPermissionsQuery = window.navigator.permissions && window.navigator.permissions.query;
if (originalPermissionsQuery) {
const nativeQuery = originalPermissionsQuery.bind(window.navigator.permissions);
window.navigator.permissions.query = (parameters) => {
if (parameters && parameters.name === 'notifications') {
return Promise.resolve({ state: Notification.permission });
}
return nativeQuery(parameters);
};
}
// 5) 硬件信息随机 + 平台固定
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => RAND_HW_CONCURRENCY, configurable: true });
Object.defineProperty(navigator, 'deviceMemory', { get: () => RAND_DEVICE_MEMORY, configurable: true });
Object.defineProperty(navigator, 'platform', { get: () => FIXED_PLATFORM, configurable: true });
// 6) chrome.runtime 伪造
window.chrome = window.chrome || {};
window.chrome.runtime = window.chrome.runtime || {};
// 7) WebGL vendor/renderer 随机
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 37445) return RAND_GL_VENDOR;
if (parameter === 37446) return RAND_GL_RENDERER;
return getParameter.call(this, parameter);
};
// 8) Canvas 指纹扰动(轻度随机)
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function() {
try {
const ctx = this.getContext('2d');
if (ctx) {
const alpha = randFloat(0.00005, 0.001);
ctx.fillStyle = "rgba(0,0,0," + alpha + ")";
ctx.fillRect(randInt(0, 2), randInt(0, 2), 1, 1);
}
} catch (e) {}
return origToDataURL.apply(this, arguments);
};
const origGetImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function() {
try {
const img = origGetImageData.apply(this, arguments);
if (img && img.data && img.data.length >= 4) {
const delta = randInt(-1, 1);
img.data[3] = Math.max(0, Math.min(255, img.data[3] + delta));
}
return img;
} catch (e) {
return origGetImageData.apply(this, arguments);
}
};
// 9) Battery
if (navigator.getBattery) {
const level = parseFloat((randFloat(0.6, 1.0)).toFixed(2));
navigator.getBattery = () => Promise.resolve({
charging: true,
chargingTime: randInt(0, 10),
dischargingTime: Infinity,
level: level
});
}
// 10) 函数字符串隐藏 webdriver
const re = /function\s+get\s+webdriver|webdriver|HeadlessChrome/ig;
const origToString = Function.prototype.toString;
Function.prototype.toString = function() {
try {
const s = origToString.apply(this, arguments);
if (re.test(s)) return 'function () { [native code] }';
return s;
} catch (e) {
return origToString.apply(this, arguments);
}
};
// 11) iframe navigator 一致化
const descriptor = Object.getOwnPropertyDescriptor(navigator.__proto__, 'userAgent') || {};
if (!descriptor || !descriptor.get) {
Object.defineProperty(navigator, 'userAgent', {
get: () => navigator.userAgent.replace('HeadlessChrome', 'Chrome'),
configurable: true
});
}
// 12) 删除 webdriver
delete navigator.__proto__.webdriver;
// === 诊断输出(调试时可查看) ===
window.__fingerprint_profile = {
lang: FIXED_LANG,
platform: FIXED_PLATFORM,
hwConcurrency: RAND_HW_CONCURRENCY,
deviceMemory: RAND_DEVICE_MEMORY,
glVendor: RAND_GL_VENDOR,
glRenderer: RAND_GL_RENDERER
};
} catch (e) {
// ignore
}
})();
`
func TestDoubao(t *testing.T) {
ctx := context.Background()
chromedpOptions := []chromedp.ExecAllocatorOption{
chromedp.Flag("headless", false), // debug使用 false
chromedp.Flag("disable-hang-monitor", true), // 禁用页面无响应检测
// 核心:禁用自动化指示器
chromedp.Flag("enable-automation", false),
chromedp.Flag("useAutomationExtension", false),
chromedp.Flag("disable-blink-features", "AutomationControlled"),
// 辅助:增强伪装
chromedp.UserAgent(userAgent),
chromedp.Flag("disable-web-security", false),
chromedp.Flag("ignore-certificate-errors", false),
// 随机化窗口大小,避免所有实例千篇一律
chromedp.WindowSize(1920+rand.Intn(200), 1080+rand.Intn(200)),
}
//初始化参数,先传一个空的数据
chromedpOptions = append(chromedp.DefaultExecAllocatorOptions[:], chromedpOptions...)
//allocatorContext, cancel1 := chromedp.NewExecAllocator(ctx, chromedpOptions...)
allocatorContext, cancel1 := chromedp.NewRemoteAllocator(ctx, "ws://10.0.0.131:8222")
defer cancel1()
chromeCtx, cancel2 := chromedp.NewContext(allocatorContext)
defer cancel2()
// 执行一个空task, 用提前创建Chrome实例
//chromedp.Run(chromeCtx, make([]chromedp.Action, 0, 1)...)
//创建一个上下文,超时时间为300s
chromeCtx, cancel3 := context.WithTimeout(chromeCtx, time.Duration(300)*time.Second)
defer cancel3()
// 监听deepseek的sse请求
sseStatus := make(chan int, 1)
var content string
var requestIDs []network.RequestID
// 添加监听器
chromedp.ListenTarget(chromeCtx, func(ev interface{}) {
switch ev := ev.(type) {
case *network.EventRequestWillBeSent: //SSE请求发送
uri, _ := url.Parse(ev.Request.URL)
if uri.Path != "/samantha/chat/completion" {
break
}
if ev.Request.Method != "POST" {
break
}
requestIDs = append(requestIDs, ev.RequestID)
case *network.EventLoadingFinished: //SSE事件结束
i := slices.Index(requestIDs, ev.RequestID)
if i < 0 {
break
}
requestIDs = slices.Delete(requestIDs, i, i+1)
fc := chromedp.FromContext(chromeCtx)
ctx := cdp.WithExecutor(chromeCtx, fc.Target)
go func() { //使用协程,避免错误
bs, err := network.GetResponseBody(ev.RequestID).Do(ctx)
if err != nil {
return
}
//获取SSE的整体返回值
content = string(bs)
sseStatus <- 1
}()
}
})
screenshotLogin := make([]byte, 0)
screenshotSession := make([]byte, 0)
// 每次重启容器,作为匿名用户访问豆包
resp, err := http.Post("http://10.0.0.131:2375/containers/chromedp-doubao/restart", "application/x-www-form-urlencoded", nil)
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
time.Sleep(3 * time.Second)
err = chromedp.Run(chromeCtx, chromedp.Tasks{
//chromedp.Evaluate(`Object.defineProperty(navigator, 'webdriver', {get: () => undefined})`, nil),
chromedp.ActionFunc(func(ctx context.Context) error {
_, err := page.AddScriptToEvaluateOnNewDocument(fingerprintJSStr).Do(ctx)
return err
}),
// 启用网络事件监听,这是关键一步
network.Enable(),
// 覆盖 navigator.userAgent 等
emulation.SetUserAgentOverride(userAgent),
// 可同时设置额外请求头
network.SetExtraHTTPHeaders(network.Headers{"Accept-Language": "zh-CN,zh;q=0.9", "User-Agent": userAgent}),
//指定分辨率的窗口
emulation.SetDeviceMetricsOverride(1920+rand.Intn(200), 1080+rand.Intn(200), 1.0, false).
WithScreenOrientation(&emulation.ScreenOrientation{
Type: emulation.OrientationTypePortraitPrimary,
Angle: 0,
}),
// 导航到登录页面
chromedp.Navigate("https://www.doubao.com/chat/"),
//等待三秒
chromedp.Sleep(3 * time.Second),
/*
// 判断页面是否是已经登录
chromedp.ActionFunc(func(ctx context.Context) error {
// 检查是否在登录页面(通过查找登录相关元素).使用相同的chromeCtx
var exists bool
err := chromedp.Run(chromeCtx, chromedp.EvaluateAsDevTools(`document.evaluate('//*[text()=\"密码登录\"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue !== null`, &exists))
if err != nil {
return err
}
if !exists {
return nil
}
// 如果找到登录元素,执行登录流程.使用相同的chromeCtx
return chromedp.Run(chromeCtx, chromedp.Tasks{
// 点击密码登录标签页
chromedp.WaitReady(`//*[text()='密码登录']`, chromedp.BySearch),
chromedp.Click(`//*[text()='密码登录']`, chromedp.BySearch),
//输入账号
chromedp.WaitReady(`//*[@placeholder='请输入手机号/邮箱地址']`, chromedp.BySearch),
chromedp.SendKeys(`//*[@placeholder='请输入手机号/邮箱地址']`, "账号", chromedp.BySearch),
//输入密码
chromedp.WaitReady(`//*[@placeholder='请输入密码']`, chromedp.BySearch),
chromedp.SendKeys(`//*[@placeholder='请输入密码']`, "密码", chromedp.BySearch),
//点击登录
chromedp.WaitReady(`//*[text()='登录']`, chromedp.BySearch),
chromedp.Click(`//*[text()='登录']`, chromedp.BySearch),
// 等待登录完成
chromedp.Sleep(3 * time.Second),
})
}),
*/
// 登录截屏
chromedp.CaptureScreenshot(&screenshotLogin),
//1.输入问题
chromedp.SendKeys(`textarea[data-testid="chat_input_input"]`, "今天的热点新闻", chromedp.ByQuery),
//等待三秒
chromedp.Sleep(3 * time.Second),
// 2. 等待按钮处于可点击状态 (aria-disabled="false" 且没有 disabled 属性)
chromedp.WaitVisible(`button#flow-end-msg-send[aria-disabled="false"]:not([disabled])`, chromedp.ByQuery),
// 3. 点击按钮
chromedp.Click(`button#flow-end-msg-send`, chromedp.ByQuery),
// 等待大模型回复,最多等待3分钟
chromedp.ActionFunc(func(ctx context.Context) error {
// 设置超时时间为180秒
select {
case <-sseStatus: // 成功状态
return nil
case <-time.After(180 * time.Second):
return nil
}
}),
// 会话截屏
chromedp.CaptureScreenshot(&screenshotSession),
})
fmt.Println(err)
if len(screenshotLogin) > 0 {
os.Remove("screenshotLogin.png")
os.WriteFile("screenshotLogin.png", screenshotLogin, 0644)
}
if len(screenshotSession) > 0 {
os.Remove("screenshotSession.png")
os.WriteFile("screenshotSession.png", screenshotSession, 0644)
}
fmt.Println(content)
}
docker compose
services:
chromedp:
image: chromedp/headless-shell:142.0.7420.3
container_name: chromedp
restart: unless-stopped
command:
- "--headless=new"
- "--window-size=1920,1080"
- "--no-sandbox"
- "--disable-setuid-sandbox"
- "--disable-background-timer-throttling"
- "--disable-backgrounding-occluded-windows"
- "--disable-renderer-backgrounding"
- "--disable-features=VizDisplayCompositor"
- "--enable-unsafe-swiftshader"
- "--disable-hang-monitor"
- "--disable-automation"
- "--disable-extensions"
- "--disable-blink-features=AutomationControlled"
- "--disable-web-security=false"
- "--ignore-certificate-errors=false"
shm_size: 4g
#extra_hosts:
# - "www.doubao.com:10.0.0.131"
environment:
TZ: Asia/Shanghai
LANG: zh_CN.UTF-8
LC_ALL: zh_CN.UTF-8
ports:
- "8222:9222"
deploy:
resources:
limits:
cpus: '4'
memory: 16G
reservations:
cpus: '4'
memory: 8G
中文乱码
### 豆包的 Content-Type text/event-stream 没有 charset=utf-8,造成乱码.(CDP会严格按照协议执行解析,无法修改Content-Type)
### 指定 www.doubao.com 的Nginx解析IP, 自签https证书
#extra_hosts:
# - "www.doubao.com:10.0.0.131"
location /samantha/chat/completion {
## 设置UTF-8编码
add_header Content-Type "text/event-stream; charset=utf-8";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 取消缓冲
proxy_buffering off;
# 关闭代理缓存
proxy_cache off;
# 代理到真实的豆包服务
proxy_pass https://www.doubao.com;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 取消缓冲
proxy_buffering off;
# 关闭代理缓存
proxy_cache off;
# 代理到真实的豆包服务
proxy_pass https://www.doubao.com;
}
文章作者
上次更新 2025-10-16