Сильный ИИ. Элира2. Сохранение диалогов ChatGPT

от автора

Всем привет!

В этой статье я расскажу о том, как можно сохранить диалоги в ChatGPT вместе с изображениями из чата!

Несмотря на уже довольно долгое существование ChatGPT в нем до сих пор отсутствует функция сохранения диалогов с изображениями. Более того, при наличии изображений в диалоге, ссылка для сохранения вообще не создается. Именно поэтому я решил создать скрипт, который позволит сохранять диалоги напрямую через веб-интерфейс ChatGPT, используя Selenium.

Подготовка

Для начала необходимо установить необходимые зависимости. Создаем файл requirements.txt с содержимым:

undetected-chromedriver
selenium
requests

Затем устанавливаем зависимости с помощью команды (в консоли):
pip install -r requirements.txt

Далее создаем файл credentials.txt, в который записываем логин и пароль для автоматического входа в ChatGPT:

myemail@example.com
mypassword

Создание скрипта

Начнем с импорта необходимых библиотек и чтения учетных данных:

import os import time import requests from undetected_chromedriver import Chrome, ChromeOptions from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC  # Чтение учетных данных из файла with open('credentials.txt', 'r', encoding='utf-8') as f:     credentials = f.read().splitlines() email = credentials[0] password = credentials[1]

Для автоматизации браузера (и обхода cloudflare) используем библу undetected_chromedriver.
Настраиваем его и открываем страницу для входа в ChatGPT:

# Настройка undetected_chromedriver options = ChromeOptions() options.add_argument('--no-sandbox') options.add_argument('--disable-dev-shm-usage')  with Chrome(options=options) as driver:     driver.get('https://chat.openai.com/')     wait = WebDriverWait(driver, 10)     login_button = wait.until(EC.element_to_be_clickable((By.XPATH, '/html/body/div[1]/div[1]/div[2]/div[1]/div/div/button[1]/div/span')))     login_button.click()      # Ввод логина     email_field = wait.until(EC.presence_of_element_located((By.XPATH, '/html/body/div/div/main/section/div[2]/div[1]/input')))     email_field.send_keys(email)     next_button = driver.find_element(By.XPATH, '/html/body/div/div/main/section/div[2]/button')     next_button.click()      # Ввод пароля     password_field = wait.until(EC.presence_of_element_located((By.XPATH, '/html/body/div[1]/main/section/div/div/div/form/div[1]/div/div[2]/div/input')))     password_field.send_keys(password)     login_button = driver.find_element(By.XPATH, '/html/body/div[1]/main/section/div/div/div/form/div[2]/button')     login_button.click()      input("Выберите нужный диалог и нажмите Enter для продолжения...")

Создаем папки для сохранения данных и функции для сохранения текста и скачивания файлов:

# Создание папок для сохранения данных if not os.path.exists('Диалог'):     os.makedirs('Диалог/Вложения')  # Функция для сохранения текста сообщения def save_text(message, filename):     with open(filename, 'w', encoding='utf-8') as f:         f.write(message)  # Функция для скачивания файла по URL def download_file(url, dest_path):     response = requests.get(url)     with open(dest_path, 'wb') as f:         f.write(response.content)

Далее создаем основной HTML-файл для сохранения диалогов. В HTML включены стили, сделанные в цветах веб-интерфейса ChatGPT (для крОсоты):

# Создание основного HTML файла html_content = """ <!DOCTYPE html> <html lang="ru"> <head>     <meta charset="UTF-8">     <title>Диалог</title>     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css">     <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>     <script>         hljs.highlightAll();          function copyCode(button) {             const code = button.parentElement.querySelector('code').innerText;             navigator.clipboard.writeText(code);             button.innerText = 'Скопировано';             setTimeout(() => { button.innerText = 'Копировать'; }, 2000);         }          function showModal(imageSrc) {             const modal = document.getElementById('imageModal');             const modalImg = document.getElementById('modalImg');             modal.style.display = 'block';             modalImg.src = imageSrc;         }          function closeModal() {             const modal = document.getElementById('imageModal');             modal.style.display = 'none';         }     </script>     <style>         body { font-family: Arial, sans-serif; line-height: 1.6; margin: 20px; background-color: #212121; color: #d1d5da; }         .user-message { color: #ececec !important; background-color: #2f2f2f !important; padding: 10px; border-radius: 8px; margin-bottom: 10px; }         .ai-message { color: #ececec !important; background-color: #212121; padding: 10px; border-radius: 8px; margin-bottom: 10px; }         .message-container { margin-bottom: 20px; }         .code-block { background-color: #000000 !important; border-left: 4px solid #00aaff; padding: 10px; position: relative; border-radius: 5px; }         .code-block code { display: block;  background-color: #000000 !important; color: #f8f8f2; }         .copy-button { position: absolute; top: 10px; right: 10px; padding: 5px; background-color: #444654; border-radius: 8px; border: 1px solid #ccc; cursor: pointer; }         .image-container { width: 200px; display: inline-block; margin: 10px; }         .image-container img { width: 100%; cursor: pointer; border-radius: 5px; }         .image-modal { display: none; position: fixed; z-index: 1000; padding-top: 100px; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.9); }         .image-modal-content { margin: auto; display: block; width: 80%; max-width: 700px; border-radius: 5px; }         .image-modal-content, .image-modal { animation-name: zoom; animation-duration: 0.6s; }         @keyframes zoom { from { transform: scale(0) } to { transform: scale(1) } }         .close { position: absolute; top: 50px; right: 35px; color: #f1f1f1; font-size: 40px; font-weight: bold; transition: 0.3s; }         .close:hover, .close:focus { color: #bbb; text-decoration: none; cursor: pointer; }     </style> </head> <body> <div id="imageModal" class="image-modal">     <span class="close" onclick="closeModal()">&times;</span>     <img class="image-modal-content" id="modalImg"> </div> """ 

