Работа с API Яндекс.Вебмастер на примере добавления оригинальных текстов

9 мая 2015, суббота

В этой статье хочу рассказать о работе c API Яндекс. Вебмастер на примере скрипта для добавления текстов в сервис Яндекс Оригинальные тексты.

Введение

API Яндекс. Вебмастера дает возможность разработчикам создавать приложения для работы с пользовательскими данными, хранящимися на сервисе Яндекс. Вебмастер.

API Яндекс. Вебмастера реализовано на REST-принципах (Representational State Transfer) с использованием HTTP и XML для обмена данными.

Клиентское приложение обменивается XML-сообщениями с сервером API Яндекс. Вебмастера посредством HTTP-запросов в соответствии с REST-принципами: ресурсы сайтов и списков сайтов получаются методом HTTP GET, создаются методом HTTP POST, изменяются методом HTTP PUT и удаляются методом HTTP DELETE.

API Яндекс. Вебмастера доступен для идентифицированных пользователей по адресу: https://webmaster.yandex.ru/api/v2.

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

Так как большинство действий в API Яндекс. Вебмастера выполняется от имени конкретного пользователя, клиентское приложение должно авторизоваться на OAuth-сервере Яндекса oauth.yandex.ru.

Для демонстрации работы скрипта я создам модель статей Article, которая будет содержать 2 поля: заголовок и контент.

article.rb:

class Article < ActiveRecord::Base
end

Код миграции:

class CreateArticles < ActiveRecord::Migration
  def change
    create_table :articles do |t|
      t.string :title
      t.text :content

      t.timestamps
    end
  end
end

Вставим пару статей:

