В этой статье мы разберем, как получить и использовать API сайта, если по нему нет документации или оно еще не открыто официально. Руководство написано для новичков, которые еще не пробовали зареверсить простой API. Для тех же кто сам занимался подобным ничего нового здесь нет.


Разбор проведем на примере API сервиса https://www.captionbot.ai/ который недавно открыл Microsoft (спасибо им за это). Многие могли прочитать о нем в статье на Geektimes. Сайт использует ajax запросы в формате JSON, поэтому скопировать их будет легко и приятно. Поехали!


КДПВ


Анализируем запросы


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


image


В нашем случае все интересующие нас запросы имеют базовый URL https://www.captionbot.ai/api


Инициализация


При первом открытии сайта идет GET запрос на /api/init без параметров.
Ответ имеет Content-Type: application/json, при этом в теле ответа нам приходит просто строка вида:


"54cER5HILuE"

Запомним это и идем дальше.


Отправка URL


У нас есть два способа загрузить изображение: через URL и через загрузку файла. Для теста берем URL изображения Лены с вики и отсылаем. В сетевой активности появляется POST запрос на /api/message со следующими параметрами:


{
    "conversationId": "54cER5HILuE",
    "waterMark": "",
    "userMessage": "https://upload.wikimedia.org/wikipedia/ru/2/24/Lenna.png"
}

Ага, говорим себе мы, значит метод init вернул нам строку для conversationId, а в userMessage попала наша ссылка. Что такое waterMark пока непонятно. Смотрим на данные ответа:


"{\"ConversationId\":null,\"WaterMark\":\"131071012038902294\",\"UserMessage\":\"I am not really confident, but I think it's a woman wearing a hat\\nand she seems . \",\"Status\":null}"

Зачем-то закодировали JSON дважды, ну да ладно. В человеческом виде это выглядит так:


{
    "ConversationId": null,
    "WaterMark": "131071012038902294",
    "UserMessage": "I am not really confident, but I think it's a woman wearing a hat\\nand she seems .",
    "Status": null
}

Все параметры по пути поменяли манеру написания, но это мелочи жизни. Итак, нам вернули некоторое значение WaterMark, почему-то пустой ConversationId, собственно подпись к фото в поле UserMessage и некий пустой статус.


Загрузка изображения


Далее, не закрывая вкладку, пробуем ту же операцию с загрузкой фото из локального файла. Видим POST запрос на /api/upload в формате multipart/form-data с названием поля file:


-----------------------------50022246920687
Content-Disposition: form-data; name="file"; filename="Lenna.png"
Content-Type: image/png

В ответ получаем строку URL нашего загруженного файла, можем перейти по нему и убедиться в этом:


"https://captionbot.blob.core.windows.net/images-container/2ogw3q4m.png"

Затем отсылается уже знакомый нам запрос на /api/message:


{
    "conversationId": "54cER5HILuE",
    "waterMark": "131071012038902294",
    "userMessage": "https://captionbot.blob.core.windows.net/images-container/2ogw3q4m.png"
}

Вот и пригодился waterMark из предыдущего ответа, а URL тот, что нам вернул метод upload. Данные ответа аналогичны предыдущим.


Пишем обертку


Чтобы использовать полученные знания с удобством, делаем простую обертку на вашем любимом языке программирования. Я сделаю это на Python. Для запросов к сайту использую requests, так как он удобный и в нем есть сессии, которые хранят cookie за меня. Сайт использует SSL, но по дефолту requests будет ругаться на сертификат:


hostname 'www.captionbot.ai' doesn't match either of '*.azurewebsites.net', '*.scm.azurewebsites.net', '*.azure-mobile.net', '*.scm.azure-mobile.net'

Решается это установкой флага verify=False при каждом запросе.


Полный исходник
import json
import mimetypes
import os
import requests
import logging
logger = logging.getLogger("captionbot")

