Límite
Hay una limitación en LinkedIn — . Es muy probable que tú, como yo hasta hace poco, nunca te hayas encontrado ni hayas oído hablar de ello.

La esencia del límite es que si usas la búsqueda de personas fuera de tus contactos demasiado a menudo (no hay métricas exactas, lo decide el algoritmo, basado en tus acciones — cuán a menudo y cuánto has buscado, añadido personas), entonces el resultado de búsqueda estará limitado a tres perfiles, en lugar de 1000 (por defecto 100 páginas, 10 perfiles por página). El límite se restablece al comienzo de cada mes. Naturalmente, .
Pero no hace mucho, para un proyecto personal, comencé a jugar bastante con la búsqueda en LinkedIn y de repente obtuve esta limitación. Naturalmente, no me agradó mucho, ya que no lo estaba utilizando con fines comerciales, por lo que el primer pensamiento fue investigar la limitación e intentar eludirla.
[Una aclaración importante: los materiales en este artículo se presentan únicamente con fines informativos y educativos. El autor no fomenta su uso con fines comerciales.]
Examinamos el problema
Tenemos: en lugar de diez perfiles con paginación, la búsqueda devuelve solo tres, después de los cuales se inserta un bloque con la “recomendación” de la cuenta premium y a continuación aparecen perfiles difusos y no clicables.
Inmediatamente, la mano se dirige a la consola del desarrollador para ver estos perfiles ocultos — tal vez podamos eliminar algunos estilos que aplican el desenfoque, o extraer información del bloque en el marcado. Pero, como era de esperar, estos perfiles son solo y no almacenan ninguna información.

Bien, ahora veamos la pestaña Network y verifiquemos si realmente se activa la alternativa de resultados de búsqueda, que devuelve solo tres perfiles. Encontramos la consulta que nos interesa a “/api/search/blended” y observamos la respuesta.

Los perfiles llegan en un array `included`, pero hay 15 entidades en él. En este caso, los primeros tres de ellos son objetos con información adicional, cada objeto contiene información sobre un perfil específico (por ejemplo, si el perfil es premium).

Los siguientes 12 son perfiles reales: resultados de búsqueda, de los cuales solo se mostrarán tres. Como ya se puede adivinar, solo se muestra a aquellos para los que llega información adicional (los primeros tres objetos). Por ejemplo, si tomamos la respuesta de un perfil sin límite, se obtendrán 28 entidades: 10 objetos con información adicional y 18 perfiles.
Respuesta para perfil sin límite

Por qué llegan más de 10 perfiles, aunque se soliciten precisamente 10, y no participan en la visualización, incluso en la siguiente página no estarán — aún no lo sé. Si analizamos la URL de la solicitud, podemos ver que count=10 (cuántos perfiles devolver en la respuesta, máximo 49).
Estaré encantado de cualquier comentario al respecto.
Estamos experimentando
Bien, lo más importante que ahora sabemos con certeza es que llegan más perfiles en la respuesta de los que nos muestran. Esto significa que podemos obtener más datos, a pesar del límite. Vamos a intentar llamar a la API nosotros mismos, directamente desde la consola, usando fetch.

Como era de esperar, recibimos un error, 403. Esto está relacionado con la seguridad; aquí no enviamos el token CSRF (). En pocas palabras, a cada solicitud se le agrega un token único que se verifica en el servidor por autenticidad.

Se puede copiar de cualquier otra solicitud exitosa o de las cookies, donde se almacena en el campo 'JSESSIONID'.
Dónde encontrar el tokenEncabezado de otra solicitud:

O de las cookies, directamente a través de la consola:

Probemos de nuevo, esta vez pasamos en fetch la configuración, en la que indicamos como parámetro en el encabezado nuestro csrf-token.

