Хабрахабр

[Из песочницы] Обходим лимит поиска LinkedIn, играя с API

Лимит

Есть на LinkedIn такое ограничение — Лимит коммерческого использования. Крайне вероятно, что вы, как и я до недавнего времени, никогда не сталкивались и не слышали о нем.

Лимит сбрасывается в начале каждого месяца. Суть лимита в том, что если вы используете поиск людей вне ваших контактов слишком часто (точных метрик нет, решает алгоритм, на основе ваших действий — как часто и много искали, добавляли людей), то результат поиска будет ограничен тремя профилями, вместо 1000 (по умолчанию 100 страниц, по 10 профилей на страницу). Естественно, премиум аккаунты такого ограничения не имеют.

Естественно, такое мне не очень понравилось, ведь я не использовал его в каких-либо коммерческих целях, поэтому первой мыслью было изучить ограничение и попытаться его обойти.
[Важное уточнение — материалы в статье представлены исключительно в ознакомительных и обучающих целях. Но не так давно, для одного пет-проекта, я начал много играться с поиском на LinkedIn и внезапно получил это ограничение. Автор не поощряет их использование в коммерческих целях.]

Изучаем проблему

Имеем: вместо десяти профилей с пагинацией, поиск выдает только три, после которых вставляется блок с “рекомендацией” премиум аккаунта и ниже идут размытые и не кликабельные профили.

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

Находим интересующий нас запрос к “/api/search/blended” и смотрим на ответ. Хорошо, теперь посмотрим во вкладку Network и проверим, действительно ли срабатывает альтернативная выдача результатов поиска, возвращающая только три профиля.

В данном случае, первые три из них — объекты с дополнительной информацией, каждый объект содержит информацию по конкретному профилю (например, является ли профиль премиумом). Профили приходят в массиве `included`, но сущностей в нем аж 15.

Как уже можно догадаться, показывает только тех, на кого приходит дополнительная информация (первые три объекта). Последующие 12 это реальные профили — результаты поиска, из которых нам покажут только три. информацией и 18 профилей. Например, если взять ответ с профиля без лимита, то придет 28 сущностей — 10 объектов с доп.

Ответ для профиля без лимита


Если проанализировать урл запроса то можно увидеть, что count=10 (сколько профилей вернуть в ответе, максимум 49). Почему профилей приходит больше 10, хотя запрашивается именно 10, и они никак не участвуют в отображении, даже на следующей странице их не будет — пока не знаю.

Буду рад любым коментариям по этому поводу.

Экспериментируем

Хорошо, самое главное мы теперь точно знаем — профилей приходит в ответе больше, чем нам показывают. Значит мы можем достать больше данных, не смотря на лимит. Давайте попробуем дернуть апи сами, прямо из консоли, при помощи fetch.

Это связано с безопасностью, здесь мы не отсылаем CSRF токен (CSRF на Википедии. Ожидаемо, получаем ошибку, 403. Если в двух словах — к каждому запросу добавляется уникальный токен, который проверяется на сервере на подлинность).

Его можно скопировать из любого другого успешного запроса или же из cookies, где он хранится в поле ‘JSESSIONID’.

Где найти токен

Заголовок другого запросa:

Или из куки, прямо через консоль:

Пробуем еще раз, в этот раз передаем в fetch настройки, в которых указываем параметром в header наш csrf-token.

:tada: Успех, нам приходят все 10 профилей.

Можно получить такую же структуру, если добавить 'Accept: 'application/vnd.linkedin.normalized+json+2. Из-за разницы заголовков структура ответа немного отличается от того, что в приходит в оригинальном запросe. 1', к нам в объект, рядом с csrf токеном.

Пример ответа с добавленным заголовком

Больше о заголовке Accept

Что дальше?

Дальше можно редактировать (руками или автоматизировать) параметр `start`, указывающий на индекс, начиная с которого нам отдадут 10 профилей (по-умолчанию = 0) из всего результата поиска. Иначе говоря, инкрементируя его на 10 после каждого запроса, у нас получается обычная постраничная выдача, по 10 профилей за раз.

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

Код на jQuery