class CaptionBot:
    BASE_URL = "https://www.captionbot.ai/api/"

    def __init__(self):
        self.session = requests.Session()
        url = self.BASE_URL + "init"
        resp = self.session.get(url, verify=False)
        logger.debug("init: {}".format(resp))
        self.conversation_id = json.loads(resp.text)
        self.watermark = ''

    def _upload(self, filename):
        url = self.BASE_URL + "upload"
        mime = mimetypes.guess_type(filename)[0]
        name = os.path.basename(filename)
        files = {'file': (name, open(filename, 'rb'), mime)}
        resp = self.session.post(url, files=files, verify=False)
        logger.debug("upload: {}".format(resp))
        return json.loads(resp.text)

    def url_caption(self, image_url):
        data = json.dumps({
            "userMessage": image_url,
            "conversationId": self.conversation_id,
            "waterMark": self.watermark
        })
        headers = {
            "Content-Type": "application/json"
        }
        url = self.BASE_URL + "message"
        resp = self.session.post(url, data=data, headers=headers, verify=False)
        logger.debug("url_caption: {}".format(resp))
        if not resp.ok:
            return None
        res = json.loads(json.loads(resp.text))
        self.watermark = res.get("WaterMark")
        return res.get("UserMessage")

    def file_caption(self, filename):
        upload_filename = self._upload(filename)
        return self.url_caption(upload_filename)

Исходный код есть на Github, плюс готовый пакет в pip.

Поделиться с друзьями
-->

Комментарии (15)


  1. kursorik2
    07.05.2016 19:29
    +4

    А я думал что судя по заголовку в статье, автор расскажет как правильно парсить контент с сайта без API.


    1. Mayflower
      07.05.2016 19:30

      Да, наверное заголовок вышел не самый удачный. Есть ли совет как сделать лучше?


      1. evnuh
        07.05.2016 20:26
        +7

        «Пользуемся недокументированным API сайта captionbot.ai»


        1. Mayflower
          07.05.2016 22:51

          Спасибо. Меняю, чтобы не вводить людей в заблуждение


  1. Mayflower
    08.05.2016 13:08

    Товарищи минусующие, реквестирую ваш фидбек: что не так? Это моя первая статья, поэтому хочу узнать, что можно улучшить.


    1. lizarge
      08.05.2016 16:46
      +2

      Все например, статья описывает то что известно и делается любым разработчиком с минимальным знаем основ http запросов. Ничего нового кроме того что Microsoft не позаботился о защите она не несет. Лучше бы вы написали статю как поднять тот же captionbot на своем сервере используя Сaffe, но это уже совсем другой уровень.


      1. summerwind
        09.05.2016 00:48

        О защите чего именно не заботится Microsoft? Вы ведь в любом SPA можете увидеть в консоли, какие запросы отправляются на сервер.


    1. icoz
      09.05.2016 10:01

      Что хоть за сервис?
      Чем нам может помочь знание этого api?
      Имея красивую обертку над api на Питоне, мы теперь можем… (продолжите фразу).


      1. Mayflower
        09.05.2016 11:04

        У меня например на нем работает Телеграм бот, который присылает подпись на любое присланное фото или URL. Можно сделать фото и получить к нему подпись за несколько секунд.


        1. icoz
          09.05.2016 11:57

          Думаю, что это стоило указать в статье. А то какой-то сферический конь в вакууме получается… Особенно для тех, кто в этой теме не варится.
          Типа такого:
          image


      1. Mayflower
        09.05.2016 11:19

        Целью всего этого занятия было посмотреть на сервис, насколько хорошо подписи подходят к изображениям. Можно взять базу потеганных изображений, скормить captionbot'у через API и посмотреть на процент вхождения тега в подпись. Но это пока что в планах на будущее.


    1. icoz
      09.05.2016 10:04

      Что это за сервис?
      Чем нам помогает знание этого api?
      Имея красивую обертку над api на Питоне, мы можем… (продолжите фразу)


      1. icoz
        09.05.2016 10:16

        Извиняюсь за повтор. Приложение на мобильном глюкнуло.


    1. icoz
      09.05.2016 10:10

      А минусуют, скорее всего, за то, что здесь реверс-инжиниринга почти и нет. Ценности api конкретного сервиса тоже не видно.
      В результате хабр пополнился статьёй, которая не несёт никакой ценности, но будет вылезать в поисковых запросах и затруднять поиск действительно полезной информации.


    1. icoz
      09.05.2016 10:21
      +1

      А минусуют, скорее всего, за то, что хабр пополнился почти никому ненужной статьей.
      Реверс-инжиниринга здесь почти нет, а тот что есть — очевиден. Ценности данный конкретный API также не несет. Но эта статья теперь есть на хабре. Она будет вылезать в поисковой выдаче и затруднять поиск действительно ценной информации.
      ИМХО, конечно.