スクレイピング開発者として、タスクを実行するために一時キーなどの認証データを抽出する必要がある場合があります。しかし、それはそれほど単純ではありません。通常、これは HTML または XHR ネットワーク リクエスト内にありますが、認証データが計算される場合もあります。その場合、スクリプトの難読化を解除するのに多くの時間がかかる計算をリバースエンジニアリングするか、計算を行う JavaScript を実行することができます。通常ブラウザを使用しますが、それは高価です。 Crawlee は、ブラウザ スクレイパーと Cheerio Scraper を並行して実行するためのサポートを提供しますが、これはコンピューティング リソースの使用量の点で非常に複雑で高価です。 JSDOM は、ブラウザより少ないリソースで、Cheerio よりわずかに高いリソースでページ JavaScript を実行するのに役立ちます。
この記事では、実際にブラウザを実行せず、代わりに JSDOM を使用して、ブラウザ Web アプリケーションによって生成された TikTok 広告クリエイティブ センターから認証データを取得するために、アクターの 1 つで使用する新しいアプローチについて説明します。
この URL にアクセスすると:
https://ads.tiktok.com/business/creativecenter/inspire/popular/hashtag/pc/en
ライブランキング、投稿数、トレンドチャート、作成者、分析を含むハッシュタグのリストが表示されます。また、業界をフィルターしたり、期間を設定したり、チェック ボックスを使用してトレンドがトップ 100 に新しいものであるかどうかをフィルターしたりできることにも注目してください。
ここでの目標は、指定されたフィルターを使用してリストから上位 100 個のハッシュタグを抽出することです。
考えられるアプローチは 2 つあり、CheerioCrawler を使用するもので、2 つ目はブラウザベースのスクレイピングです。 Cheerio は結果をより速く表示しますが、JavaScript でレンダリングされた Web サイトでは機能しません。
クリエイティブ センターは Web アプリケーションであり、データ ソースは API であるため、ここでは Cheerio は最良の選択肢ではありません。そのため、HTML 構造に最初に存在するハッシュタグのみを取得できますが、必要な 100 個のハッシュタグをそれぞれ取得することはできません。
2 番目のアプローチは、Puppeteer、Playwright などのライブラリを使用してブラウザベースのスクレイピングを実行し、自動化を使用してすべてのハッシュタグをスクレイピングする方法ですが、以前の経験では、このような小さなタスクには多くの時間がかかります。
このプロセスをブラウザ ベースよりもはるかに優れ、CheerioCrawler ベースのクロールに非常に近づけるために開発した新しいアプローチが登場します。
このアプローチについて詳しく説明する前に、このアプローチの開発について、Apify の Web オートメーション エンジニアである Alexey Udovydchenko の功績を認めたいと思います。彼に敬意を表します!
このアプローチでは、https://ads.tiktok.com/creative_radar_api/v1/popular_trend/hashtag/list への API 呼び出しを行って、必要なデータを取得します。
この API を呼び出す前に、必要なヘッダー (認証データ) がいくつか必要になるため、最初に https://ads.tiktok.com/business/creativecenter/inspire/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/inspire/popular/hashtag/pad/en を呼び出し、ヘッダーを返すことです。ヘッダーを取得するには、getApiUrlWithVerificationToken 関数を使用します。
先に進む前に、Crawlee は JSDOM Crawler を使用して JSDOM をネイティブにサポートしていることに言及したいと思います。これは、プレーンな HTTP リクエストと jsdom DOM 実装を使用して Web ページを並列クロールするためのフレームワークを提供します。生の HTTP リクエストを使用して Web ページをダウンロードするため、データ帯域幅が非常に高速かつ効率的です。
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 呼び出しを行うために 3 つの必須ヘッダーが必要です。それらは anonymous-user-id、timestamp、および user-sign です。
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 呼び出しをいくつでも実行できるようにこのコードを作成していることです。
この特定の例では、1 つのリクエストと 1 つのセッションを作成しただけですが、必要に応じてさらに作成することもできます。最初の API 呼び出しが完了すると、2 番目の API 呼び出しが作成されます。繰り返しますが、必要に応じてさらに電話をかけることもできますが、ここでは 2 つで停止しました。
物事をより明確にするために、コード フローは次のようになります:
結論
このアプローチは、実際にブラウザを使用せずに認証データを抽出し、そのデータを CheerioCrawler に渡す 3 番目の方法を取得するのに役立ちます。これにより、パフォーマンスが大幅に向上し、RAM 要件が 50% 削減されます。ブラウザベースのスクレイピング パフォーマンスは純粋な Cheerio よりも 10 倍遅いのに対し、JSDOM はわずか 3 ~ 4 倍遅いので、ブラウザより 2 ~ 3 倍高速になります。ベースのスクレイピング。
プロジェクトのコードベースはすでにここにアップロードされています。コードは Apify アクターとして記述されます。詳細については、こちらをご覧ください。ただし、Apify SDK を使用せずに実行することもできます。
このアプローチについて疑問や質問がある場合は、Discord サーバーまでご連絡ください。
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3