/* рендер блока, принимаем данные профиля и вставляем блок в список профилей используя эти данные */
const createProfileBlock = () => { $('.search-results__list').append( `<li class="search-result search-result__occluded-item ember-view"> <div class="search-entity search-result search-result--person search-result--occlusion-enabled ember-view"> <div class="search-result__wrapper"> <div class="search-result__image-wrapper"> <a class="search-result__result-link ember-view" href="/in/${publicIdentifier}/"> <figure class="search-result__image"> <div class="ivm-image-view-model ember-view"> <img class="lazy-image ivm-view-attr__img--centered EntityPhoto-circle-4 presence-entity__image EntityPhoto-circle-4 loaded" src="http://www.userlogos.org/files/logos/give/Habrahabr3.png" /> </div> </figure> </a> </div> <div class="search-result__info pt3 pb4 ph0"> <a class="search-result__result-link ember-view" href="/in/${publicIdentifier}/"> <h3 class="actor-name-with-distance search-result__title single-line-truncate ember-view"> ${title.text} </h3> </a> <p class="subline-level-1 t-14 t-black t-normal search-result__truncate">${headline.text}</p> <p class="subline-level-2 t-12 t-black--light t-normal search-result__truncate">${subline.text}</p> </div> </div> </div> <li>` );
}; // дергаем апи, получаем данные и рендерим профили
const fetchProfiles = () => { // токен const csrf = 'ajax:9082932176494192209'; // объект с настройками запроса, передаем токен const settings = { headers: { 'csrf-token': csrf } } // урл запроса, с динамическим индексом старта в конце const url = `https://www.linkedin.com/voyager/api/search/blended?count=10&filters=List(geoRegion-%3Ejp%3A0,network-%3ES,resultType-%3EPEOPLE)&origin=FACETED_SEARCH&q=all&queryContext=List(spellCorrectionEnabled-%3Etrue,relatedSearchesEnabled-%3Etrue)&start=${nextItemIndex}`; /* делаем запрос, для каждого профиля в ответе вызываем рендер блока, и после инкрементируем стартовый индекс на 10 */ fetch(url, settings).then(response => response.json()).then(data => { data.elements[0].elements.forEach(createProfileBlock); nextItemIndex += 10;
});
}; // удаляем все профили из списка
$('.search-results__list').find('li').remove();
// вставляем кнопку загрузки профилей
$('.search-results__list').after('<button id="load-more">Load More</button>');
// добавляем функционал на кнопку
$('#load-more').addClass('artdeco-button').on('click', fetchProfiles); // ставим по умолчания индекс профиля для запроса
window.nextItemIndex = 0;

Если выполнить это прямо в консоли на странице поиска, то это добавит кнопку, загружающую 10 новых профилей при каждом нажатии, и рендярещее их списком. Конечно, токен и урл перед этим поменять на необходимый. Блок профиля будет содержать имя, должность, локацию, ссылку на профиль и картинку-заглушку.

Заключение

Таким образом, при минимуме усилий, мы смогли найти уязвимое место и вернуть себе поиск без ограничений. Достаточно было проанализировать данные и их путь, заглянуть в сам запрос.

Максимум, это потерянная прибыль из-за подобных «обходов», позволяющая не платить за премиум. Я не могу сказать что это является серьезной проблемой для LinkedIn, потому что никакой угрозы не несет. (Ограничение появилось с января 2015 года, до этого лимита не было). Возможно, такой ответ сервера необходим для корректной работы других частей сайта, или же это просто лень разработчиков недостаток ресурсов, не позволяющий сделать хорошо.

P.S.

Ествественно, код на jQuery довольно примитивный пример возможностей. В данный момент я создал extension для браузера под свои нужды. Он добавляет кнопки контроля и рендерит полноценные профили с картинками, кнопкой приглашения и общими коннектами. Плюс динамически собирает фильтры локаций, компаний и прочего, достает токен из куки. Так что ничего хардокдить уже не нужно. Ну и добавляет дополнительные поля настроек, а-ля «сколько профилей запрашивать за раз, до 49».

Пишите если вам интересно. Над этим дополнением я все еще работаю и в планах выложить его в открытый доступ.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть