Лимит
Има такова ограничение в LinkedIn — . Доста вероятно е, че вие, като мен до неотдавна, никога не сте се сблъсквали с него и не сте чували за него.

Същността на лимита е, че ако използвате търсенето на хора извън вашите контакти твърде често (нямате точни метрики, алгоритъмът решава на базата на вашите действия — колко често и много търсите, добавяте хора), то резултатът от търсенето ще бъде ограничен до три профила вместо 1000 (по подразбиране 100 страници по 10 профила на страница). Лимитът се нулира в началото на всеки месец. Разбира се, .
Но неотдавна, за един личен проект, започнах да експериментирам много с търсенето в LinkedIn и изведнъж получих това ограничение. Разбира се, не ми хареса, защото не го използвах за търговски цели, затова първата ми мисъл беше да проуча ограничението и да опитам да го заобиколя.
[Важно уточнение — материалите в статията са представени единствено за ознакомителни и обучителни цели. Авторът не подканя тяхното използване за търговски цели.]
Изучаваме проблема
Имаме: вместо десет профила с пагинация, търсенето дава само три, след които идва блок с „препоръка“ за премиум акаунт, а по-долу следват размазани и некликаеми профили.
Веднага ръката се търси в конзолата на разработчика, за да се видят тези скрити профили — може би можем да премахнем някакви стилове, които правят размазването, или да извлечем информация от блока в разметката. Но, напълно очаквано, тези профили са само и не съхраняват никаква информация.

Добре, сега да видим в таба Network и да проверим наистина ли се задейства алтернативното извеждане на резултатите от търсенето, което връща само три профила. Намираме ни интересния запит за “/api/search/blended” и виждаме отговора.

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

Следващите 12 са реални профили – резултати от търсенето, от които ще ни покажат само трите. Както може да се предполага, показва само тези, за които идва допълнителна информация (първите три обекта). Например, ако вземем отговора от профила без лимит, ще получим 28 единици – 10 обекта с допълнителна информация и 18 профила.
Отговор за профил без лимит

Защо идват повече от 10 профила, когато се изискват точно 10, и те не участват в показването, дори на следващата страница няма да ги има – все още не знам. Ако анализираме URL на заявката, можем да видим, че count=10 (колко профила да се върнат в отговора, максимум 49).
Ще се радвам на всякакви коментари по този въпрос.
Експериментираме
Добре, най-важното, което сега знаем – профилите идват в отговора повече, отколкото ни показват. Значи можем да извлечем повече данни, независимо от лимита. Нека опитаме да извлечем API сами, директно от консолата, с помощта на fetch.

Очаквано, получаваме грешка, 403. Това е свързано с безопасността, тук не изпращаме CSRF токен (. В две думи – към всяка заявка се добавя уникален токен, който се проверява на сървъра за автентичност).

Може да бъде копиран от всяка друга успешна заявка или от бисквитки, където се съхранява в полето ‘JSESSIONID’.
Къде да намеря токенаЗаглавие на друга заявка:

Или от бисквитките, направо през консолата:

Опитваме отново, този път предаваме във fetch настройките, в които посочваме като параметър в заглавката нашия csrf-токен.

Успех, получаваме всичките 10 профила. :tada:
Заради разликите в заглавките, структурата на отговора малко се различава от това, което идва в оригиналната заявка. Може да получим същата структура, ако добавим ‘Accept: ‘application/vnd.linkedin.normalized+json+2.1’, в нашия обект, до CSRF токена.
Пример за отговор с добавено заглавие
Какво следва?
След това можем да редактираме (ръчно или автоматизирано) параметъра `start`, който указва индекса, от който ще ни дадат 10 профила (по подразбиране = 0) от всичките резултати от търсенето. С други думи, увеличавайки го с 10 след всяка заявка, получаваме обикновена странична лента, по 10 профила на куп.
В този етап разполагах с достатъчно данни и свобода, за да продължа работа по личния проект. Но би било грешка да не опитам да визуализирам тези данни на място, след като ги имам. Няма да навлизам в Ember, който се използва на фронтенда. На сайта беше включен jQuery и, като си припомня знанията за основния синтаксис, може за няколко минути да създам следното.
Код на jQuery
/* рендер блока, принимаем данные профиля и вставляем блок в список профилей используя эти данные */
const createProfileBlock = ({ headline, publicIdentifier, subline, title }) => {
$('.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="/bg/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="/bg/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>`
);
};
// Извличаме API, получаваме данни и рендерим профилите
const fetchProfiles = () => {
// токен
const csrf = 'ajax:9082932176494192209';
// обект с настройки на запитването, предаваме токена
const settings = { headers: { 'csrf-token': csrf } }
// URL на запитването, с динамичен индекс за начало в края
const url = `https://www.linkedin.com/voyager/api/search/blended?count=10&filters=List(geoRegion-jp0,network-S,resultType-PEOPLE)&origin=FACETED_SEARCH&q=all&queryContext=List(spellCorrectionEnabled-true,relatedSearchesEnabled-true)&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">Зареди повече</button>');
// добавяме функционалност на бутона
$('#load-more').addClass('artdeco-button').on('click', fetchProfiles);
// задаваме по подразбиране индекс на профила за запитването
window.nextItemIndex = 0;
Ако изпълните това директно в конзолата на страницата с търсене, то ще добави бутон, който зарежда 10 нови профила при всяко натискане и ще ги визуализира в списък. Разбира се, преди това трябва да смените токена и URL адреса на необходимите. Профилният блок ще съдържа име, длъжност, местоположение, линк към профила и изображение-заглушка.

Заключение
По този начин, с минимални усилия, успяхме да намерим уязвимост и да възстановим търсенето без ограничения. Достатъчно беше да анализираме данните и техния маршрут, да се загледаме в самия запит.
Не мога да кажа, че това е сериозен проблем за LinkedIn, тъй като не представлява заплаха. Най-много, това е загубена печалба заради подобни "заобиколки", позволяващи да не се плаща за премиум. Възможно е, такъв отговор от сървъра да е необходим за коректната работа на други части от сайта, или просто е мързел на разработчиците, недостатък на ресурсите, който не позволява да се направи добре. (Ограничението е въведено през януари 2015 година, преди това лимит нямаше).
P.S.
Естествено, кодът на jQuery е доста примитивен пример за възможностите. В момента съм създал разширение за браузър за собствените си нужди. То добавя контролните бутони и визуализира пълни профили с изображения, бутон за покана и общи свързвания. Плюс динамично събира филтри за местоположения, компании и други, извлича токена от бисквитките. Така че не е нужно да се хардкодва нищо. А също така добавя допълнителни полета за настройки, а-ля "колко профила да се заявят наведнъж, до 49".

Все още работя над това разширение и планирам да го публикувам с отворен достъп. Пишете, ако ви интересува.
Източник: habr.com


