”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 优化网页抓取:使用 JSDOM 抓取身份验证数据

优化网页抓取:使用 JSDOM 抓取身份验证数据

发布于2024-11-08
浏览:605

作为抓取开发人员,我们有时需要提取临时密钥等身份验证数据来执行我们的任务。然而,事情并没有那么简单。通常,它位于 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 中的 PDO 异常并防止“Null”状态?
    如何处理 PHP 中的 PDO 异常并防止“Null”状态?
    处理 PDO 异常在 PHP 中使用 PDO 时,处理错误对于调试和确保数据完整性至关重要。然而,您提供的代码没有正确处理错误,导致“null”状态和未报告的错误。关键问题是PDO默认情况下不会抛出异常。要启用异常处理,必须显式设置错误模式属性:$connection->setAttribut...
    编程 发布于2024-11-08
  • 十二人受伤,一千多人受伤。
    十二人受伤,一千多人受伤。
    美国不知道黎巴嫩传呼机爆炸吗?事实上,他们已经提前收到警告了! 据多家外媒报道,9月17日下午,黎巴嫩和叙利亚数千名真主党成员使用的寻呼机几乎同​​时发生爆炸,造成12人死亡、千余人受伤。 黎巴嫩真主党指责以色列发动了这次袭击,并发誓要对以色列进行报复,但以色列拒绝对此事件发表评论。事件发生后,美...
    编程 发布于2024-11-08
  • 机器学习简介
    机器学习简介
    什么是机器学习? 机器学习是计算机科学的一个领域,它使用静态技术赋予计算机系统“学习”的能力数据,没有 被明确编程。 这意味着,“ML 就是从数据中学习” 显式编程意味着,为每个场景编写代码,以处理该情况。 在机器学习中,我们不是为每个场景编写明确的代码,而是训练模型从数据中学习模...
    编程 发布于2024-11-08
  • 如何捕获 JavaScript 中自定义异常的堆栈跟踪?
    如何捕获 JavaScript 中自定义异常的堆栈跟踪?
    获取一系列抛出的异常虽然 JavaScript 允许引发异常,但获取自定义异常的堆栈跟踪可能具有挑战性。本文探讨了专门针对用户定义的异常检索堆栈跟踪的各种方法。Error 对象的 stack 属性提供了一个简单的解决方案。通过创建一个新的 Error 对象并访问其 stack 属性,您可以捕获跟踪:...
    编程 发布于2024-11-08
  • 了解 React 中的关键属性 - 常见错误
    了解 React 中的关键属性 - 常见错误
    如果你喜欢我的文章,可以请我喝杯咖啡:) 在 React 中使用列表时,最关键的概念之一是 key 属性。键在 React 如何管理列表更新方面发挥着重要作用。 React 中的键是什么? 在 React 中,键是分配给列表中元素的唯一标识符。这些键帮助 React 确定哪些项目已更...
    编程 发布于2024-11-08
  • 掌握 React:构建强大 Web 应用程序的循序渐进之旅(简介)
    掌握 React:构建强大 Web 应用程序的循序渐进之旅(简介)
    React is a popular JavaScript library used to build user interfaces, especially for single-page websites or apps. Whether you're a complete beginner o...
    编程 发布于2024-11-08
  • JavaScript DOM 与 BOM!
    JavaScript DOM 与 BOM!
    DOM DOM 代表文档对象模型,代表网页。这允许程序操纵文档结构、样式和内容。 const listDiv = document.getElementById("list-div"); listDiv.classList.add('new-class'); listDiv.cla...
    编程 发布于2024-11-08
  • 绑定和模板:Peasy-UI 系列的一部分
    绑定和模板:Peasy-UI 系列的一部分
    Table of Contents Introduction Bindings and the Template Text Bindings Basic Binding Conditional Boolean Text B...
    编程 发布于2024-11-08
  • 实现接口
    实现接口
    定义接口后,一个或多个类可以实现它。 要实现接口,请在类定义中使用 Implements 子句。 该类必须实现接口所需的所有方法。 包含 Implements 子句的类的一般形式是: 类类名扩展超类实现接口{ // 类体 } 若要实现多个接口,接口之间用逗号分隔。 实现接口时,extend...
    编程 发布于2024-11-08
  • 检查 Effect-TS 选项中的元素:实用指南
    检查 Effect-TS 选项中的元素:实用指南
    Effect-TS 提供了检查 Option 是否包含特定值的方法。这些函数允许您使用自定义等价函数或默认等价来确定选项中是否存在值。在本文中,我们将探讨用于检查选项中元素的两个关键函数:O.containsWith 和 O.contains. 示例 1:使用 O.containsW...
    编程 发布于2024-11-08
  • Python 面向对象编程简介
    Python 面向对象编程简介
    Python 编程语言 Python 是一种解释型、面向对象的编程语言。由于其高级内置数据结构和动态类型,它在快速开发新应用程序以及编写脚本代码以组合用不同语言编写的现有组件方面很受欢迎。 Python简单易学的语法强调可读性,从而降低了长期程序维护的成本和复杂性。它支持各种包含代...
    编程 发布于2024-11-08
  • 最佳软件比较中的顶级数据科学工具
    最佳软件比较中的顶级数据科学工具
    介绍 到 2024 年,数据科学将通过使用复杂的分析、人工智能和机器学习推动决策,继续改变业务。随着对熟练数据科学家的需求不断增加,对能够加快操作、提高生产力并提供可靠见解的强大工具的需求也在增加。但是,有这么多可用的选项,目前哪种软件最适合专业人士? 这项比较研究探讨了 2024...
    编程 发布于2024-11-08
  • 我如何将应用程序性能提高到
    我如何将应用程序性能提高到
    ⌛ 回顾时间 在我的上一篇博客中,我谈到了如何在短短 2 周内将应用程序大小从 75MB 减少到 34MB(查看!)。但这还不是全部,我还将我们应用程序的整体性能提高了 80%?. 让我们来看看如何!! ?传说 经过简单的一轮浏览后,我发现我们的应用程序中存在一些导...
    编程 发布于2024-11-08
  • Django 查询集可以通过模型属性过滤吗?
    Django 查询集可以通过模型属性过滤吗?
    按模型属性过滤 Django 查询集Django 模型上的查询通常使用标准过滤器根据预定义字段值选择特定实例。但是,如果您需要根据模型中定义的自定义属性进行过滤,该怎么办?您可以通过模型属性过滤查询集吗?不幸的是,Django 的过滤器主要运行在数据库级别,将它们转换为 SQL 命令以有效地检索数据...
    编程 发布于2024-11-08
  • 尽管配置正确,为什么我无法在 Laravel 中发送 TLS 电子邮件?
    尽管配置正确,为什么我无法在 Laravel 中发送 TLS 电子邮件?
    无法发送 TLS 电子邮件:解决 Laravel 证书验证错误尽管启用了不太安全的 Gmail 设置并正确配置了 Laravel 的 .env 文件,您在发送 TLS 电子邮件时遇到证书验证失败。错误消息表明 SSL 操作失败并且无法验证服务器证书。要解决此问题,如果您的操作系统没有自动管理受信任的...
    编程 发布于2024-11-08

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3