В эти выходные на конференции я обнаружил, что у 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 просмотров, и если вы все попробуете это, он быстро истечет. Я, конечно, не виню Нью-Йорк Таймс за это, но имейте это в виду, если вы попробуете это. У меня нет хороших отчетов об ошибках (то есть «любая»), поэтому проверьте консоль. Потому что я предполагаю , что я буду превышу предел, вот видео его в действии.



