」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 優化網頁抓取:使用 JSDOM 抓取身份驗證數據

優化網頁抓取:使用 JSDOM 抓取身份驗證數據

發佈於2024-11-08
瀏覽:185

作为抓取开发人员,我们有时需要提取临时密钥等身份验证数据来执行我们的任务。然而,事情并没有那么简单。通常,它位于 HTML 或 XHR 网络请求中,但有时,会计算身份验证数据。在这种情况下,我们可以对计算进行逆向工程,这需要花费大量时间来对脚本进行反混淆,或者运行计算它的 JavaScript。通常,我们使用浏览器,但这很昂贵。 Crawlee 支持并行运行浏览器抓取工具和 Cheerio Scraper,但这在计算资源使用方面非常复杂且昂贵。 JSDOM 帮助我们以比浏览器更少的资源运行页面 JavaScript,并且比 Cheerio 略高。

本文将讨论一种新方法,我们在一个 Actor 中使用该方法,从浏览器 Web 应用程序生成的 TikTok 广告创意中心获取身份验证数据,而无需实际运行浏览器,而是使用 JSDOM。

分析网站

当您访问此网址时:

https://ads.tiktok.com/business/creativecenter/inspiration/popular/hashtag/pc/en

您将看到主题标签列表,其中包含其实时排名、帖子数量、趋势图、创建者和分析。您还可以注意到,我们可以筛选行业、设置时间段,并使用复选框来筛选趋势是否新进入前 100 名。

Optimizing web scraping: Scraping auth data using JSDOM

我们的目标是使用给定的过滤器从列表中提取前 100 个主题标签。

两种可能的方法是使用 CheerioCrawler,第二种方法是基于浏览器的抓取。 Cheerio 可以更快地提供结果,但不适用于 JavaScript 渲染的网站。

Cheerio 在这里不是最佳选择,因为 Creative Center 是一个 Web 应用程序,数据源是 API,因此我们只能获取最初出现在 HTML 结构中的主题标签,而不能获取我们需要的 100 个主题标签中的每一个。

第二种方法可以使用 Puppeteer、Playwright 等库来进行基于浏览器的抓取,并使用自动化来抓取所有主题标签,但根据以前的经验,这么小的任务需要花费大量时间。

现在我们开发了新方法,使这个过程比基于浏览器的抓取要好得多,并且非常接近基于 CheerioCrawler 的抓取。

JSDOM 方法

在深入研究这种方法之前,我想感谢 Apify 的 Web 自动化工程师 Alexey Udovydchenko 开发了这种方法。向他致敬!

在此方法中,我们将对 https://ads.tiktok.com/creative_radar_api/v1/popular_trend/hashtag/list 进行 API 调用以获取所需的数据。

在调用此 API 之前,我们需要一些必需的标头(身份验证数据),因此我们将首先调用 https://ads.tiktok.com/business/creativecenter/inspiration/popular/hashtag/pad /en.

我们将通过创建一个函数来开始这种方法,该函数将为我们创建 API 调用的 URL,然后进行调用并获取数据。

export const createStartUrls = (input) => {
    const {
        days = '7',
        country = '',
        resultsLimit = 100,
        industry = '',
        isNewToTop100,
    } = input;

    const filterBy = isNewToTop100 ? 'new_on_board' : '';
    return [
        {
            url: `https://ads.tiktok.com/creative_radar_api/v1/popular_trend/hashtag/list?page=1&limit=50&period=${days}&country_code=${country}&filter_by=${filterBy}&sort_by=popular&industry_id=${industry}`,
            headers: {
                // required headers
            },
            userData: { resultsLimit },
        },
    ];
};

在上面的函数中,我们创建 API 调用的起始 URL,其中包含我们之前讨论过的各种参数。根据参数创建URL后,它将调用creative_radar_api并获取所有结果。

但是在我们获得标头之前它不会起作用。因此,让我们创建一个函数,该函数首先使用 sessionPool 和 proxyConfiguration 创建会话。

export const createSessionFunction = async (
    sessionPool,
    proxyConfiguration,
) => {
    const proxyUrl = await proxyConfiguration.newUrl(Math.random().toString());
    const url =
        'https://ads.tiktok.com/business/creativecenter/inspiration/popular/hashtag/pad/en';
    // need url with data to generate token
    const response = await gotScraping({ url, proxyUrl });
    const headers = await getApiUrlWithVerificationToken(
        response.body.toString(),
        url,
    );
    if (!headers) {
        throw new Error(`Token generation blocked`);
    }
    log.info(`Generated API verification headers`, Object.values(headers));
    return new Session({
        userData: {
            headers,
        },
        sessionPool,
    });
};

