Как стать автором
Обновить

Выкачиваем фотографии из каждого диалога ВК средствами API

Иногда бывает полезно выгрузить все фотографии из диалога ВКонтакте, руками это не сделать не просто, поэтому обратимся к API.

В документации присутствует метод messages.getHistoryAttachments — он нам и нужен. Возвращать он может не только фотографии, но и другого рода документы: аудио, видео и т.д. Но в этом примере будем рассматривать только фотографии.

Передавать в метод будем 4 параметра:

peer_id — собственно, ID юзера из диалога с которым нужно получить фотографии
media_type — тип необходимого документа, будем передавать photo
count — количество фотографий, которые будут возвращены. Мы хотим выкачать все, поэтому будет брать по максимуму — 200
start_from — это смещение, с помощью которого мы сможем получить все имеющиеся в диалоге фотографии

Нам требуется загрузить фотографии из всех диалогов, поэтому воспользуемся еще одним методом для получения списка всех диалогов — messages.getDialogs. Получив списки диалогов, мы получим идентификаторы юзеров, которые в последующем сможем передавать в метод для получения фотографий.

И, конечно же, не обойтись без библиотеки для работы с API вконтакте, которая умеет выполнять нужные функции. Я воспользуюсь своей — golang-vk-api

Диалогов, также как и фотографий. нельзя получить больше 200, поэтому напишем функцию для получения всех диалогов, где будем использовать смещение:

func getDialogs(client *vkapi.VKClient) ([]*vkapi.DialogMessage, error) {
	offset := 0
	params := url.Values{}
	messages := []*vkapi.DialogMessage{}

	for {
		params.Set("offset", strconv.Itoa(offset))
		dialogs, err := client.GetDialogs(200, params)
		if err != nil {
			return nil, err
		}

		if len(dialogs.Messages) > 0 {
			for _, msg := range dialogs.Messages {
				messages = append(messages, msg.Message)
			}
		}

		offset += 200

		if len(messages) >= dialogs.Count {
			break
		}
	}

	return messages, nil
}

После того, как получили все диалоги, можно получить непосредственно сами фотографии, используя метод, упомянутый в самом начале:

func getAttachments(client *vkapi.VKClient, UID int) ([]*vkapi.PhotoAttachment, error) {
	attachments := []*vkapi.PhotoAttachment{}
	params := url.Values{}

	for {
		att, err := client.GetHistoryAttachments(UID, "photo", 200, params)
		if err != nil {
			return nil, err
		}

		if len(att.Attachments) == 0 {
			break
		}

		for _, photo := range att.Attachments {
			attachments = append(attachments, photo.Attachment.Photo)
		}

		params.Set("start_from", att.NextFrom)
	}

	return attachments, nil
}

Прежде чем преступить к загрузке всех фотографий, напишем пару необходимых методов:

Проверка существования папки

func folderExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}

	return true
}

Создание папки

func mkdir(name string) bool {
	err := os.MkdirAll(name, 0755)
	if err != nil {
		return false
	}

	return true
}

Получение имени, с которым будет сохраняться файл, из ссылки

func getFileName(url string) string {
	idx := strings.LastIndex(url, "/")
	return url[idx+1:]
}

У фотографии не всегда доступны все размеры, поэтому напишем метод для выборки наибольшего размера

func getBestLink(photo *vkapi.PhotoAttachment) string {
	if photo.Photo2560 != "" {
		return photo.Photo2560
	} else if photo.Photo1280 != "" {
		return photo.Photo1280
	}

	return photo.Photo604
}

Загрузка файла

func downloadFile(url string, name string) error {
	file, err := os.Create(name)
	if err != nil {
		return err
	}
	defer file.Close()

	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	_, err = io.Copy(file, resp.Body)
	if err != nil {
		return err
	}

	return nil
}

В основной функции получаем все диалоги, для каждого диалога получаем все фотографии и загружаем по 10 штук одновременно в отдельную для юзера папку

		dialogs, err := getDialogs(client)
		if err != nil {
			log.Printf("failed to get dialogs: %s\n", err)
			return
		}

		curUser := 0
		for _, d := range dialogs {
			photos, err := getAttachments(client, d.UID)
			if err != nil {
				log.Printf("failed to get attachments for UID %d\n", d.UID)
				continue
			}

			if len(photos) == 0 {
				continue
			}

			downloadPath := "photos/" + strconv.Itoa(d.UID)
			mkdir(downloadPath)

			curUser++
			downloaded := 0
			gocounter := 0
			limit := 10
			queue := len(photos)
			total := len(photos)
			totalUsers := len(dialogs)
			var wg sync.WaitGroup

			for _, p := range photos {
				link := getBestLink(p)
				path := downloadPath + "/" + getFileName(link)
				wg.Add(1)

				go func() {
					err := downloadFile(link, path)
					if err != nil {
						log.Printf("failed to download file: %s\n", err)
					}
					wg.Done()
					queue--
					downloaded++
					log.Printf("downloaded %d/%d photos. user %d/%d\n",
						downloaded, total, curUser, totalUsers)
				}()

				if queue < limit {
					limit = queue
				}

				gocounter++
				if gocounter == limit {
					wg.Wait()
					gocounter = 0
				}
			}
		}


Вот и все. Полный код по ссылке
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.