通过 API 绕过 LinkedIn 的搜索限制

限制

LinkedIn上有这样一个限制—— 商业用途限制。 您很可能像我一样,直到最近才遇到或听说过它。

通过 API 绕过 LinkedIn 的搜索限制

限制的本质是,如果您过于频繁地搜索联系人之外的人(没有确切的指标,算法根据您的操作来决定 - 您搜索的频率和数量、添加的人),那么搜索结果将限制为三个配置文件,而不是 1000 个(默认 100 页,每页 10 个配置文件)。 该限额在每个月初重置。 自然, 高级帐户没有此限制.

但不久前,为了一个喜欢的项目,我开始大量使用 LinkedIn 搜索,突然遇到了这个限制。 当然,我不太喜欢这个,因为我没有将它用于任何商业目的,所以我的第一个想法是研究它的限制并尝试绕过它。

[重要的澄清:本文中的材料仅供参考和教育目的。 作者不鼓励将其用于商业目的。]

我们正在研究这个问题

我们有:搜索只返回三个,而不是十个带分页的配置文件,之后插入一个带有高级帐户“推荐”的块,下面是模糊且不可点击的配置文件。

立即,手伸向开发人员控制台来查看这些隐藏的配置文件 - 也许我们可以删除一些模糊的样式,或者从标记中的块中提取信息。 但是,不出所料,这些配置文件只是 占位符图片 并且不存储任何信息。

通过 API 绕过 LinkedIn 的搜索限制

好的,现在让我们看看“网络”选项卡,检查仅返回三个配置文件的替代搜索结果是否确实有效。 我们找到我们感兴趣的“/api/search/blished”请求并查看响应。

通过 API 绕过 LinkedIn 的搜索限制

配置文件位于“included”数组中,但其中已经有 15 个实体。在本例中,前三个是具有附加信息的对象,每个对象包含有关特定配置文件的信息(例如,配置文件是否是高级的) )。

通过 API 绕过 LinkedIn 的搜索限制

接下来的 12 个是真实的个人资料 - 搜索结果,其中只有三个会显示给我们。 正如您已经猜到的,它仅显示那些接收附加信息的对象(前三个对象)。 例如,如果您从无限制的配置文件中获取答案,您将收到 28 个实体 - 10 个附加对象。 信息和 18 个配置文件。

个人资料答案无限制通过 API 绕过 LinkedIn 的搜索限制
通过 API 绕过 LinkedIn 的搜索限制

为什么有超过 10 个配置文件到达,尽管正好请求了 10 个,并且它们不以任何方式参与显示,甚至在下一页上它们也不会参与显示 - 我还不知道。 如果分析请求 URL,您可以看到 count=10(响应中返回的配置文件数量,最多 49)。

通过 API 绕过 LinkedIn 的搜索限制

我很高兴收到有关此事的任何评论。

我们来实验一下

好吧,我们现在确定的最重要的事情是,响应中的个人资料比他们向我们展示的要多。 这意味着尽管有限制,我们仍可以获得更多数据。 让我们尝试使用 fetch 直接从控制台自行提取 API。

通过 API 绕过 LinkedIn 的搜索限制

正如预期的那样,我们收到错误 403。这是由于安全原因,这里我们不发送 CSRF 令牌(维基百科上的 CSRF。 简而言之,每个请求都会添加一个唯一的令牌,并在服务器上检查其真实性)。

通过 API 绕过 LinkedIn 的搜索限制

它可以从任何其他成功的请求或 cookie 中复制,存储在“JSESSIONID”字段中。

在哪里可以找到令牌另一个请求的标头:

通过 API 绕过 LinkedIn 的搜索限制

或者从cookies中,直接通过控制台:

通过 API 绕过 LinkedIn 的搜索限制

让我们再试一次,这次我们将设置传递给 fetch,其中我们将 csrf-token 指定为标头中的参数。

通过 API 绕过 LinkedIn 的搜索限制

成功,我们收到全部 10 份个人资料。 :田田:

由于标头的不同,响应的结构与原始请求中收到的结构略有不同。 如果将“Accept: 'application/vnd.linkedin.normalized+json+2.1”添加到我们的对象中(csrf 令牌旁边),您可以获得相同的结构。
添加标头的示例响应通过 API 绕过 LinkedIn 的搜索限制

有关 Accept 标头的更多信息

接下来是什么?

然后,您可以编辑(手动或自动)“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="/zh-CN/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="/zh-CN/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 个新配置文件并将它们呈现在列表中。 当然,在执行此操作之前,请将令牌和 URL 更改为所需的。 个人资料块将包含姓名、职位、位置、个人资料链接和占位符图像。

通过 API 绕过 LinkedIn 的搜索限制

结论

因此,我们能够以最小的努力找到弱点并不受限制地重新进行搜索。 分析数据及其路径、研究请求本身就足够了。

我不能说这对 LinkedIn 来说是一个严重的问题,因为它不会构成任何威胁。 这种“变通办法”最大的损失就是利润损失,它可以让您避免支付溢价。 也许这样的服务器响应对于网站其他部分的正确操作是必要的,或者这只是开发人员的懒惰和缺乏资源而不允许它做得很好。 (该限制于2015年XNUMX月出现;在此之前没有限制)。

PS

当然,jQuery 代码是这些功能的一个相当原始的示例。 目前我已经创建了一个浏览器扩展来满足我的需求。 它添加了控制按钮并呈现带有图片、邀请按钮和一般连接的完整配置文件。 此外,它还动态收集位置、公司和其他事物的过滤器,并从 cookie 中检索令牌。 因此无需再对任何内容进行硬编码。 嗯,它添加了额外的设置字段,例如“一次请求多少个配置文件,最多 49 个”。

通过 API 绕过 LinkedIn 的搜索限制

我仍在致力于此添加,并计划将其向公众发布。 有兴趣就写吧。

来源: habr.com

添加评论