Files
ai_mip/adspower_client.py

1091 lines
41 KiB
Python
Raw Normal View History

2026-01-13 18:59:26 +08:00
import requests
from typing import Dict, Optional
from loguru import logger
from playwright.sync_api import sync_playwright, Browser, Page
from config import Config
import json
class AdsPowerClient:
"""AdsPower API客户端 - 集成 Playwright CDP
参考官方示例: localAPI-main/py-examples/example-start-profile.py
"""
# 大麦IP代理配置
DAMAI_API_URL = "https://api2.damaiip.com/index.php?s=/front/user/getIPlist&xsn=2912cb2b22d3b7ae724f045012790479&osn=TC_NO176707424165606223&tiqu=1"
DAMAI_USER = "69538fdef04e1"
DAMAI_PASSWORD = "63v0kQBr2yJXnjf"
def __init__(self, api_url: str = None, user_id: str = None, api_key: str = None):
self.api_url = api_url or Config.ADSPOWER_API_URL
self.user_id = user_id or Config.ADSPOWER_USER_ID
self.api_key = api_key or Config.ADSPOWER_API_KEY
self.playwright = None
self.browser = None
# 调试信息
if self.api_key:
logger.debug(f"AdsPowerClient 初始化 - API Key: {self.api_key[:8]}... (len={len(self.api_key)})")
else:
logger.debug("AdsPowerClient 初始化 - 未配置 API Key")
def update_profile_proxy(self, profile_id: str, proxy_id: str) -> bool:
"""
更新 Profile 的代理配置
使用 AdsPower API v2
Args:
profile_id: Profile ID
proxy_id: 代理ID
Returns:
是否成功
"""
try:
url = f"{self.api_url}/api/v2/browser-profile/update"
# 准备请求头
headers = {
'Content-Type': 'application/json'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
# 准备请求体
payload = {
"profile_id": profile_id,
"proxy_id": proxy_id
}
logger.info("\n" + "="*70)
logger.info("更新 Profile 代理")
logger.info("="*70)
logger.info(f"URL: {url}")
logger.info(f"Method: POST")
logger.info(f"Payload:")
logger.info(json.dumps(payload, indent=2, ensure_ascii=False))
response = requests.post(url, json=payload, headers=headers, timeout=30)
logger.info("\n" + "-"*70)
logger.info("响应信息")
logger.info("-"*70)
logger.info(f"Status Code: {response.status_code}")
try:
response_json = response.json()
logger.info("Response Body:")
logger.info(json.dumps(response_json, indent=2, ensure_ascii=False))
except:
logger.info(f"Response Body (Raw):")
logger.info(response.text)
logger.info("="*70 + "\n")
result = response_json if 'response_json' in locals() else response.json()
if result.get('code') == 0:
logger.success(f"成功更新 Profile 代理: {profile_id} -> Proxy {proxy_id}")
# 验证代理是否真正更新
import time
time.sleep(1) # 等待配置生效
verify_result = self.get_profile_info(profile_id)
if verify_result:
actual_proxy_id = verify_result.get('data', {}).get('proxy_id', '')
logger.info(f"验证代理配置 - 期望: {proxy_id}, 实际: {actual_proxy_id}")
if actual_proxy_id == proxy_id:
logger.success("✅ 代理配置验证通过")
return True
else:
logger.warning(f"❌ 代理配置验证失败 - Profile中的proxy_id不匹配")
return False
else:
logger.warning("无法验证代理配置但API返回成功")
return True
else:
logger.error(f"更新 Profile 代理失败: {result.get('msg')}")
return False
except Exception as e:
logger.error(f"更新 Profile 代理异常: {str(e)}")
return False
def get_profile_info(self, profile_id: str) -> Optional[Dict]:
"""
查询 Profile 详细信息
使用 AdsPower API v2
Args:
profile_id: Profile ID
Returns:
Profile 信息
"""
try:
url = f"{self.api_url}/api/v2/browser-profile/list"
headers = {
'Content-Type': 'application/json'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
payload = {
"profile_id": profile_id
}
response = requests.post(url, json=payload, headers=headers, timeout=30)
result = response.json()
if result.get('code') == 0:
profiles = result.get('data', {}).get('list', [])
if profiles:
return {'code': 0, 'data': profiles[0]}
return None
except Exception as e:
logger.error(f"查询 Profile 信息异常: {str(e)}")
return None
def update_profile_proxy_v1(self, profile_id: str, proxy_config: Dict) -> bool:
"""
使用 API v1 方式直接更新 Profile 的代理配置
传入完整的 user_proxy_config 对象
Args:
profile_id: Profile ID (user_id)
proxy_config: 代理配置字典格式
{
"proxy_type": "http",
"proxy_host": "112.83.100.233",
"proxy_port": "11059",
"proxy_user": "69538fdef04e1", # 可选
"proxy_password": "63v0kQBr2yJXnjf", # 可选
"proxy_soft": "other"
}
Returns:
是否成功
"""
try:
url = f"{self.api_url}/api/v1/user/update"
# 准备请求头
headers = {
'Content-Type': 'application/json'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
# 构建 user_proxy_config
proxy_port = proxy_config.get("proxy_port")
if isinstance(proxy_port, str):
proxy_port = int(proxy_port)
user_proxy_config = {
"proxy_soft": proxy_config.get("proxy_soft", "other"),
"proxy_type": proxy_config.get("proxy_type", "http"),
"proxy_host": proxy_config["proxy_host"],
"proxy_port": proxy_port # 使用整数类型
}
# 如果有认证信息,添加到配置中
if proxy_config.get("proxy_user") and proxy_config.get("proxy_password"):
user_proxy_config["proxy_user"] = proxy_config["proxy_user"]
user_proxy_config["proxy_password"] = proxy_config["proxy_password"]
logger.info("使用认证代理模式")
else:
logger.info("使用白名单代理模式(无认证)")
# 准备请求体
payload = {
"user_id": profile_id,
"user_proxy_config": user_proxy_config
}
logger.info("\n" + "="*70)
logger.info("更新 Profile 代理 (API v1)")
logger.info("="*70)
logger.info(f"URL: {url}")
logger.info(f"Method: POST")
logger.info(f"Headers:")
for k, v in headers.items():
if k == 'Authorization' and v:
logger.info(f" {k}: Bearer {v.split()[-1][:8]}...")
else:
logger.info(f" {k}: {v}")
logger.info(f"Payload:")
logger.info(json.dumps(payload, indent=2, ensure_ascii=False))
response = requests.post(url, json=payload, headers=headers, timeout=30)
logger.info("\n" + "-"*70)
logger.info("响应信息")
logger.info("-"*70)
logger.info(f"Status Code: {response.status_code}")
try:
response_json = response.json()
logger.info("Response Body:")
logger.info(json.dumps(response_json, indent=2, ensure_ascii=False))
except:
logger.info(f"Response Body (Raw):")
logger.info(response.text)
logger.info("="*70 + "\n")
result = response_json if 'response_json' in locals() else response.json()
if result.get('code') == 0:
logger.success(f"成功更新 Profile 代理 (API v1): {profile_id}")
# 验证代理是否真正更新
import time
time.sleep(1) # 等待配置生效
verify_result = self.get_profile_info(profile_id)
if verify_result:
actual_proxy = verify_result.get('data', {}).get('user_proxy_config', {})
actual_host = actual_proxy.get('proxy_host', '')
actual_port = str(actual_proxy.get('proxy_port', ''))
expected_host = proxy_config['proxy_host']
expected_port = str(proxy_port)
logger.info(f"验证代理配置:")
logger.info(f" 期望: {expected_host}:{expected_port}")
logger.info(f" 实际: {actual_host}:{actual_port}")
if actual_host == expected_host and actual_port == expected_port:
logger.success("✅ 代理配置验证通过")
return True
else:
logger.warning(f"❌ 代理配置验证失败 - 地址不匹配")
return False
else:
logger.warning("无法验证代理配置但API返回成功")
return True
else:
logger.error(f"更新 Profile 代理失败 (API v1): {result.get('msg')}")
return False
except Exception as e:
logger.error(f"更新 Profile 代理异常 (API v1): {str(e)}")
return False
def update_profile_proxy_v2_direct(self, profile_id: str, proxy_config: Dict) -> bool:
"""
使用 API v2 方式直接传入 user_proxy_config 更新代理
根据官方文档API v2 支持 proxy_id user_proxy_config 两种方式
Args:
profile_id: Profile ID
proxy_config: 代理配置字典格式
{
"proxy_type": "http",
"proxy_host": "112.83.100.233",
"proxy_port": "11059", # 可以是字符串或整数
"proxy_user": "69538fdef04e1", # 可选
"proxy_password": "63v0kQBr2yJXnjf", # 可选
"proxy_soft": "other"
}
Returns:
是否成功
"""
try:
url = f"{self.api_url}/api/v2/browser-profile/update"
# 准备请求头
headers = {
'Content-Type': 'application/json'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
# 解析端口(确保是整数类型)
proxy_port = proxy_config.get("proxy_port")
if isinstance(proxy_port, str):
proxy_port = int(proxy_port)
# 构建 user_proxy_config
user_proxy_config = {
"proxy_soft": proxy_config.get("proxy_soft", "other"),
"proxy_type": proxy_config.get("proxy_type", "http"),
"proxy_host": proxy_config["proxy_host"],
"proxy_port": proxy_port # 使用整数类型!
}
# 如果有认证信息,添加到配置中
if proxy_config.get("proxy_user") and proxy_config.get("proxy_password"):
user_proxy_config["proxy_user"] = proxy_config["proxy_user"]
user_proxy_config["proxy_password"] = proxy_config["proxy_password"]
logger.info("使用认证代理模式")
else:
logger.info("使用白名单代理模式(无认证)")
# 准备请求体注意API v2 使用 profile_id 而不是 user_id
payload = {
"profile_id": profile_id,
"user_proxy_config": user_proxy_config
}
logger.info("\n" + "="*70)
logger.info("更新 Profile 代理 (API v2 - Direct user_proxy_config)")
logger.info("="*70)
logger.info(f"URL: {url}")
logger.info(f"Method: POST")
logger.info(f"Headers:")
for k, v in headers.items():
if k == 'Authorization' and v:
logger.info(f" {k}: Bearer {v.split()[-1][:8]}...")
else:
logger.info(f" {k}: {v}")
logger.info(f"Payload:")
logger.info(json.dumps(payload, indent=2, ensure_ascii=False))
logger.info(f"注意: proxy_port类型为 {type(user_proxy_config['proxy_port']).__name__}")
response = requests.post(url, json=payload, headers=headers, timeout=30)
logger.info("\n" + "-"*70)
logger.info("响应信息")
logger.info("-"*70)
logger.info(f"Status Code: {response.status_code}")
try:
response_json = response.json()
logger.info("Response Body:")
logger.info(json.dumps(response_json, indent=2, ensure_ascii=False))
except:
logger.info(f"Response Body (Raw):")
logger.info(response.text)
logger.info("="*70 + "\n")
result = response_json if 'response_json' in locals() else response.json()
if result.get('code') == 0:
logger.success(f"成功更新 Profile 代理 (API v2 Direct): {profile_id}")
# 验证代理是否真正更新
import time
time.sleep(1)
verify_result = self.get_profile_info(profile_id)
if verify_result:
actual_proxy = verify_result.get('data', {}).get('user_proxy_config', {})
actual_host = actual_proxy.get('proxy_host', '')
actual_port = str(actual_proxy.get('proxy_port', ''))
expected_host = proxy_config['proxy_host']
expected_port = str(proxy_port)
logger.info(f"验证代理配置:")
logger.info(f" 期望: {expected_host}:{expected_port}")
logger.info(f" 实际: {actual_host}:{actual_port}")
if actual_host == expected_host and actual_port == expected_port:
logger.success("✅ 代理配置验证通过")
return True
else:
logger.warning(f"❌ 代理配置验证失败 - 地址不匹配")
return False
else:
logger.warning("无法验证代理配置但API返回成功")
return True
else:
logger.error(f"更新 Profile 代理失败 (API v2 Direct): {result.get('msg')}")
return False
except Exception as e:
logger.error(f"更新 Profile 代理异常 (API v2 Direct): {str(e)}")
return False
def setup_proxy_and_start(self, user_id: str = None, use_proxy: bool = True) -> Optional[Dict]:
"""
设置代理并启动浏览器
Args:
user_id: AdsPower用户ID
use_proxy: 是否使用代理
Returns:
浏览器信息
"""
target_user_id = user_id or self.user_id
if use_proxy:
# 1. 获取大麦IP代理
proxy_info = self.get_damai_proxy()
if not proxy_info:
logger.warning("获取代理失败,将不使用代理启动浏览器")
return self.start_browser(user_id=target_user_id)
# 2. 创建 AdsPower 代理
proxy_config = {
"type": "http",
"host": proxy_info["host"],
"port": proxy_info["port"],
"user": self.DAMAI_USER,
"password": self.DAMAI_PASSWORD,
"ipchecker": "ip2location",
"remark": "Damai Auto Proxy"
}
proxy_id = self.create_proxy(proxy_config)
if not proxy_id:
logger.warning("创建代理失败,将不使用代理启动浏览器")
return self.start_browser(user_id=target_user_id)
# 3. 更新 Profile 使用新代理
if not self.update_profile_proxy(target_user_id, proxy_id):
logger.warning("更新 Profile 代理失败,将不使用代理启动浏览器")
return self.start_browser(user_id=target_user_id)
# 4. 启动浏览器
return self.start_browser(user_id=target_user_id)
def start_browser(self, user_id: str = None) -> Optional[Dict]:
"""
启动浏览器
使用 AdsPower API v2
Args:
user_id: AdsPower用户IDProfile ID
Returns:
包含浏览器连接信息的字典
"""
target_user_id = user_id or self.user_id
if not target_user_id:
logger.error("未设置 AdsPower User ID")
return None
try:
# 使用 AdsPower API v2
url = f"{self.api_url}/api/v2/browser-profile/start"
# 准备请求头
headers = {
'Content-Type': 'application/json'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
# 准备请求体
payload = {
"profile_id": target_user_id,
"launch_args": [], # 可以根据需要添加启动参数
"headless": "0",
"last_opened_tabs": "1",
"proxy_detection": "1",
"password_filling": "0",
"password_saving": "0",
"cdp_mask": "1",
"delete_cache": "0",
"device_scale": "1"
}
# 打印请求信息
logger.info("\n" + "="*70)
logger.info("📤 启动浏览器请求 (API v2)")
logger.info("="*70)
logger.info(f"URL: {url}")
logger.info(f"Method: POST")
logger.info(f"Headers:")
for key, value in headers.items():
if key == 'Authorization' and 'Bearer' in value:
display_value = f"Bearer {value.split(' ')[1][:8]}..."
else:
display_value = value
logger.info(f" {key}: {display_value}")
logger.info(f"Payload:")
logger.info(f" {json.dumps(payload, indent=2, ensure_ascii=False)}")
response = requests.post(url, json=payload, headers=headers, timeout=30)
# 打印响应信息
logger.info("\n" + "-"*70)
logger.info("📥 响应信息")
logger.info("-"*70)
logger.info(f"Status Code: {response.status_code}")
logger.info(f"Headers:")
for key, value in response.headers.items():
logger.info(f" {key}: {value}")
# 格式化 JSON 响应
try:
response_json = response.json()
logger.info(f"Response Body:")
logger.info(json.dumps(response_json, indent=2, ensure_ascii=False))
except:
logger.info(f"Response Body (Raw):")
logger.info(response.text)
logger.info("="*70 + "\n")
result = response_json if 'response_json' in locals() else response.json()
if result.get('code') == 0:
logger.info(f"成功启动浏览器User ID: {target_user_id}")
# v2 API 的响应结构可能不同,需要根据实际返回调整
if 'data' in result and 'ws' in result['data']:
logger.info(f"Selenium: {result['data']['ws'].get('selenium', 'N/A')}")
logger.info(f"Puppeteer: {result['data']['ws'].get('puppeteer', 'N/A')}")
return result
else:
logger.error(f"启动浏览器失败: {result.get('msg')}")
return None
except Exception as e:
logger.error(f"启动浏览器异常: {str(e)}")
return None
def connect_browser(self, browser_info: Dict) -> Optional[Browser]:
"""
通过 CDP 连接到 AdsPower 浏览器
Args:
browser_info: start_browser 返回的浏览器信息
Returns:
Playwright Browser 实例
"""
try:
# 获取 CDP WebSocket 端点
ws_endpoint = browser_info['data']['ws']['puppeteer']
# 初始化 Playwright
if not self.playwright:
self.playwright = sync_playwright().start()
# 通过 CDP 连接到浏览器
self.browser = self.playwright.chromium.connect_over_cdp(ws_endpoint)
logger.info("成功通过 CDP 连接到 AdsPower 浏览器")
return self.browser
except Exception as e:
logger.error(f"CDP 连接失败: {str(e)}")
return None
def get_page(self, browser: Browser) -> Optional[Page]:
"""
获取或创建浏览器页面
Args:
browser: Browser 实例
Returns:
Page 实例
"""
try:
# 获取默认上下文
if browser.contexts:
context = browser.contexts[0]
else:
context = browser.new_context()
# 获取或创建页面
if context.pages:
page = context.pages[0]
else:
page = context.new_page()
logger.info("成功获取浏览器页面")
return page
except Exception as e:
logger.error(f"获取页面失败: {str(e)}")
return None
def stop_browser(self, user_id: str = None) -> bool:
"""
停止浏览器
使用 AdsPower API v2
Args:
user_id: AdsPower用户ID
Returns:
是否成功停止
"""
target_user_id = user_id or self.user_id
if not target_user_id:
logger.error("未设置 AdsPower User ID")
return False
# 清理 Playwright 资源
try:
if self.browser:
self.browser.close()
self.browser = None
if self.playwright:
self.playwright.stop()
self.playwright = None
except Exception as e:
logger.warning(f"清理 Playwright 资源异常: {str(e)}")
try:
# 使用 AdsPower API v2
url = f"{self.api_url}/api/v2/browser-profile/stop"
# 准备请求头
headers = {
'Content-Type': 'application/json'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
# 准备请求体
payload = {
"profile_id": target_user_id
}
logger.info("\n" + "="*70)
logger.info("🛑 停止浏览器请求 (API v2)")
logger.info("="*70)
logger.info(f"URL: {url}")
logger.info(f"Method: POST")
logger.info(f"Headers:")
for key, value in headers.items():
if key == 'Authorization' and 'Bearer' in value:
display_value = f"Bearer {value.split(' ')[1][:8]}..."
else:
display_value = value
logger.info(f" {key}: {display_value}")
logger.info(f"Payload:")
logger.info(f" {json.dumps(payload, indent=2, ensure_ascii=False)}")
response = requests.post(url, json=payload, headers=headers, timeout=30)
logger.info("\n" + "-"*70)
logger.info("📥 响应信息")
logger.info("-"*70)
logger.info(f"Status Code: {response.status_code}")
# 格式化 JSON 响应
try:
response_json = response.json()
logger.info(f"Response Body:")
logger.info(json.dumps(response_json, indent=2, ensure_ascii=False))
except:
logger.info(f"Response Body (Raw):")
logger.info(response.text)
logger.info("="*70 + "\n")
result = response_json if 'response_json' in locals() else response.json()
if result.get('code') == 0:
logger.info(f"成功停止浏览器User ID: {target_user_id}")
return True
else:
logger.error(f"停止浏览器失败: {result.get('msg')}")
return False
except Exception as e:
logger.error(f"停止浏览器异常: {str(e)}")
return False
def get_damai_proxy(self) -> Optional[Dict]:
"""
从大麦IP代理池获取代理
Returns:
包含host和port的字典失败返回None
"""
try:
logger.info("\n" + "="*70)
logger.info("获取大麦IP代理")
logger.info("="*70)
logger.info(f"URL: {self.DAMAI_API_URL}")
# 禁用代理直接连接大麦API
proxies = {
'http': None,
'https': None
}
response = requests.get(self.DAMAI_API_URL, proxies=proxies, timeout=10)
logger.info(f"Status Code: {response.status_code}")
logger.info(f"Response: {response.text}")
if response.status_code == 200 and response.text:
# 解析返回的IP:端口格式
proxy_str = response.text.strip()
if ':' in proxy_str:
host, port = proxy_str.split(':', 1)
logger.success(f"成功获取代理: {host}:{port}")
logger.info("="*70 + "\n")
return {
"host": host,
"port": port
}
else:
logger.error(f"代理格式错误: {proxy_str}")
return None
else:
logger.error(f"获取代理失败: {response.text}")
return None
except Exception as e:
logger.error(f"获取大麦代理异常: {str(e)}")
return None
def create_proxy(self, proxy_config: Dict) -> Optional[str]:
"""
AdsPower 中创建代理
使用 AdsPower API v2
Args:
proxy_config: 代理配置字典
Returns:
代理ID失败返回None
"""
try:
url = f"{self.api_url}/api/v2/proxy-list/create"
# 准备请求头
headers = {
'Content-Type': 'application/json'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
# 准备请求体(数组格式)
payload = [proxy_config]
logger.info("\n" + "="*70)
logger.info("创建 AdsPower 代理")
logger.info("="*70)
logger.info(f"URL: {url}")
logger.info(f"Method: POST")
logger.info(f"Payload:")
logger.info(json.dumps(payload, indent=2, ensure_ascii=False))
response = requests.post(url, json=payload, headers=headers, timeout=30)
logger.info("\n" + "-"*70)
logger.info("响应信息")
logger.info("-"*70)
logger.info(f"Status Code: {response.status_code}")
try:
response_json = response.json()
logger.info("Response Body:")
logger.info(json.dumps(response_json, indent=2, ensure_ascii=False))
except:
logger.info(f"Response Body (Raw):")
logger.info(response.text)
logger.info("="*70 + "\n")
result = response_json if 'response_json' in locals() else response.json()
if result.get('code') == 0:
# 获取创建的代理ID
proxy_data = result.get('data', {})
proxy_ids = proxy_data.get('proxy_id', [])
if proxy_ids and len(proxy_ids) > 0:
proxy_id = proxy_ids[0]
logger.success(f"成功创建代理ID: {proxy_id}")
return proxy_id
else:
logger.error("创建代理成功但未返回ID")
return None
else:
logger.error(f"创建代理失败: {result.get('msg')}")
return None
except Exception as e:
logger.error(f"创建代理异常: {str(e)}")
return None
def list_proxies(self, proxy_ids: list = None, page: int = 1, limit: int = 100) -> Optional[Dict]:
"""
查询代理列表
使用 AdsPower API v2
Args:
proxy_ids: 代理ID列表可选
page: 页码默认1
limit: 每页数量默认100
Returns:
代理列表数据
"""
try:
url = f"{self.api_url}/api/v2/proxy-list/list"
# 准备请求头
headers = {
'Content-Type': 'application/json'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
# 准备请求体
payload = {
"page": str(page),
"limit": str(limit)
}
if proxy_ids:
payload["proxy_id"] = proxy_ids
logger.info("\n" + "="*70)
logger.info("查询代理列表 (API v2)")
logger.info("="*70)
logger.info(f"URL: {url}")
logger.info(f"Method: POST")
logger.info(f"Headers:")
for k, v in headers.items():
if k == 'Authorization' and v:
logger.info(f" {k}: Bearer {v.split()[-1][:8]}...")
else:
logger.info(f" {k}: {v}")
logger.info(f"Payload:")
logger.info(f" {json.dumps(payload, indent=2, ensure_ascii=False)}")
response = requests.post(url, json=payload, headers=headers, timeout=30)
logger.info("\n" + "-"*70)
logger.info("响应信息")
logger.info("-"*70)
logger.info(f"Status Code: {response.status_code}")
logger.info("Response Body:")
try:
response_json = response.json()
logger.info(json.dumps(response_json, indent=2, ensure_ascii=False))
except:
logger.info(response.text)
logger.info("="*70 + "\n")
result = response_json if 'response_json' in locals() else response.json()
if result.get('code') == 0:
proxy_list = result.get('data', {}).get('list', [])
logger.success(f"查询成功,找到 {len(proxy_list)} 个代理")
return result
else:
logger.error(f"查询代理列表失败: {result.get('msg')}")
return None
except Exception as e:
logger.error(f"查询代理列表异常: {str(e)}")
return None
def check_browser_status(self, user_id: str = None) -> Optional[Dict]:
"""
检查浏览器状态
Args:
user_id: AdsPower用户ID
Returns:
浏览器状态信息
"""
target_user_id = user_id or self.user_id
if not target_user_id:
logger.error("未设置 AdsPower User ID")
return None
try:
url = f"{self.api_url}/api/v1/browser/active?user_id={target_user_id}"
# 准备请求头
headers = {}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
response = requests.get(url, headers=headers, timeout=10)
result = response.json()
return result
except Exception as e:
logger.error(f"检查浏览器状态异常: {str(e)}")
return None
def list_profiles(self, group_id: str = None, page: int = 1, page_size: int = 100) -> Optional[Dict]:
"""
查询 Profile 列表
使用 AdsPower API v2
Args:
group_id: 组ID可选
page: 页码
page_size: 每页数量
Returns:
Profile 列表信息
"""
try:
url = f"{self.api_url}/api/v2/browser-profile/list"
# 准备请求头
headers = {
'Content-Type': 'application/json'
}
if self.api_key:
headers['Authorization'] = f'Bearer {self.api_key}'
# 准备请求体
payload = {
"page": page,
"page_size": page_size
}
if group_id:
payload["group_id"] = group_id
# 打印请求信息
logger.info("\n" + "="*70)
logger.info("📋 查询 Profile 列表 (API v2)")
logger.info("="*70)
logger.info(f"URL: {url}")
logger.info(f"Method: POST")
logger.info(f"Headers:")
for key, value in headers.items():
if key == 'Authorization' and 'Bearer' in value:
display_value = f"Bearer {value.split(' ')[1][:8]}..."
else:
display_value = value
logger.info(f" {key}: {display_value}")
logger.info(f"Payload:")
logger.info(f" {json.dumps(payload, indent=2, ensure_ascii=False)}")
response = requests.post(url, json=payload, headers=headers, timeout=30)
# 打印响应信息
logger.info("\n" + "-"*70)
logger.info("📥 响应信息")
logger.info("-"*70)
logger.info(f"Status Code: {response.status_code}")
# 格式化 JSON 响应(限制长度)
try:
response_json = response.json()
# 如果响应太长,只显示关键部分
if len(str(response_json)) > 2000:
logger.info(f"Response Body (摘要):")
summary = {
"code": response_json.get("code"),
"msg": response_json.get("msg"),
"data": {
"total": response_json.get("data", {}).get("total"),
"list_count": len(response_json.get("data", {}).get("list", []))
}
}
logger.info(json.dumps(summary, indent=2, ensure_ascii=False))
else:
logger.info(f"Response Body:")
logger.info(json.dumps(response_json, indent=2, ensure_ascii=False))
except:
logger.info(f"Response Body (Raw):")
logger.info(response.text[:500])
logger.info("="*70 + "\n")
result = response_json if 'response_json' in locals() else response.json()
if result.get('code') == 0:
profiles = result.get('data', {}).get('list', [])
logger.success(f"查询成功,找到 {len(profiles)} 个 Profile")
# 显示 Profile 信息
if profiles:
logger.info("\nProfile 列表:")
for idx, profile in enumerate(profiles[:10], 1): # 只显示前10个
profile_id = profile.get('profile_id', 'N/A')
profile_name = profile.get('name', 'N/A')
group_name = profile.get('group_name', 'N/A')
logger.info(f" {idx}. ID: {profile_id} | 名称: {profile_name} | 组: {group_name}")
if len(profiles) > 10:
logger.info(f" ...还有 {len(profiles) - 10} 个 Profile")
return result
else:
logger.error(f"查询 Profile 失败: {result.get('msg')}")
return None
except Exception as e:
logger.error(f"查询 Profile 异常: {str(e)}")
return None
def __del__(self):
"""析构函数,确保资源清理"""
try:
if self.browser:
self.browser.close()
if self.playwright:
self.playwright.stop()
except:
pass
if __name__ == "__main__":
"""快速测试 AdsPower 连接"""
logger.info("开始测试 AdsPower API v2 连接")
logger.info("="*50)
# 创建客户端
client = AdsPowerClient()
# 步骤 1: 先查询 Profile 列表
logger.info("\n步骤 1: 查询 Profile 列表")
result = client.list_profiles()
if not result:
logger.error("❌ 查询 Profile 失败,测试终止")
exit(1)
profiles = result.get('data', {}).get('list', [])
if not profiles:
logger.error("❌ 没有可用的 Profile请先在 AdsPower 中创建 Profile")
exit(1)
# 使用第一个 Profile
first_profile = profiles[0]
profile_id = first_profile.get('profile_id')
profile_name = first_profile.get('name', 'N/A')
logger.success(f"\n将使用 Profile: {profile_name} (ID: {profile_id})")
# 提示用户是否继续
choice = input(f"\n是否启动此 Profile (y/n): ").strip().lower()
if choice != 'y':
logger.info("用户取消操作")
exit(0)
# 问是否使用代理
use_proxy_choice = input(f"\n是否使用大麦IP代理 (y/n): ").strip().lower()
use_proxy = use_proxy_choice == 'y'
# 步骤 2: 设置代理并启动浏览器
logger.info(f"\n步骤 2: {'[使用代理] ' if use_proxy else ''}启动浏览器 {profile_id}")
result = client.setup_proxy_and_start(user_id=profile_id, use_proxy=use_proxy)
if result:
logger.success("✅ 浏览器启动成功!")
# 等待用户确认
input("\n按 Enter 键停止浏览器...")
# 步骤 3: 停止浏览器
logger.info(f"\n步骤 3: 停止浏览器 {profile_id}")
if client.stop_browser(user_id=profile_id):
logger.success("✅ 浏览器停止成功!")
else:
logger.error("❌ 浏览器停止失败")
else:
logger.error("❌ 浏览器启动失败")
logger.info("="*50)
logger.info("测试完成")