Éxito, recibimos todos los 10 perfiles. :tada:
Debido a la diferencia en los encabezados, la estructura de la respuesta es un poco diferente de la que se recibe en la solicitud original. Se puede obtener la misma estructura si se agrega 'Accept: 'application/vnd.linkedin.normalized+json+2.1' a nuestro objeto, junto con el token csrf.
Ejemplo de respuesta con encabezado agregado
¿Qué sigue?
A continuación, se puede editar (manualmente o automatizar) el parámetro `start`, que indica el índice a partir del cual se nos devolverán 10 perfiles (por defecto = 0) de todo el resultado de búsqueda. En otras palabras, incrementándolo en 10 después de cada solicitud, obtendremos una entrega paginada habitual, 10 perfiles a la vez.
En esta etapa, tenía suficientes datos y libertad para continuar trabajando en mi proyecto personal. Pero sería un error no intentar mostrar estos datos прямо en el lugar, ya que los tenía a mano. No vamos a profundizar en Ember, que se utiliza en el front-end. En el sitio se había integrado jQuery, y recordando los conocimientos básicos de la sintaxis, se puede crear lo siguiente en un par de minutos.
Código en 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="/es/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="/es/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>`
);
};
// Llamamos a la API, obtenemos datos y renderizamos perfiles
const fetchProfiles = () => {
// token
const csrf = 'ajax:9082932176494192209';
// objeto con configuraciones de la solicitud, pasamos el token
const settings = { headers: { 'csrf-token': csrf } }
// URL de la solicitud, con índice dinámico de inicio al final
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}`;
/* hacemos la solicitud, para cada perfil en la respuesta llamamos renderizar bloque, y después incrementamos el índice inicial en 10 */
fetch(url, settings).then(response => response.json()).then(data => {
data.elements[0].elements.forEach(createProfileBlock);
nextItemIndex += 10;
});
};
// eliminamos todos los perfiles de la lista
$('.search-results__list').find('li').remove();
// insertamos el botón de carga de perfiles
$('.search-results__list').after('<button id="load-more">Cargar más</button>');
// agregamos funcionalidad al botón
$('#load-more').addClass('artdeco-button').on('click', fetchProfiles);
// establecemos el índice de perfil para la solicitud por defecto
window.nextItemIndex = 0;
Si se ejecuta esto directamente en la consola en la página de búsqueda, se agregará un botón que carga 10 nuevos perfiles con cada clic y los renderiza en una lista. Por supuesto, el token y la URL deben cambiarse a los necesarios antes de esto. El bloque de perfil contendrá el nombre, el cargo, la ubicación, el enlace al perfil y una imagen de marcador de posición.

Conclusión
De esta manera, con un mínimo esfuerzo, logramos encontrar una vulnerabilidad y recuperar la búsqueda sin restricciones. Solo fue necesario analizar los datos y su ruta, y mirar la propia solicitud.
No puedo decir que esto sea un problema serio para LinkedIn, porque no representa una amenaza. En el mejor de los casos, es una pérdida de ganancias debido a este tipo de "workarounds" que permiten evitar pagar por premium. Tal vez, esta respuesta del servidor sea necesaria para el correcto funcionamiento de otras partes del sitio, o simplemente sea pereza de los desarrolladores, una falta de recursos que no permite hacer las cosas bien. (La limitación se impuso desde enero de 2015, antes de eso no había límite).
P.D.
Por supuesto, el código en jQuery es un ejemplo bastante primitivo de lo que se puede hacer. En este momento, he creado una extensión para el navegador para mis necesidades. Añade botones de control y renderiza perfiles completos con imágenes, un botón de invitación y conexiones en común. Además, recopila dinámicamente filtros de ubicaciones, empresas y más, extrayendo el token de las cookies. Así que ya no es necesario codificar nada a mano. También agrega campos adicionales de configuración, como "cuántos perfiles solicitar a la vez, hasta 49".

Todavía estoy trabajando en este complemento y tengo planes de hacerlo público. Escríbeme si estás interesado.
Fuente: habr.com