#encoding: utf-8
class InsertSomeArticles < ActiveRecord::Migration
  def up
    Article.create([
        {title:'Нечетный тетрахорд: гипотеза и теории', content:'Явление культурологического порядка стремительно изменяет музыкальный октавер. Promotion-кампания упорядочивает метод последовательных приближений. Надо сказать, что пуанта вероятна.

Согласно предыдущему, аллюзийно-полистилистическая композиция слабо притягивает аксиоматичный хамбакер. Производство стремительно ускоряет мелодический традиционный канал. Анализ зарубежного опыта, на первый взгляд, стабилизирует расходящийся ряд, это и есть одномоментная вертикаль в сверхмногоголосной полифонической ткани. Стимулирование сбыта вызывает позиционный эффект "вау-вау". Эффект "вау-вау" разнородно использует маркетинг. Геодезическая линия, так или иначе, неоднозначна.

В заключении добавлю, струна изящно допускает октавер. Связное множество, согласно традиционным представлениям, создает тройной интеграл. Форшлаг определяет конвергентный флажолет. Лидерство в продажах, общеизвестно, решительно экономит микрохроматический интервал.'},
        {title:'Позиционный презентационный материал: методология и особенности', content:'Легато непрерывно. Цикл искажает имидж предприятия. Российская специфика по-прежнему востребована.

Контекстная реклама порождена временем. Говорят также о фактуре, типичной для тех или иных жанров ("фактура походного марша", "фактура вальса" и пр.), и здесь мы видим, что алгебра инновационна. Еще Аристотель в своей Политике говорил, что музыка, воздействуя на человека, доставляет своего рода очищение, то есть облегчение, связанное с наслаждением, однако фишка фактурна. Стратегическое планирование оправдывает действительный хамбакер, не случайно эта композиция вошла в диск В.Кикабидзе "Ларису Ивановну хочу".

Детройтское техно, в первом приближении, одновременно. Midi-контроллер наиболее полно создает аксиоматичный контент, отвоевывая свою долю рынка. Рекламное сообщество программирует алеаторически выстроенный бесконечный канон с полизеркальной векторно-голосовой структурой. Рыночная ситуация вызывает хорус. Однако не все знают, что абсолютная погрешность расточительно диссонирует рыночный сет, откуда следует доказываемое равенство. Рассмотрим непрерывную функцию y = f ( x ), заданную на отрезке [ a, b ], ряд Тейлора определяет общественный медиамикс.'}
                   ])
  end

  def down
    Article.destroy_all
  end
end

CRUD контроллер для модели Article:

class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]

  # GET /articles
  # GET /articles.json
  def index
    @articles = Article.all
  end

  # GET /articles/1
  # GET /articles/1.json
  def show
  end

  # GET /articles/new
  def new
    @article = Article.new
  end

  # GET /articles/1/edit
  def edit
  end

  # POST /articles
  # POST /articles.json
  def create
    @article = Article.new(article_params)

    respond_to do |format|
      if @article.save
        format.html { redirect_to @article, notice: 'Article was successfully created.' }
        format.json { render action: 'show', status: :created, location: @article }
      else
        format.html { render action: 'new' }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /articles/1
  # PATCH/PUT /articles/1.json
  def update
    respond_to do |format|
      if @article.update(article_params)
        format.html { redirect_to @article, notice: 'Article was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /articles/1
  # DELETE /articles/1.json
  def destroy
    @article.destroy
    respond_to do |format|
      format.html { redirect_to articles_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_article
      @article = Article.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def article_params
      params.require(:article).permit(:title, :content)
    end
end

В routes.rb добавим следующие строки:

Rails.application.routes.draw do

  root 'articles#index'
  resources :articles

end

Теперь статьи есть, приступим к добавлению контента в сервис Оригинальные тексты от Yandex. Для этого нужно сначала зарегистрировать приложение и получить токен.

Регистрация приложения

Теория

Для того чтобы начать работать с API Яндекс. Вебмастера необходимо получить client_id в системе авторизации Яндекса oauth.yandex.ru. Без client_id использование API невозможно. Для получения client_id нужно зарегистрировать клиентское приложение на странице oauth.yandex.ru/client/new.

При регистрации нового приложения требуется указать следующие данные:

  • Название — название приложения, отображается на странице запроса доступа и в списке зарегистрированных приложений;
  • Описание — краткое описание приложения, отображается в списке приложений, которым пользователь разрешил доступ к своему аккаунту;
  • Права — список операций, на которые пользователь может дать доступ (в настоящий момент это добавление сайта и получение информации о сайте);
  • Ссылка на иконку -необязательное cвойство;
  • Ссылка на сайт приложения — необязательное свойство;
  • Callback URI — ссылка на страницу, куда пользователь будет перенаправляться после того, как подтвердит доступ к своим данным.

Практика

Форма регистрации нового приложения представлена на рисунке. Подробнее об условиях использования API можно прочитать здесь.

Заполняем форму своими данными жмем кнопку «Сохранить» и попадаем на страницу зарегистрированного приложения, на которой видим наш client_id и client_password:

Список всех приложений, зарегистрированных разработчиком, приведен на его странице Зарегистрированные приложения.

Для сохранения конфигурационных данных я использую gem settingslogic и в config/application.yml добавлю следующие строки:


defaults: &defaults
  yandex:
    app_host: 'host приложенияя'
    client_id: 'наш client_id'
    client_password: 'наш client_password'
    state: 'случайная строка'

development:
  <<: *defaults

production:
  <<: *defaults

staging:
  <<: *defaults

test:
  <<: *defaults

Параметр app_host это идентификатор вашего сайта в Вебмастере. Посмотреть его можно перейдя в «вебмастер → мои сайты → ваш сайт» в адресной строке будет такая запись: https://webmaster.yandex.ru/site/?host=21706557. Параметр state — cтрока состояния, которую Яндекс. OAuth возвращает без изменения.
Можно использовать, например, чтобы идентифицировать пользователя приложения. Необязательный параметр.

Далее необходимо авторизовать приложение.

Процедура авторизации

Теория

Приложения запрашивают токены по следующей схеме:

  1. Приложение направляет пользователя на OAuth-сервер. На открывшейся странице он может разрешить приложению доступ к определенным данным своей учетной записи.
  2. Пользователь разрешает доступ к своим данным, и OAuth-сервер перенаправляет его на указанный разработчиком адрес. Выданный токен (или код для его получения) включается в URL перенаправления. Если пользователь отказал в доступе, или произошла ошибка, в URL включается описание ошибки.
  3. Приложение включает полученный токен в запрос к API Яндекса, который поддерживает OAuth.

Полученный токен можно сохранить в приложении и использовать для запросов к API до истечения времени его жизни. Есть несколько видов токенов по времени жизни с ними можно ознакомиться в документации. Я буду использовать Ограниченный токен(время жизни 180 дней) и по мере необходимости его обновлять.

Практика

В админке(или удобном для вас месте в вашем проекте) разместим ссылку на OAuth-сервер, в которой в URL’е в качестве параметра передаем наш client_id.

= link_to 'Получить/Обновить токен Yandex', "https://oauth.yandex.ru/authorize?response_type=code&client_id=#{Settings.yandex.client_id}&state=#{Settings.yandex.state}", target: '_blank'

Параметр state — cтрока состояния, которую Яндекс. OAuth возвращает без изменения. Можно использовать, например, чтобы идентифицировать пользователя приложения (необязательный параметр).

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

Далее жмем кнопку «Разрешить». В зависимости от того, что было указано в поле Callback URL формы регистрации приложения, произойдет следующее:

  • если мы нажимали ссылку 'Подставить URL для разработки' в поле Callback URL, то нам вернется код подтверджения, который можно вводить вручную
  • если же мы укажем конкретную ссылку на нашем сайте, то Яндекс. OAuth перенаправляет пользователя на указанный адрес. Код подтверждения (или описание ошибки) передается в параметре URL перенаправления.

Давайте реализуем второй сценарий. Для этого я создам YandexController c экшэном token, который и будет обрабатывать ответ от Яндекс. OAuth.

В роуты добавляю путь к этому действию get 'yandex/token', to: 'yandex#token'.

Для работы с API Яндекса я создам 2 специальных класса Yandex:AuthorizationService и Yandex:OriginalTextsService и положу их в папку app/services/yandex.

class Yandex::AuthorizationService

  CLIENT_ID       = Settings.yandex.client_id
  CLIENT_PASSWORD = Settings.yandex.client_password

  #запрос на получение токена по коду
  def authorization_code_request(code)
    uri = URI.parse('https://oauth.yandex.ru/token')
    http = Net::HTTP.new(uri.host, uri.port)
    if uri.scheme == 'https'
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    end
    request = Net::HTTP::Post.new(uri.request_uri)
    request.set_form_data(grant_type: 'authorization_code', code:code, client_id: CLIENT_ID, client_secret: CLIENT_PASSWORD)
    request.add_field('Content-Type', 'application/x-www-form-urlencoded')
    http.request(request)
  end

end
class Yandex::OriginalTextsService

  APP_HOST        = Settings.yandex.app_host
  YANDEX_TOKEN    = SystemParam.find_by(code:'yandex_access_token').try(:value)

  attr_reader :text, :response

  def initialize(text)
    @text = text
  end

  #функция добавления текста в сервис Оригинальные тексты
  def send_text
    log('YANDEX_TOKEN is empty') and return false if YANDEX_TOKEN.blank?
    @response = send_request
    log_result_of_sending_text
    @response.code == '201' || @response.code == '409'
  end

  private

  #запрос на добавление текста в сервис Оригинальные тексты
  def send_request
    uri = URI.parse("http://webmaster.yandex.ru/api/v2/hosts/#{APP_HOST}/original-texts")
    http = Net::HTTP.new(uri.host, uri.port)
    request = Net::HTTP::Post.new(uri.request_uri)
    request.add_field('Host', 'webmaster.yandex.ru')
    request.add_field('Authorization', "OAuth #{YANDEX_TOKEN}")
    content = request_body
    request.add_field('Content-Length', content.size)
    request.body = content
    http.request(request)
  end

  #формируем XML
  def request_body
    builder = Nokogiri::XML::Builder.new(encoding:'UTF-8') do |xml|
      xml.send(:'original-text') do
        xml.content( Rack::Utils.escape(@text) )
      end
    end
    builder.doc.root.to_s
  end

  def log_result_of_sending_text
    log("\r\n"*3)
    log(['='*3,'YANDEX ORIGINAL TEXTS', '='*50].join(' '))
    log(@text)
    log('Sent to Yandex result:')
    log("request.code: #{@response.code}")
    log("request.body: #{@response.body}")
    log("\r\n"*3)
  end

  def log(message)
    Rails.logger.info(message)
  end

end

Полученный токен я буду сохранять в базу данных, для этого создам модель SystemParam(также в ней можно хранить другие системные параметры).

class SystemParam < ActiveRecord::Base

end
class CreateSystemParams < ActiveRecord::Migration
  def change
    create_table :system_params do |t|
      t.text :name
      t.text :code, null:false, unique:true
      t.text :value
      t.timestamp :expires_at
      t.timestamps
    end

    add_index :system_params, :code
  end
end

class CreateSystemParamYandexToken < ActiveRecord::Migration
  def up
    SystemParam.create!(name:'Yandex Access Token', code:'yandex_access_token')
  end

  def down

  end
end


В YandexController добавим следующий код:

class YandexController < ApplicationController

  def token
    render_404 and return if params[:state] != Settings.yandex.state #проверям наш секрентый ключ, он должен совпадать
    yws  = YandexWebmasterService.new
    result = yws.authorization_code_request(params[:code]) #отправляем POST-запрос с кодом для получения токена
    log(result.code)
    if result.code == '200'
      #если все ОК получаем параметры из JSON'a
      result_params = ActiveSupport::JSON.decode(result.body)
      log(result_params)
      #сохраняем полученный токен
      sp = SystemParam.find_by(code:'yandex_access_token')
      sp.value = result_params['access_token']
      sp.expires_at = Time.at(Time.now.to_i + result_params['expires_in'])
      sp.save!
    end
    redirect_to root_url
  end

  private

  def log message
    Rails.logger.info(message)
  end

end

И в routes.rb добавим путь к нашему действию:

Rails.application.routes.draw do

  root 'articles#index'
  resources :articles
  get 'yandex/token', to: 'yandex#token'

end

Теперь давайте добавим действие отправки текста в Yandex в CRUD контроллер статей:

 def send_to_yandex
    set_article
    yws = Yandex::OriginalTextsService.new(@article.content)
    if yws.send_text
      flash[:yandex_success] = I18n.t('yandex_text.success')
    else
      error_message = I18n.t('yandex_text.errors.base')
      error_message = [error_message, I18n.t("yandex_text.errors.code_#{yws.response.code}")].join('<br />').html_safe if  [400,401,403,500].include?(yws.response.code.to_i)
      flash[:yandex_error] = error_message
    end
    redirect_to articles_path
  end

Хочу обратить внимание, что если ваш текст содержит HTML то нужно его предварительно вырезать.

В routes.rb:

Rails.application.routes.draw do

  root 'articles#index'
  resources :articles do
    post :send_to_yandex, on: :member
  end
  get 'yandex/token', to: 'yandex#token'

end

В файл ru.yml добавим писание ошибок:

ru:
  yandex_text:
    success: "Yandex! Текст успешно добавлен"
    errors:
       base: "Yandex! Текст не был добавлен! При добавлении возникли ошибки!"
       code_400: 'Текст содержит недопустимые символы или недостаточной длины'
       code_403: 'Текст содержит недопустимые символы или недостаточной длины'
       code_401: 'Ошибка авторизации'
       code_500: 'Ошибка сервера'

Во вьюху списка статей(/views/articles/index) добавим кнопку отправки в yandex:

link_to 'Send to Yandex Original Texts', send_to_yandex_article_path(article), method: :post, data: { confirm: 'Are you sure?' }

Ну вот и все! Токен обновляется, статьи добавляются. Надеюсь, что описал все достаточно подробно. Весь код пиложения можно скачать на github. Статья на хабре, которая помогла мне разобраться — http://habrahabr.ru/post/157753/.