在此函数中,主要目标是调用 https://ads.tiktok.com/business/creativecenter/inspiration/popular/hashtag/pad/en 并获取 headers 作为返回。为了获取标头,我们使用 getApiUrlWithVerificationToken 函数。

在继续之前,我想提一下,Crawlee 使用 JSDOM Crawler 原生支持 JSDOM。它提供了一个使用普通 HTTP 请求和 jsdom DOM 实现并行抓取网页的框架。它使用原始 HTTP 请求来下载网页,数据带宽非常快且高效。

让我们看看如何创建 getApiUrlWithVerificationToken 函数:

const getApiUrlWithVerificationToken = async (body, url) => {
    log.info(`Getting API session`);
    const virtualConsole = new VirtualConsole();
    const { window } = new JSDOM(body, {
        url,
        contentType: 'text/html',
        runScripts: 'dangerously',
        resources: 'usable' || new CustomResourceLoader(),
        // ^ 'usable' faster than custom and works without canvas
        pretendToBeVisual: false,
        virtualConsole,
    });
    virtualConsole.on('error', () => {
        // ignore errors cause by fake XMLHttpRequest
    });

    const apiHeaderKeys = ['anonymous-user-id', 'timestamp', 'user-sign'];
    const apiValues = {};
    let retries = 10;
    // api calls made outside of fetch, hack below is to get URL without actual call
    window.XMLHttpRequest.prototype.setRequestHeader = (name, value) => {
        if (apiHeaderKeys.includes(name)) {
            apiValues[name] = value;
        }
        if (Object.values(apiValues).length === apiHeaderKeys.length) {
            retries = 0;
        }
    };
    window.XMLHttpRequest.prototype.open = (method, urlToOpen) => {
        if (
            ['static', 'scontent'].find((x) =>
                urlToOpen.startsWith(`https://${x}`),
            )
        )
        log.debug('urlToOpen', urlToOpen);
    };
    do {
        await sleep(4000);
        retries--;
    } while (retries > 0);

    await window.close();
    return apiValues;
};

在此函数中,我们将创建一个虚拟控制台,它使用 CustomResourceLoader 来运行后台进程并用 JSDOM 替换浏览器。

对于这个特定的示例,我们需要三个强制标头来进行 API 调用,它们是匿名用户 ID、时间戳和用户签名。

使用 XMLHttpRequest.prototype.setRequestHeader,我们检查提到的标头是否在响应中,如果是,我们获取这些标头的值,并重复重试,直到获得所有标头。

然后,最重要的部分是我们使用 XMLHttpRequest.prototype.open 提取身份验证数据并进行调用,而无需实际使用浏览器或公开机器人活动。

在 createSessionFunction 的末尾,它返回一个具有所需标头的会话。

现在来到我们的主要代码,我们将使用 CheerioCrawler 并使用 prenavigationHooks 将从前面的函数获取的标头注入到 requestHandler 中。

const crawler = new CheerioCrawler({
    sessionPoolOptions: {
        maxPoolSize: 1,
        createSessionFunction: async (sessionPool) =>
            createSessionFunction(sessionPool, proxyConfiguration),
    },
    preNavigationHooks: [
        (crawlingContext) => {
            const { request, session } = crawlingContext;
            request.headers = {
                ...request.headers,
                ...session.userData?.headers,
            };
        },
    ],
    proxyConfiguration,
});

最后,在请求处理程序中,我们使用标头进行调用,并确保需要多少次调用来获取所有数据处理分页。