Теперь приступаем к сбору всех сообщений и их обработке. Будем определять «авторство» сообщения и обрабатывать текст, код и изображения отдельно:

# Инициализация счетчиков total_messages = 0 user_messages = 0 ai_messages = 0 skipped_msg = 0  # Сбор всех сообщений messages = driver.find_elements(By.XPATH, '//div[contains(@class, "group") and contains(@class, "flex") and not(contains(@class, "h-48"))]') for idx, message in enumerate(messages):     if not message.text.strip():         skipped_msg += 1         continue  # Пропускаем пустые сообщения      total_messages += 1          role = ""     # Проверка, является ли автор сообщения пользователем или ИИ     try:         author_role_element = message.find_element(By.XPATH, './/div[@data-message-author-role]')         author_role = author_role_element.get_attribute('data-message-author-role')         if author_role == 'user':             user_messages += 1             role = "Пользователь"             role_class = "user-message"         elif author_role == 'assistant':             ai_messages += 1             role = "ИИ"             role_class = "ai-message"         else:             continue  # Пропускаем неизвестные роли     except:         continue  # Пропускаем сообщения без атрибута data-message-author-role      # Заменяем роль на значок для ИИ     if role == "ИИ":         role_icon = "<img src='../GPT_icon.png' alt='ChatGPT Icon' width='20' height='20'>"         role_html = f"<p>{role_icon}</p>"     else:         role_html = ""      html_content += f"<div class='message-container'><div class='{role_class}'>{role_html}"      # Сохранение текста и кода     text_elements = message.find_elements(By.XPATH, './/div[contains(@class, "whitespace-pre-wrap")]')     if text_elements:         text_content = text_elements[0].text         save_text(text_content, f'Диалог/message_{idx}.txt')          code_elements = message.find_elements(By.XPATH, './/code[contains(@class, "!whitespace-pre hljs")]')         if code_elements:             for code_element in code_elements:                 code_content = code_element.text                 save_text(code_content, f'Диалог/code_{idx}.txt')                 text_content = text_content.replace(code_content, f"<div class='code-block'><button class='copy-button' onclick='copyCode(this)'>Копировать</button><pre><code>{code_content}</code></pre></div>")                  html_content += f"<p>{text_content}</p>"         print(f"Сообщение {idx - skipped_msg} сохранено (Текст: 1, Код: {len(code_elements)})")      # Сохранение изображений     img_elements = message.find_elements(By.TAG_NAME, 'img')     if img_elements:         for img_idx, img_element in enumerate(img_elements):             img_url = img_element.get_attribute('src')             img_path = f'Вложения/image_{idx}_{img_idx}.png'             download_file(img_url, f'Диалог/{img_path}')             html_content += f"<div class='image-container'><img src='{img_path}' onclick='showModal(\"{img_path}\")'></div>"         print(f"Изображения: {len(img_elements)}", end=", ")     else:         print(f"Изображения: 0", end=", ")      # Сохранение прикрепленных файлов     file_elements = message.find_elements(By.XPATH, './/a[contains(@href, "/attachment")]')     if file_elements:         for file_element in file_elements:             file_url = file_element.get_attribute('href')             file_name = file_element.text             file_path = f'Вложения/{file_name}'             download_file(file_url, f'Диалог/{file_path}')             html_content += f"<a href='{file_path}'>{file_name}</a>"         print(f"Файлы: {len(file_elements)})")     else:         print(f"Файлы: 0)")      html_content += "</div></div>"

Не забудем корректно закрыть HTML-структуру, сохранить основной HTML-файл и вывести статистику:

# Закрытие HTML структуры html_content += """ </body> </html> """  # Сохранение основного HTML файла with open('Диалог/диалог.html', 'w', encoding='utf-8') as f:     f.write(html_content)  # Вывод статистики print(f"Всего сообщений: {total_messages}") print(f"Сообщений пользователя: {user_messages}") print(f"Сообщений ИИ: {ai_messages}")  print("Диалог сохранен.")

Ссылка на репо с исходным кодом:

https://github.com/Devastor/DevastorSaveDialogues_ChatGPT

там же можно скачать файл иконки (для обозначения ИИ в финальном диалоге)


Что можно (и нужно) улучшить

  1. Добавить возможность сохранения не только изображений, но и других форматов вложений, например, таблицы xls, csv (другие вложения живут в чате недолго, в отличие от таблиц).

  2. Пофиксить ошибку «OSError: [WinError 6] Неверный дескриптор», которая вылетает после сохранения диалога (у меня не получилось, хотя я не особо старался, ибо лень, и работе скрипта не мешает, это должно быть довольно просто).

  3. Добавить сохранение всех диалогов аккаунта в цикле (легко).

  4. Добавить сохранение названия диалога (не так тривиально, как кажется — нужно как-то придумать, каким образом ловить имя контейнера того диалога, который выбран).

  5. Убрать лишнюю кнопку «Копировать» из правого верхнего угла 🙂

  6. Сделать логи отключаемыми (лучше всего прописать в args)

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Пригодится скрипт?

57.14% Да, полезная штука!4
28.57% Нет, зачем оно вообще?2
14.29% Лучше напишу свой)1

Проголосовали 7 пользователей. Воздержались 2 пользователя.

ссылка на оригинал статьи https://habr.com/ru/articles/823428/