要在 Python 终端中与类似豆包这样的 AI(比如调用 API 来实现交互)进行对话,一般可以按照以下思路来实现:
- 选择合适的 API:如果是要与豆包进行对话,你需要使用字节跳动提供的 API(目前暂未公开提供,这里以通用思路举例其他有 API 的情况)。对于其他类似有公开 API 的语言模型(如 OpenAI 的 GPT 等),要去注册获取 API 密钥,以便进行身份验证和调用服务。
- 安装必要的库:通常需要安装
requests
库(用于发送 HTTP 请求)等。在终端中可以使用pip install requests
来安装。 - 导入库并设置 API 密钥:在 Python 脚本中导入
requests
等相关库,并将 API 密钥设置为环境变量或者在代码中直接指定(但不建议直接在代码中明文写密钥,更好的方式是从环境变量读取)。 - 构建请求:根据 API 的文档要求,构建包含对话内容(如输入的问题)、模型参数(如最大生成长度、温度等)等信息的请求数据。
- 发送请求并处理响应:使用
requests
库发送 HTTP 请求到 API 端点,并接收返回的响应。对响应进行解析(通常是 JSON 格式),提取出 AI 生成的回答内容。 - 实现对话循环:使用一个循环结构,让用户可以不断输入问题,并持续与 AI 进行交互,直到用户输入特定的结束指令(如“退出”等)。
以下是一个简单的示例代码(以假设的类似 OpenAI 的 API 调用为例,实际调用豆包 API 会有所不同且需等待官方提供):
import requests
import os
# 设置 API 密钥,这里假设从环境变量中获取,你也可以直接指定(但不安全)
api_key = os.getenv("OPENAI_API_KEY")
# API 端点 URL
api_url = "https://api.openai.com/v1/chat/completions"
# 对话循环
while True:
user_input = input("你: ")
if user_input.lower() == "退出":
break
# 构建请求数据
data = {
"model": "gpt-3.5-turbo", # 假设的模型名称
"messages": [
{"role": "user", "content": user_input}
]
}
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
try:
response = requests.post(api_url, json=data, headers=headers)
response.raise_for_status() # 检查请求是否成功
result = response.json()
ai_response = result["choices"][0]["message"]["content"]
print("AI: ", ai_response)
except requests.exceptions.RequestException as e:
print(f"请求出错: {e}")
except KeyError:
print("无法解析响应数据")
以上代码实现了一个简单的在 Python 终端中与假设的 AI 进行对话的功能,你可以根据实际情况调整和完善,并且如果未来豆包有 API 提供,按照其具体文档来修改代码即可。
视频中终端的主要代码如下
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
import time
import json
import os
import sys
import select
import random
import string
import pyperclip
from selenium.webdriver.common.action_chains import ActionChains
# 根据操作系统导入不同的模块
if sys.platform != 'win32':
import termios
class DouBanChatCrawler:
def __init__(self):
# 获取项目根目录路径
root_dir = os.path.dirname(os.path.abspath(__file__))
# 配置Chrome选项
self.chrome_options = Options()
# 设置Chrome二进制文件路径
chrome_path = os.path.join(root_dir, "chrome-win64", "chrome.exe")
if not os.path.exists(chrome_path):
raise Exception(f"Chrome不存在: {chrome_path}")
self.chrome_options.binary_location = chrome_path
# 添加参数
self.chrome_options.add_argument('--disable-gpu')
self.chrome_options.add_argument('--disable-software-rasterizer')
self.chrome_options.add_argument('--disable-dev-shm-usage')
self.chrome_options.add_argument('--no-sandbox')
self.chrome_options.add_argument('--ignore-certificate-errors')
self.chrome_options.add_argument('--enable-unsafe-swiftshader')
self.chrome_options.add_argument('--disable-web-security')
self.chrome_options.add_argument('--disable-blink-features=AutomationControlled')
# 添加实验性选项
self.chrome_options.add_experimental_option('excludeSwitches', ['enable-automation', 'enable-logging'])
self.chrome_options.add_experimental_option('useAutomationExtension', False)
try:
# 设置ChromeDriver路径
chromedriver_path = os.path.join(root_dir, "chromedriver.exe")
if not os.path.exists(chromedriver_path):
raise Exception(f"ChromeDriver不存在: {chromedriver_path}")
service = Service(chromedriver_path)
# 初始化浏览器
self.driver = webdriver.Chrome(service=service, options=self.chrome_options)
# 修改 window.navigator.webdriver 标记
self.driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
# 最大化窗口
self.driver.maximize_window()
except Exception as e:
print(f"浏览器初始化失败: {str(e)}")
raise
def wait_for_network_idle(self, timeout=10):
"""等待网络请求完成"""
time.sleep(timeout) # 简单的等待方式
def get_network_requests(self):
"""获取网络请求信息,优化过滤逻辑"""
return self.driver.execute_script("""
let items = [];
try {
let entries = performance.getEntries() || [];
items = entries.filter(e => {
// 扩展过滤条件以包含更多相关接口
const apiPatterns = [
'/samantha/', // 主要的API接口
'/alice/', // 用户相关接口
'/chat/', // 聊天相关接口
'/message/', // 消息相关接口
'/monitor_browser/' // 监控相关接口
];
return apiPatterns.some(pattern => e.name.includes(pattern)) &&
e.entryType === 'resource' &&
(e.initiatorType === 'xmlhttprequest' || e.initiatorType === 'fetch');
}).map(e => ({
url: e.name,
method: e.initiatorType,
type: e.initiatorType,
duration: e.duration,
status: e.responseStatus,
timestamp: e.startTime,
size: e.transferSize || 0
}));
// 按时间戳排序
items.sort((a, b) => a.timestamp - b.timestamp);
} catch(err) {
console.error('Error:', err);
}
return items;
""")
def generate_chat_id(self):
"""生成5位数字和字母的唯一对话标识"""
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(5))
def send_message(self, message):
"""发送消息到豆包对话框"""
try:
# 检查消息内容
if message.strip() == "":
print("消息内容为空,已取消发送")
return False
# 生成唯一对话标识
chat_id = self.generate_chat_id()
# 在消息后添加完成提示请求和唯一标识
message_with_id = f"{message},回复完毕请告诉我本次回复完毕[{chat_id}]"
max_retries = 3
for attempt in range(max_retries):
try:
# 重置页面焦点
self.driver.execute_script("document.activeElement.blur();")
time.sleep(0.1)
# 重新定位输入框
input_box = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "textarea.semi-input-textarea"))
)
# 确保输入框可交互
WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "textarea.semi-input-textarea"))
)
# 重置输入框状态
self.driver.execute_script("""
var input = arguments[0];
input.value = '';
input.blur();
setTimeout(() => {
input.focus();
input.click();
}, 100);
""", input_box)
time.sleep(0.2)
# 再次检查输入框状态
input_box = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "textarea.semi-input-textarea"))
)
# 直接输入消息
input_box.clear()
input_box.send_keys(message_with_id)
time.sleep(0.1)
# 发送消息
input_box.send_keys(Keys.ENTER)
time.sleep(0.2)
# 验证消息是否发送成功
try:
current_value = input_box.get_attribute('value')
if not current_value.strip():
print(f"已发送消息: {message}")
# 发送成功后重置输入框状态
self.driver.execute_script("""
var input = arguments[0];
input.value = '';
input.blur();
""", input_box)
return chat_id
except:
# 如果无法获取值,可能是因为消息已发送
print(f"已发送消息: {message}")
return chat_id
if attempt < max_retries - 1:
print(f"重试发送消息 (尝试 {attempt + 2}/{max_retries})")
time.sleep(0.5)
continue
except Exception as e:
if attempt < max_retries - 1:
print(f"发送失败,正在重试 ({attempt + 2}/{max_retries})")
time.sleep(0.5)
continue
else:
raise e
return None
except Exception as e:
print(f"发送消息失败: {str(e)}")
import traceback
print(traceback.format_exc())
return None
def wait_for_response(self, timeout=30, chat_id=None):
"""等待并获取豆包的回复"""
try:
start_time = time.time()
last_check_time = time.time()
# 获取初始状态
initial_elements = self.driver.find_elements(
By.CSS_SELECTOR,
"div.paragraph-JOTKXA, li, pre.container-_HmLba"
)
initial_count = len(initial_elements)
last_response_text = ""
print(f"等待新回复... (当前元素数: {initial_count})")
print("按回车键可以中断等待")
import threading
interrupt_wait = threading.Event()
def check_input():
try:
# 使用 select 来检测输入,避免阻塞
while not interrupt_wait.is_set():
if sys.platform == 'win32':
import msvcrt
if msvcrt.kbhit():
if msvcrt.getch() in [b'\r', b'\n']:
interrupt_wait.set()
break
else:
if select.select([sys.stdin], [], [], 0.1)[0]:
if sys.stdin.read(1) in ['\n', '\r']:
interrupt_wait.set()
break
time.sleep(0.1)
except:
pass
# 启动输入监听线程
input_thread = threading.Thread(target=check_input)
input_thread.daemon = True
input_thread.start()
# 等待回复的主循环
while time.time() - start_time < timeout and not interrupt_wait.is_set():
try:
current_time = time.time()
# 获取所有回复元素
current_elements = self.driver.find_elements(
By.CSS_SELECTOR,
"div[class*='paragraph-'], li, pre[class*='container-']"
)
# 获取当前所有回复文本
current_response_text = ""
code_block_complete = False
response_complete = False
for element in current_elements[initial_count:]:
if element.tag_name == 'pre':
try:
# 查找代码块中的复制按钮
copy_button = element.find_element(
By.CSS_SELECTOR,
"button[data-testid='message_action_copy']"
)
# 点击复制按钮
copy_button.click()
time.sleep(0.1)
# 从剪贴板获取代码
text = pyperclip.paste()
if text.strip():
# 检查代码块中是否包含对话标识,如果有则移除
if chat_id:
text = text.replace(f"[{chat_id}]", "")
current_response_text += text.strip() + "\n"
# 检查代码块是否完整
if "def" in text and "if __name__ == \"__main__\":" in text:
code_block_complete = True
except Exception as e:
# 如果复制按钮不可用,回退到原来的方法
code_element = element.find_element(By.CSS_SELECTOR, "code")
text = code_element.text
if text.strip():
current_response_text += text.strip() + "\n"
else:
text = element.text.strip()
if text:
# 检查是否包含完成标记
if chat_id and f"回复完毕[{chat_id}]" in text:
response_complete = True
# 移除标记和标识
if chat_id:
text = text.replace(f"回复完毕[{chat_id}]", "")
text = text.replace(f"[{chat_id}]", "")
text = text.replace(",本次回复完毕", "").replace("本次回复完毕", "")
text = text.strip()
if text:
current_response_text += text + "\n"
# 检查是否有新内容
if current_response_text != last_response_text:
last_check_time = current_time
last_response_text = current_response_text
# 检查回复是否完成
if chat_id:
response_complete = f"回复完毕[{chat_id}]" in current_response_text
else:
response_complete = "回复完毕" in current_response_text
# 如果有回复内容,且满足完成条件
if current_response_text and (
response_complete or
(code_block_complete and current_time - last_check_time > 3) or
(current_time - last_check_time > 5)
):
# 处理回复文本
response_parts = []
for element in current_elements[initial_count:]:
if element.tag_name == 'pre':
try:
# 查找并点击复制按钮
copy_button = element.find_element(
By.CSS_SELECTOR,
"button[data-testid='message_action_copy']"
)
copy_button.click()
time.sleep(0.1)
# 从剪贴板获取代码并清理
text = pyperclip.paste()
if text.strip():
# 移除代码中的标识
if chat_id:
text = text.replace(f"[{chat_id}]", "")
response_parts.append("\n```python\n" + text.strip() + "\n```\n")
except:
# 如果复制按钮不可用,回退到原来的方法
code_element = element.find_element(By.CSS_SELECTOR, "code")
text = code_element.text
if text.strip():
response_parts.append("\n```python\n" + text.strip() + "\n```\n")
else:
text = element.text.strip()
if text:
# 移除所有标记和标识
if chat_id:
text = text.replace(f"回复完毕[{chat_id}]", "")
text = text.replace(f"[{chat_id}]", "")
text = text.replace(",本次回复完毕", "").replace("本次回复完毕", "")
text = text.strip()
if text:
if element.tag_name == 'li':
response_parts.append(f"• {text}")
else:
response_parts.append(text)
if response_parts:
complete_response = "\n".join(response_parts)
if response_complete:
print(f"收到完整回复 (元素数: {len(current_elements) - initial_count})")
elif code_block_complete:
print("代码块已完成")
else:
print("回复似乎已完成")
print("回复的内容是" + complete_response)
print("\nCTRL+K开启新对话")
# 模拟按下 CTRL+K
try:
actions = ActionChains(self.driver)
actions.key_down(Keys.CONTROL)
actions.send_keys('k')
actions.key_up(Keys.CONTROL)
actions.perform()
print("已模拟按下 CTRL+K,开启新对话")
time.sleep(1) # 等待新对话加载
except Exception as e:
print(f"模拟 CTRL+K 失败: {str(e)}")
return complete_response
time.sleep(0.2)
except Exception as e:
print(f"获取回复时出错: {str(e)}")
time.sleep(0.2)
continue
# 等待输入线程完成
interrupt_wait.set() # 确保输入线程退出
input_thread.join(0.1)
# 清空输入缓冲区
if sys.platform == 'win32':
import msvcrt
while msvcrt.kbhit():
msvcrt.getch()
elif hasattr(sys, 'stdin'): # 对于其他系统
try:
import termios
termios.tcflush(sys.stdin, termios.TCIOFLUSH)
except (ImportError, AttributeError):
pass # 如果无法使用 termios,就跳过清理
if interrupt_wait.is_set():
greetings = [
"您好,我正在思考中...",
"请稍等片刻,我在整理思路...",
"让我想一想哦...",
"我在认真思考您的问题...",
"稍等片刻,马上为您解答...",
"我需要一点时间来思考...",
"正在为您准备答案...",
"请给我一点时间组织语言...",
"我在努力思考中...",
"让我好好想想这个问题..."
]
return random.choice(greetings)
return "等待回复超时"
except Exception as e:
print(f"等待回复失败: {str(e)}")
import traceback
print(traceback.format_exc())
return None
def chat_session(self):
"""启动交互式聊天会话"""
print("\n=== 豆包聊天会话已启动 ===")
print("输入 'quit' 退出会话")
# 用于跟踪上一次响应的状态
last_response_time = 0
while True:
try:
print("\nCTRL+K开启新对话")
# 模拟按下 CTRL+K
try:
actions = ActionChains(self.driver)
actions.key_down(Keys.CONTROL)
actions.send_keys('k')
actions.key_up(Keys.CONTROL)
actions.perform()
print("已模拟按下 CTRL+K,开启新对话")
time.sleep(1) # 等待新对话加载
except Exception as e:
print(f"模拟 CTRL+K 失败: {str(e)}")
# 确保在获取新输入前有足够的冷却时间
current_time = time.time()
if current_time - last_response_time < 0.5: # 减少冷却时间
time.sleep(0.5)
# 使用 input() 获取用户输入
raw_message = input("\n你: ")
# 处理消息内容
message = raw_message.strip()
# 检查是否为退出命令
if message.lower() == 'quit':
break
# 检查消息是否为空或太短
if not message:
print("请输入有效的消息内容")
continue
if len(message) < 2: # 允许单个字符的消息,但给出提示
print("提示:消息太短可能无法获得好的回复,是否继续?(y/n)")
confirm = input().strip().lower()
if confirm != 'y':
continue
# 清除可能存在的旧输入框内容
try:
input_box = self.driver.find_element(By.CSS_SELECTOR, "textarea.semi-input-textarea")
if input_box:
self.driver.execute_script("arguments[0].value = '';", input_box)
except:
pass
# 发送消息并获取对话标识
chat_id = self.send_message(message)
if chat_id:
print("\n豆包正在回复...")
response = self.wait_for_response(chat_id=chat_id)
if response:
print(f"\n豆包: {response}")
last_response_time = time.time()
time.sleep(0.5)
else:
print("未能获取到回复")
last_response_time = time.time()
except Exception as e:
print(f"聊天过程出错: {str(e)}")
continue
def save_login_state(self):
"""保存完整的登录状态到 cookie.txt"""
try:
# 获取 cookies
cookies = self.driver.get_cookies()
# 获取 localStorage
local_storage = self.driver.execute_script("""
let items = {};
try {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
items[key] = localStorage.getItem(key);
}
} catch(err) {
console.error('Error:', err);
}
return items;
""")
# 保存登录状态
login_state = {
'cookies': cookies,
'localStorage': local_storage,
'timestamp': time.time()
}
# 保存到 cookie.txt
with open('cookie.txt', 'w', encoding='utf-8') as f:
json.dump(login_state, f, ensure_ascii=False, indent=2)
print("已保存登录状态到 cookie.txt")
return True
except Exception as e:
print(f"保存登录状态失败: {str(e)}")
return False
def load_login_state(self):
"""从 cookie.txt 加载登录状态"""
try:
if not os.path.exists('cookie.txt'):
print("未找到登录状态文件,需要首次登录")
return False
with open('cookie.txt', 'r', encoding='utf-8') as f:
login_state = json.load(f)
# # 检查登录状态是否过期(7天)
# if time.time() - login_state.get('timestamp', 0) > 7 * 24 * 3600:
# print("登录状态已过期,需要重新登录")
# return False
# 先访问网站
self.driver.get("https://www.doubao.com")
time.sleep(1)
# 添加 cookies
for cookie in login_state.get('cookies', []):
try:
# 移除可能导致问题的属性
if 'expiry' in cookie:
del cookie['expiry']
self.driver.add_cookie(cookie)
except Exception as e:
print(f"添加cookie失败: {str(e)}")
continue
# # 添加 localStorage
# for key, value in login_state.get('localStorage', {}).items():
# try:
# self.driver.execute_script(f"window.localStorage.setItem('{key}', '{value}')")
# except Exception as e:
# print(f"添加localStorage失败: {str(e)}")
# continue
print("已从 cookie.txt 加载登录状态")
return True
except Exception as e:
print(f"加载登录状态失败: {str(e)}")
return False
def check_login_status(self):
"""检查登录状态"""
try:
# 等待页面加载
WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
# 检查是否存在登录相关元素
login_indicators = self.driver.execute_script("""
return {
'hasTextarea': !!document.querySelector('textarea.semi-input-textarea'),
'hasLoginButton': !!document.querySelector('button[data-testid="login-button"]'),
'hasUserInfo': !!document.querySelector('div[data-testid="user-info"]')
}
""")
return login_indicators.get('hasTextarea', False) and not login_indicators.get('hasLoginButton', True)
except Exception as e:
print(f"检查登录状态失败: {str(e)}")
return False
def get_chat_info(self):
try:
print("正在打开豆包网站...")
# 尝试从 cookie.txt 加载登录状态
if self.load_login_state():
print("已加载登录状态,尝试自动登录...")
self.driver.get("https://www.doubao.com/chat/")
time.sleep(3)
# 检查登录状态
if self.check_login_status():
print("自动登录成功")
else:
print("自动登录失败,需要重新登录")
print("\n请在打开的浏览器中完成以下操作:")
print("1. 登录豆包账号")
print("2. 进入任意对话")
print("3. 等待页面加载完成")
print("4. 按回车键继续...")
input()
# 保存新的登录状态到 cookie.txt
self.save_login_state()
else:
self.driver.get("https://www.doubao.com/chat/")
print("\n首次登录,请完成以下操作:")
print("1. 登录豆包账号")
print("2. 进入任意对话")
print("3. 等待页面加载完成")
print("4. 按回车键继续...")
input()
# 保存登录状态到 cookie.txt
self.save_login_state()
# 启动聊天会话
self.chat_session()
try:
# 获取cookies
cookies = self.driver.get_cookies()
cookie_dict = {cookie['name']: cookie['value'] for cookie in cookies}
# 获取localStorage
local_storage = self.driver.execute_script("""
let items = {};
try {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
items[key] = localStorage.getItem(key);
}
} catch(err) {
console.error('Error:', err);
}
return items;
""")
# 获取网络请求
network_requests = self.get_network_requests()
# 保存信息到文件
data = {
'cookies': cookie_dict,
'local_storage': local_storage,
'network_requests': network_requests
}
with open('chat_info.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print("\n成功!所有信息已保存到 chat_info.json 文件中")
print(f"发现的网络请求数量: {len(network_requests)}")
print("\n主要API接口:")
# 按类型分组显示API接口
api_groups = {
'Samantha APIs': [req for req in network_requests if '/samantha/' in req['url']],
'Alice APIs': [req for req in network_requests if '/alice/' in req['url']],
'Chat APIs': [req for req in network_requests if '/chat/' in req['url']],
'Message APIs': [req for req in network_requests if '/message/' in req['url']],
'Monitor APIs': [req for req in network_requests if '/monitor/' in req['url']]
}
for group_name, requests in api_groups.items():
if requests:
print(f"\n{group_name}:")
for req in requests:
print(f"- {req['method'].upper()}: {req['url']}")
print(f" Duration: {req['duration']:.2f}ms, Size: {req['size']} bytes")
except Exception as e:
print(f"收集信息时发生错误: {str(e)}")
except Exception as e:
print(f"发生错误: {str(e)}")
finally:
input("\n按回车键关闭浏览器...")
try:
self.driver.quit()
except:
pass
def main():
try:
crawler = DouBanChatCrawler()
crawler.get_chat_info()
except Exception as e:
print(f"程序运行失败: {str(e)}")
input("按回车键退出...")
if __name__ == "__main__":
main()
实际的视频演示
使用python终端和豆包AI对话|豆包API