async requestHandler(context) {
    const { log, request, json } = context;
    const { userData } = request;
    const { itemsCounter = 0, resultsLimit = 0 } = userData;
    if (!json.data) {
        throw new Error('BLOCKED');
    }
    const { data } = json;
    const items = data.list;
    const counter = itemsCounter   items.length;
    const dataItems = items.slice(
        0,
        resultsLimit && counter > resultsLimit
            ? resultsLimit - itemsCounter
            : undefined,
    );
    await context.pushData(dataItems);
    const {
        pagination: { page, total },
    } = data;
    log.info(
        `Scraped ${dataItems.length} results out of ${total} from search page ${page}`,
    );
    const isResultsLimitNotReached =
        counter 



这里需要注意的一件重要事情是,我们正在以可以进行任意数量的 API 调用的方式编写此代码。

在此特定示例中,我们仅发出了一个请求和一个会话,但如果需要,您可以发出更多请求。当第一个 API 调用完成时,它将创建第二个 API 调用。同样,如果需要,您可以拨打更多电话,但我们只打了两个电话。

为了让事情更清楚,代码流程如下:

Optimizing web scraping: Scraping auth data using JSDOM

结论

这种方法帮助我们获得第三种方法来提取身份验证数据,而无需实际使用浏览器并将数据传递给 CheerioCrawler。这显着提高了性能,并将 RAM 要求降低了 50%,虽然基于浏览器的抓取性能比纯 Cheerio 慢十倍,但 JSDOM 仅慢 3-4 倍,这使其比浏览器快 2-3 倍 -基于抓取。

该项目的代码库已上传至此处。代码是作为 Apify Actor 编写的;您可以在此处找到有关它的更多信息,但您也可以在不使用 Apify SDK 的情况下运行它。

如果您对此方法有任何疑问或疑问,请通过我们的 Discord 服务器联系我们。

版本聲明 本文轉載於:https://dev.to/crawlee/optimizing-web-scraping-scraping-auth-data-using-jsdom-3cji?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何存取 PHP $_GET 數組中的多值參數?
    如何存取 PHP $_GET 數組中的多值參數?
    在 PHP $_GET 陣列中存取多值參數PHP 的 $_GET 超全域數組允許存取查詢字串參數。預設情況下,當為相同參數指派多個值時,僅最後一個值會儲存在 $_GET 中。但是,可以以數組形式檢索此類值。 建立多值$_GET 參數要為查詢字串中的參數傳送多個值,只需使用方括號表示法:http://...
    程式設計 發佈於2024-11-08
  • 如何使用 javac、Ant 或 Maven 遞歸編譯多個 Java 檔案?
    如何使用 javac、Ant 或 Maven 遞歸編譯多個 Java 檔案?
    如何使用javac 遞歸編譯所有Java 檔案為每個套件使用單獨的shell 指令編譯分佈在多個套件中的大量Java 檔案可能會很乏味包裹。相反,請考慮使用以下方法之一來簡化編譯。 方法1:使用@source產生一個文字檔案(例如,sources.txt) txt),其中列出了要編譯的所有Java文...
    程式設計 發佈於2024-11-08
  • 如何使用 Python 在文件的特定位置插入一行?
    如何使用 Python 在文件的特定位置插入一行?
    在Python中的文件中間插入一行在文件中的指定位置插入一行,同時保持文件的完整性現有內容可以使用Python 的檔案處理功能來實現。 要在文件中的索引 x 處插入一行,請按照下列步驟操作:開啟檔案進行讀取。 使用 readlines() 方法將整個文件讀入行列表。 使用 insert() 方法在指...
    程式設計 發佈於2024-11-08
  • React、Vue 和 Svelte 中的 JavaScript 框架 – 選擇哪一個?
    React、Vue 和 Svelte 中的 JavaScript 框架 – 選擇哪一個?
    JavaScript 框架在过去几年中取得了显着的发展,成为现代 Web 应用程序的支柱。 2024 年,React、Vue 和 Svelte 脱颖而出,成为最受欢迎的框架,每个框架都有其独特的优点和缺点。如果您正在构建新的 Web 应用程序,选择正确的框架对于项目的成功至关重要。 在本文中,我们将...
    程式設計 發佈於2024-11-08
  • ## 當方法具有指標接收器時,我什麼時候應該避免在 Go 中複製實例?
    ## 當方法具有指標接收器時,我什麼時候應該避免在 Go 中複製實例?
    複製實例時指針接收器的重要性操作資料時,請理解按引用或按值傳遞值的細微差別至關重要。在 Go 中,方法可以使用值接收器或指標接收器來定義,理解這種選擇的含義至關重要,尤其是在複製實例時。 值接收器具有值接收器的方法對它們接收到的值的副本進行操作。方法內所做的任何修改都不會影響原始值。這確保了在複製的...
    程式設計 發佈於2024-11-08
  • 如何修改不可變 Python 字串中的單一字元?
    如何修改不可變 Python 字串中的單一字元?
    錯誤:'str'物件不支援專案分配錯誤:'str'物件不支援專案分配Python字串是不可變的,這意味著一旦創建,它們的單一字元就不能被修改修改的。嘗試直接使用項目分配來修改字元(如代碼s2[j] = s1[i] 所示)會導致錯誤「TypeError: 'st...
    程式設計 發佈於2024-11-08
  • 使用 Java Spring Boot 和 JdbcTemplate 設定 JDBC 以連接到 Databricks
    使用 Java Spring Boot 和 JdbcTemplate 設定 JDBC 以連接到 Databricks
    在軟體開發領域,連接到各種資料來源是一項基本技能。 Databricks 是一個基於雲端的資料分析平台,提供了一種處理和分析大量資料的強大方法。在這篇文章中,我們將探討如何使用 Java 和 Spring 的 JdbcTemplate 來配置 JDBC 連線來連接到 Databricks,讓您能夠充...
    程式設計 發佈於2024-11-08
  • Copilotkit:您的程式設計冒險人工智慧僚機
    Copilotkit:您的程式設計冒險人工智慧僚機
    简介:当人工智能遇见代码(火花四溅) 在不断发展的科技世界中,算法起舞,数据流歌唱,镇上出现了一位新玩家:Copilotkit。这就像有一个非常聪明的朋友,他从不睡觉,不会喝掉你所有的咖啡,也不会因为你凌晨 3 点穿着睡衣编码而评判你。欢迎来到编码的未来,人工智能不仅是辅助,而且是...
    程式設計 發佈於2024-11-08
  • 如何修復 Mac 上的 Java 8 安裝問題
    如何修復 Mac 上的 Java 8 安裝問題
    解決Mac 上Java 8 的安裝問題您關於Mac 上Java 8 安裝檔案的意外位置和兼容性挑戰的查詢重點開發商面臨的共同問題。本文旨在提供一個全面的解決方案來解決這些問題。 安裝異常Oracle的Java安裝程式傾向於將Java 8檔案放在/Library/Java/JavaVirtualMac...
    程式設計 發佈於2024-11-08
  • useMemo 與 useCallback
    useMemo 與 useCallback
    介紹 React 提供了廣泛的鉤子來幫助我們有效地建立動態應用程式。在這些鉤子中,useMemo和useCallback是提高元件效能的重要工具。儘管兩者都有相似的目的——防止不必要的重新計算或函數重新創建——但它們適用於不同的場景。 在本文中,我們將探討 useMemo 和 u...
    程式設計 發佈於2024-11-08
  • 為什麼 MDM 很重要:優勢和商業價值
    為什麼 MDM 很重要:優勢和商業價值
    在当今的数字经济中,数据是每个成功企业的基石。随着组织生成的信息呈指数级增长,主数据的有效管理已成为当务之急。主数据管理 (MDM) 是管理组织关键数据资产(例如客户信息、产品详细信息和财务记录)的战略流程,确保所有部门和系统的准确性、一致性和可访问性。但为什么 MDM 很重要?更重要的是,它能带来...
    程式設計 發佈於2024-11-08
  • 使用 MetaTrader 訂單管理和市場資料收集進行自動交易
    使用 MetaTrader 訂單管理和市場資料收集進行自動交易
    Your AsimovMT class provides a comprehensive interface for interacting with MetaTrader5 (MT5) using Python. However, there are several areas in your c...
    程式設計 發佈於2024-11-08
  • 是什麼導致 Google Chrome 的 Console.log() 中陣列和物件的行為不一致?
    是什麼導致 Google Chrome 的 Console.log() 中陣列和物件的行為不一致?
    Google Chrome 的console.log() 表現出數組和對像不一致的行為了解問題在Google Chrome 中調試代碼時,觀察到console.log() 在處理巢狀數組時表現得很奇怪。記錄數組時,在記錄後修改其內部值會導致記錄的輸出反映更新後的值而不是記錄時的值。 Firefox ...
    程式設計 發佈於2024-11-08
  • 在 PHP 中按物件欄位對物件數組進行排序
    在 PHP 中按物件欄位對物件數組進行排序
    在 PHP 中,有多種方法可以依照物件欄位對物件陣列進行排序。以下是一些常見的方法: 將 usort() 函數與自訂比較函數結合使用 實作自訂排序演算法 利用 array_multisort() 函數 將 usort() 函數與自訂比較函數結合使用 以下是在 PHP 中使用 uso...
    程式設計 發佈於2024-11-08
  • 注意 Java 中的型別轉換
    注意 Java 中的型別轉換
    Java是強類型語言,但仍可在不同類型的原始變數之間傳遞值。例如,我可以將 int 的值指派給 double ,沒有任何問題,只要接收該值的類型的儲存容量可以處理它。 請參閱下面每個原始類型的大小: 將值轉移到具有更大儲存容量的類型有一個技術名稱:「擴大轉換」。該術語在葡萄牙語中通常被翻譯為“放大...
    程式設計 發佈於2024-11-08

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3