Тема: AHK v1: Обновить состояние скрипта, скачивающего посты ВК
Составил с ИИ код ниже, который за 1 запрос может скачивать до 100 постов из VK с форматированием в HTML-формат различных типов ВК-постов. Для запроса используется приведенная "хранимая процедура", которую нужно создать на сайте - и токен, прописываемый в файл "token.txt". Так же используется библиотека JSON.ahk.
Код имеет странную проблему, когда после парсинга json-ответа первой итерации (offset := 0), на второй итерации (offset := 1) json-ответ не распознается как валидный. При этом достоверно известно, что все последующие ответы валидны, что проверялось подменой ответа первой итерации - ответом второй итерации, а так же при старте скрипта - запуском парсинга со второй итерации. Пытался очищать предыдущее состояние всех данных, но помогает только перезагрузка скрипта, когда JSON класс полностью обновляется. Просьба подсказать решение задачи с полным обновлением состояния скрипта после каждой итерации.
Хранимая процедура
// execute.getMaxPosts
var response = API.wall.get({
"owner_id": Args.owner_id,
"offset": Args.offset,
"count": 25, // 100 — МАКСИМУМ ДЛЯ WALL.GET
"extended": 1,
"fields": "first_name,last_name",
"v": "5.291"
});
return response;AHK v1
(Первая часть кода)
#NoEnv
SendMode Input
#SingleInstance Force
SetWorkingDir %A_ScriptDir%
; Подключаем JSON.ahk
#Include JSON.ahk
; Чтение токена из файла
FileRead, access_token, token.txt
if (ErrorLevel || access_token = "") {
MsgBox, Не удалось прочитать токен из файла token.txt
ExitApp
}
; Убираем возможные пробелы и переводы строк
access_token := Trim(access_token)
owner_id := "-29534144"
output_folder := "VK_Posts_HTML"
; Создаем папку для сохранения
FileCreateDir, %output_folder%
; Получаем и сохраняем все посты
total_posts := GetAllPostsAndSaveHTML(access_token, owner_id, output_folder)
MsgBox, % "Готово! Сохранено постов: " total_posts
; Основная функция для получения всех постов
GetAllPostsAndSaveHTML(access_token, owner_id, output_folder) {
total_saved := 0
batch_size := 25
offset := 0
total_count := 0 ; Добавляем переменную для общего количества постов
Loop {
; Получаем пачку постов через execute
posts_data := GetPostsBatch(access_token, owner_id, offset)
if (!posts_data || !posts_data.items || posts_data.items.MaxIndex() = 0) {
break
}
; Сохраняем общее количество постов при первом запросе
if (offset = 0 && posts_data.count > 0) {
total_count := posts_data.count
}
; Обрабатываем каждый пост
for index, post_item in posts_data.items {
; Парсим пост в наш формат
post_data := ParsePostItem(post_item, posts_data)
if (post_data && post_data.id) {
html_content := ConvertPostToHTML(post_data)
filename := output_folder "\" owner_id "_" post_data.id ".html"
SaveHTMLToFile(html_content, filename)
total_saved++
; Прогресс с информацией об общем количестве
progress_text := "Сохранено: " total_saved " из " total_count " постов (offset: " offset ")"
if (total_count > 0) {
percent := Round((total_saved / total_count) * 100)
progress_text .= " (" percent "%)"
}
ToolTip, % progress_text
}
}
offset += batch_size ; Увеличиваем offset для следующей пачки
Sleep, 2500 ; Пауза между запросами
; если сохранили больше или равно общему количеству постов - выходим
if (total_count > 0 && total_saved >= total_count) {
break
}
; Дополнительная проверка: если в ответе нет постов
if (posts_data.items.MaxIndex() = 0) {
break
}
}
ToolTip
return total_saved
}
; Получение пачки постов через execute.getMaxPosts
GetPostsBatch(access_token, owner_id, offset) {
url := "https://api.vk.com/method/execute.getMaxPosts?owner_id=" owner_id
. "&offset=" offset
. "&access_token=" access_token
. "&v=5.291"
response := SendHTTPRequest(url)
; Сохраняем сырой ответ для отладки
FileDelete, debug_batch_%offset%_response.txt
FileAppend, %response%, debug_batch_%offset%_response.txt, UTF-8
if (response = "ERROR") {
return ""
}
return ParsePostsResponse(response)
}
; Парсинг ответа от execute.getMaxPosts
ParsePostsResponse(response) {
try {
json := JSON.Load(response)
; Проверяем на ошибки API
if (json.error) {
error_msg := "Ошибка API: код " json.error.error_code " - " json.error.error_msg
MsgBox, % error_msg
return ""
}
; Проверяем наличие response
if (!json.response) {
MsgBox, Нет response в ответе
return ""
}
result := Object()
result.items := json.response.items ? json.response.items : []
result.profiles := json.response.profiles ? json.response.profiles : []
result.groups := json.response.groups ? json.response.groups : []
result.count := json.response.count ? json.response.count : 0 ; Это поле теперь используется
return result
} catch e {
MsgBox, Ошибка парсинга JSON батча: %e%
return ""
}
}
; Конвертация элемента поста в наш формат
ParsePostItem(post_item, posts_data) {
post := Object()
post.photos := []
post.links := []
post.videos := []
post.audios := []
post.documents := []
post.polls := []
post.reposts := []
post.albums := []
; Основные поля поста
post.id := post_item.id
post.owner_id := post_item.owner_id
post.from_id := post_item.from_id
post.date := post_item.date
post.text := post_item.text ? post_item.text : ""
post.title := post_item.title ? post_item.title : ""
; Статистика
post.likes := post_item.likes.count
post.reposts := post_item.reposts.count
post.comments := post_item.comments.count
post.views := post_item.views.count
; Извлекаем все виды вложений
if (post_item.attachments && post_item.attachments.Length() > 0) {
for index, attachment in post_item.attachments {
if (attachment.type = "photo" && attachment.photo) {
photo_url := GetLargestPhotoUrl(attachment.photo)
if (photo_url) {
post.photos.Push(photo_url)
}
}
else if (attachment.type = "link" && attachment.link) {
link_data := ExtractLinkData(attachment.link)
if (link_data) {
post.links.Push(link_data)
}
}
else if (attachment.type = "video" && attachment.video) {
video_data := ExtractVideoData(attachment.video)
if (video_data) {
post.videos.Push(video_data)
}
}
else if (attachment.type = "audio" && attachment.audio) {
audio_data := ExtractAudioData(attachment.audio)
if (audio_data) {
post.audios.Push(audio_data)
}
}
else if (attachment.type = "doc" && attachment.doc) {
doc_data := ExtractDocumentData(attachment.doc)
if (doc_data) {
post.documents.Push(doc_data)
}
}
else if (attachment.type = "poll" && attachment.poll) {
poll_data := ExtractPollData(attachment.poll)
if (poll_data) {
post.polls.Push(poll_data)
}
}
else if (attachment.type = "post" && attachment.wall) {
repost_data := ExtractRepostData(attachment.wall)
if (repost_data) {
post.reposts.Push(repost_data)
}
}
else if (attachment.type = "album" && attachment.album) {
album_data := ExtractAlbumData(attachment.album)
if (album_data) {
post.albums.Push(album_data)
}
}
}
}
; Название группы и screen_name
post.group_name := "Группа"
post.group_screen_name := ""
post.group_avatar := ""
if (posts_data.groups && posts_data.groups.Length() > 0) {
post.group_name := posts_data.groups[1].name
post.group_screen_name := posts_data.groups[1].screen_name ? posts_data.groups[1].screen_name : ""
; Извлекаем аватар группы (лучшее качество)
if (posts_data.groups[1].photo_200) {
post.group_avatar := posts_data.groups[1].photo_200
} else if (posts_data.groups[1].photo_100) {
post.group_avatar := posts_data.groups[1].photo_100
} else if (posts_data.groups[1].photo_50) {
post.group_avatar := posts_data.groups[1].photo_50
}
}
; Если это пост от пользователя, ищем его аватар
post.user_avatar := ""
if (post.from_id > 0 && posts_data.profiles && posts_data.profiles.Length() > 0) {
for index, profile in posts_data.profiles {
if (profile.id = post.from_id) {
; Аватар пользователя (лучшее качество)
if (profile.photo_200) {
post.user_avatar := profile.photo_200
} else if (profile.photo_100) {
post.user_avatar := profile.photo_100
} else if (profile.photo_50) {
post.user_avatar := profile.photo_50
}
break
}
}
}
return post
}
; Извлечение данных ссылки
ExtractLinkData(link) {
link_data := Object()
link_data.url := link.url
link_data.title := link.title ? link.title : ""
link_data.description := link.description ? link.description : ""
link_data.caption := link.caption ? link.caption : ""
; Извлекаем превью ссылки
if (link.photo) {
link_data.preview_url := GetLargestPhotoUrl(link.photo)
} else {
link_data.preview_url := ""
}
return link_data
}
; Извлечение данных видео
ExtractVideoData(video) {
video_data := Object()
video_data.id := video.id
video_data.owner_id := video.owner_id
video_data.title := video.title ? video.title : ""
video_data.description := video.description ? video.description : ""
video_data.duration := video.duration ? video.duration : 0
; Превью видео
if (video.image && video.image.Length() > 0) {
video_data.preview_url := GetLargestPhotoUrl({"sizes": video.image})
} else if (video.first_frame && video.first_frame.Length() > 0) {
video_data.preview_url := GetLargestPhotoUrl({"sizes": video.first_frame})
} else {
video_data.preview_url := ""
}
return video_data
}
; Извлечение данных аудио
ExtractAudioData(audio) {
audio_data := Object()
audio_data.artist := audio.artist ? audio.artist : ""
audio_data.title := audio.title ? audio.title : ""
audio_data.duration := audio.duration ? audio.duration : 0
audio_data.url := audio.url ? audio.url : ""
return audio_data
}
; Извлечение данных документа
ExtractDocumentData(doc) {
doc_data := Object()
doc_data.id := doc.id
doc_data.owner_id := doc.owner_id
doc_data.title := doc.title ? doc.title : ""
doc_data.ext := doc.ext ? doc.ext : ""
doc_data.url := doc.url ? doc.url : ""
doc_data.size := doc.size ? doc.size : 0
doc_data.date := doc.date ? doc.date : 0
doc_data.access_key := doc.access_key ? doc.access_key : ""
; Превью документа (если есть)
if (doc.preview && doc.preview.photo && doc.preview.photo.sizes) {
doc_data.preview_url := GetLargestPhotoUrl(doc.preview.photo)
} else {
doc_data.preview_url := ""
}
return doc_data
}
; Извлечение данных опроса
ExtractPollData(poll) {
poll_data := Object()
poll_data.id := poll.id
poll_data.owner_id := poll.owner_id
poll_data.question := poll.question ? poll.question : ""
poll_data.votes := poll.votes ? poll.votes : 0
poll_data.answers := []
; НОВЫЕ ПОЛЯ ДЛЯ ОПРОСА
poll_data.anonymous := poll.anonymous ? poll.anonymous : 0
poll_data.multiple := poll.multiple ? poll.multiple : 0
poll_data.end_date := poll.end_date ? poll.end_date : 0
poll_data.closed := poll.closed ? poll.closed : 0
poll_data.is_board := poll.is_board ? poll.is_board : 0
poll_data.can_edit := poll.can_edit ? poll.can_edit : 0
poll_data.can_vote := poll.can_vote ? poll.can_vote : 0
poll_data.can_report := poll.can_report ? poll.can_report : 0
poll_data.can_share := poll.can_share ? poll.can_share : 0
poll_data.author_id := poll.author_id ? poll.author_id : 0
poll_data.background := poll.background ? poll.background : ""
if (poll.answers && poll.answers.Length() > 0) {
for index, answer in poll.answers {
answer_data := Object()
answer_data.id := answer.id
answer_data.text := answer.text ? answer.text : ""
answer_data.votes := answer.votes ? answer.votes : 0
answer_data.rate := answer.rate ? answer.rate : 0
poll_data.answers.Push(answer_data)
}
}
return poll_data
}
; Извлечение данных репоста
ExtractRepostData(wall) {
repost_data := Object()
repost_data.id := wall.id
repost_data.owner_id := wall.owner_id
repost_data.from_id := wall.from_id
repost_data.date := wall.date
repost_data.text := wall.text ? wall.text : ""
; Статистика репоста
if (wall.likes) {
repost_data.likes := wall.likes.count
}
if (wall.reposts) {
repost_data.reposts := wall.reposts.count
}
if (wall.comments) {
repost_data.comments := wall.comments.count
}
if (wall.views) {
repost_data.views := wall.views.count
}
return repost_data
}
; Извлечение данных альбома
ExtractAlbumData(album) {
album_data := Object()
album_data.id := album.id
album_data.owner_id := album.owner_id
album_data.title := album.title ? album.title : ""
album_data.description := album.description ? album.description : ""
album_data.size := album.size ? album.size : 0
; Обложка альбома
if (album.thumb && album.thumb.sizes) {
album_data.thumb_url := GetLargestPhotoUrl(album.thumb)
} else {
album_data.thumb_url := ""
}
return album_data
}
