Статьи

Использование API New York Times для составления графика вхождений в заголовках

В эти выходные на конференции я обнаружил, что у New York Times довольно глубокий API для разработчиков . Он охватывает как данные их газет, так и правительственную и региональную информацию. Я немного поэкспериментировал с этим и нашел, что его очень легко использовать через JavaScript (я не думаю, что они это документируют, но они используют CORS, так что вы можете пропустить JSONP), и решил, что постараюсь провести небольшой эксперимент. Что если бы мы могли использовать API для отображения количества случаев, когда ключевое слово появлялось в заголовках с течением времени?

Я начал с просмотра документации API для поиска статей и обнаружил, что они позволяют сузить ваш поисковый запрос до определенной части статьи. Я начал с простого примера, который просто искал во всей базе данных термин в заголовке.

$.get("http://api.nytimes.com/svc/search/v2/articlesearch.json", {
    "api-key":API,
    sort:"oldest",
    fq:"headline:(\""+term+"\")",
    fl:"headline,snippet,multimedia,pub_date"}, function(res) {
    console.dir(res.response);
  }, "JSON");

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

Хорошо, это на полпути. Следующим шагом было ограничение результатов одним календарным годом. Опять же, API делает это чертовски простым. Я написал функцию, которая принимала год и поисковый запрос и выполняла поиск.

function fetchForYear(year, term) {
  //YYYYMMDD
  var startYearStr = year + "0101";
  var endYearStr = year + "1231";
  console.log('doing year '+year);
  
  return $.get("http://api.nytimes.com/svc/search/v2/articlesearch.json", {
    "api-key":API,
    sort:"oldest",
    begin_date:startYearStr,
    end_date:endYearStr,
    fq:"headline:(\""+term+"\")",
    fl:"headline,snippet,multimedia,pub_date"}, function(res) {
      //Ok, currently assume a good response
      //todo - check the response
      //console.dir(res.response);
      totalDone++;
  }, "JSON");
  
}

Обратите внимание, что я возвращаю обещание, сгенерированное обработчиком Ajax jQuery. Зачем? Ну, мне нужно выполнить большое количество вызовов Ajax. Чтобы справиться со знанием того, когда все они сделаны, я могу объединить их в массив и ждать, пока все они закончат. Обещания серьезно подходят для подобных вещей.

function search() {
  var term = $search.val();
  if(term === '') return;
  totalDone = 0;
  
  console.log("Searching for "+term);
  var currentYear = START_YEAR;

  var promises = [];
  
  //Gather up all the promises...
  for(var i=START_YEAR; i<=END_YEAR; i++) {
    promises.push(fetchForYear(i,term));  
  }
  
  //And when done, lets graph this!
  $.when.apply($, promises).done(function() {
    console.log('DONE');
    console.dir(arguments);
  });
  
}

Код выше работал отлично. Я проверил его на множестве звонков, и он правильно обработал, дожидаясь их завершения и предоставив мне легкий доступ ко всем результатам. Тогда я столкнулся с проблемой. Я изменил свои данные, чтобы перейти от 10 звонков к 100, а затем включил ограничение скорости API NYT. По какой-то глупой причине (и, честно говоря, это действительно глупо), Нью-Йорк Таймс отказывается сообщать вам, каковы их пределы скорости, пока вы не зарегистрируетесь и не создадите приложение. Теперь вы можете зарегистрироваться бесплатно, поэтому ничего не стоит, но для этого нет веских причин!Если вы предоставляете API, будьте предельно откровенны с вашими ограничениями. Хорошо, напыщенная речь сделана. Таким образом, их предел составляет 10 в секунду. Это бросило меня в тупик. Мне известен код, который будет ограничивать количество вызовов за период времени, но эти методы убивают (насколько мне известно) «лишние» вызовы. Я не смог найти библиотеку, которая говорила: «Если я дам вам X в период времени Y, а предел будет немного меньше, просто сдвиньте остаток до Y». Последователь в Твиттере рекомендовал просто создать рекурсивную функцию и добавить тайм-аут, так что я выбрал этот подход.

Я построил функцию с именем processSets, которая в основном смотрит на мои желаемые данные как набор … хороших наборов. Учитывая, что мне нужно более 100 лет данных, и учитывая, что NYT требует не более 10 обращений в секунду, я подумал, что делать 10 попаданий одновременно, а затем рекурсивное обращение к себе по таймауту может сработать. Я получил это работает, хотя мне пришлось использовать несколько глобальных переменных, и я чувствую, что это немного грязно. Я должен действительно абстрагировать это в модуле. На данный момент, хотя, это работает. Вот мое текущее решение.

/*
Given an array of data, I process X items async at a time.
When done, I see if I need to do more, and if so, I call it in
Y miliseconds. The idea being I do chunks of aysnc requests with
a 'pad' between them to slow down the requests.
*/
var globalData;
var searchTerm;
var currentYear = START_YEAR;
var PER_SET = 10;
function processSets() {
  var promises = [];
  for(var i=0;i<PER_SET;i++) {
    var yearToGet = currentYear + i;
    if(yearToGet <= END_YEAR) {
      promises.push(fetchForYear(yearToGet,searchTerm));
    }
  }
  $.when.apply($, promises).done(function() {
    console.log('DONE with Set '+promises.length);
    
    //update progress
    var percentage = Math.floor(totalDone/(END_YEAR-START_YEAR)*100);
    $progress.text("Working on data, "+percentage +"% done.");
    
    //massage into something simpler
    // handle cases where promises array is 1
    if(promises.length === 1) {
      var toAddRaw = arguments[0];
      globalData.push({
        year:currentYear,
        results:toAddRaw.response.meta.hits
      });     
    } else {
      for(var i=0,len=arguments.length;i<len;i++) {
        var toAddRaw = arguments[i][0];
        var year = currentYear+i;
        globalData.push({
          year:year,
          results:toAddRaw.response.meta.hits
        });
      }
    }
    currentYear += PER_SET;

    //Am I done yet?
    if(currentYear <= END_YEAR) {
      setTimeout(processSets, 900);
    } else {
      $progress.text("");
      render(globalData); 
    }
  });

}

function search() {
  var term = $search.val();
  if(term === '') return;
  totalDone = 0;
  
  console.log("Searching for "+term);

  globalData = [];
  searchTerm = term;
  $progress.text("Beginning work...");
  processSets();
}

Надеюсь, в этом есть какой-то смысл. Опять же, я уверен, что некоторые из моих читателей разорвут мне новый рассказ о том, как я это сделал, но это версия 0, так что будьте добры. 😉 Финальная часть головоломки представляла результат. Я начал пытаться использовать Chart.js , но мне не удалось заставить его визуализировать ось гистограммы / линейной диаграммы с меньшим числом тиков, чем в моем наборе данных. Затем я переключился на Highcharts . Я смог найти демо довольно чертовски близко к тому, что я хотел, но было немного трудно понять некоторые из их документации. На самом деле, у меня ушло почти столько же времени, чтобы правильно составить график, как и до того, как я преодолел проблему «10 в секунду». Highcharts очень сложен, или я очень устал, но я смог заставить его работать. Один оченьклассная часть их документов в том, что у них есть ссылки на JSFiddle. На самом деле, именно так я наконец-то решил свою проблему. Я использовал одну из их скрипок, модифицировал ее и понял, что мне нужно сделать. Конечный результат впечатляет, я думаю. Помните, что я не дизайнер, поэтому, наверное, это можно сделать лучше, но мне действительно нравится внешний вид.

Во-первых, пример поиска в интернете. Нажмите для увеличения изображения.

А вот и поиск войны. Опять же, нажмите для увеличения.

Простите за опечатку в заголовке — очевидно, мои навыки правописания лишь немного хуже, чем моя способность хорошо одеваться. Итак — хотите поиграть с этим? Я прикрепил почтовый индекс моего кода к этой записи в блоге, за исключением моего ключа API. Я также собираюсь дать ссылку на демоверсию, но учтите, что у меня разрешено посещать только 10 тысяч обращений к API в день. Мой средний пост в блоге получает около 1000 просмотров, и если вы все попробуете это, он быстро истечет. Я, конечно, не виню Нью-Йорк Таймс за это, но имейте это в виду, если вы попробуете это. У меня нет хороших отчетов об ошибках (то есть «любая»), поэтому проверьте консоль. Потому что я предполагаю , что я буду превышу предел, вот видео его в действии.

Скачать прикрепленный файл