Como desarrolladores de scraping, a veces necesitamos extraer datos de autenticación como claves temporales para realizar nuestras tareas. Sin embargo, no es tan simple como eso. Por lo general, se encuentra en solicitudes de red HTML o XHR, pero a veces se calculan los datos de autenticación. En ese caso, podemos aplicar ingeniería inversa al cálculo, lo que lleva mucho tiempo desofuscar los scripts, o ejecutar el JavaScript que lo calcula. Normalmente utilizamos un navegador, pero es caro. Crawlee brinda soporte para ejecutar Browser Scraper y Cheerio Scraper en paralelo, pero eso es muy complejo y costoso en términos de uso de recursos informáticos. JSDOM nos ayuda a ejecutar JavaScript de páginas con menos recursos que un navegador y un poco más que Cheerio.
Este artículo analizará un nuevo enfoque que utilizamos en uno de nuestros Actors para obtener los datos de autenticación del centro creativo de anuncios de TikTok generados por las aplicaciones web del navegador sin ejecutar realmente el navegador, sino utilizando JSDOM.
Cuando visitas esta URL:
https://ads.tiktok.com/business/creativecenter/inspiration/popular/hashtag/pc/en
Verás una lista de hashtags con su clasificación en vivo, la cantidad de publicaciones que tienen, un gráfico de tendencias, creadores y análisis. También puede observar que podemos filtrar la industria, establecer el período de tiempo y usar una casilla de verificación para filtrar si la tendencia es nueva entre las 100 principales o no.
Nuestro objetivo aquí es extraer los 100 hashtags principales de la lista con los filtros proporcionados.
Los dos enfoques posibles son utilizar CheerioCrawler y el segundo será el raspado basado en navegador. Cheerio ofrece resultados más rápido pero no funciona con sitios web renderizados en JavaScript.
Cheerio no es la mejor opción aquí, ya que Creative Center es una aplicación web y la fuente de datos es API, por lo que solo podemos obtener los hashtags inicialmente presentes en la estructura HTML, pero no cada uno de los 100 que necesitamos.
El segundo enfoque puede ser usar bibliotecas como Puppeteer, Playwright, etc., para realizar scraping basado en navegador y usar la automatización para scrapear todos los hashtags, pero con experiencias previas, lleva mucho tiempo para una tarea tan pequeña.
Ahora viene el nuevo enfoque que desarrollamos para hacer que este proceso sea mucho mejor que el rastreo basado en navegador y muy cercano al rastreo basado en CheerioCrawler.
Antes de profundizar en este enfoque, me gustaría darle crédito a Alexey Udovydchenko, ingeniero de automatización web en Apify, por desarrollar este enfoque. ¡Felicitaciones a él!
En este enfoque, realizaremos llamadas API a https://ads.tiktok.com/creative_radar_api/v1/popular_trend/hashtag/list para obtener los datos requeridos.
Antes de realizar llamadas a esta API, necesitaremos algunos encabezados requeridos (datos de autenticación), por lo que primero haremos la llamada a https://ads.tiktok.com/business/creativecenter/inspiration/popular/hashtag/pad /es.
Comenzaremos este enfoque creando una función que creará la URL para la llamada API por nosotros y realizará la llamada y obtendrá los datos.
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 }, }, ]; };
En la función anterior, creamos la URL de inicio para la llamada API que incluye varios parámetros como hablamos anteriormente. Después de crear la URL de acuerdo con los parámetros, llamará a creative_radar_api y obtendrá todos los resultados.
Pero no funcionará hasta que tengamos los encabezados. Entonces, creemos una función que primero creará una sesión usando sessionPool y 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, }); };
En esta función, el objetivo principal es llamar a https://ads.tiktok.com/business/creativecenter/inspiration/popular/hashtag/pad/en y obtener encabezados a cambio. Para obtener los encabezados estamos usando la función getApiUrlWithVerificationToken.
Antes de continuar, quiero mencionar que Crawlee admite de forma nativa JSDOM utilizando JSDOM Crawler. Proporciona un marco para el rastreo paralelo de páginas web utilizando solicitudes HTTP simples e implementación jsdom DOM. Utiliza solicitudes HTTP sin procesar para descargar páginas web, es muy rápido y eficiente en el ancho de banda de datos.
Veamos cómo vamos a crear la función 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; };
En esta función, estamos creando una consola virtual que utiliza CustomResourceLoader para ejecutar el proceso en segundo plano y reemplazar el navegador con JSDOM.
Para este ejemplo en particular, necesitamos tres encabezados obligatorios para realizar la llamada a la API, y son ID de usuario anónimo, marca de tiempo y signo de usuario.
Usando XMLHttpRequest.prototype.setRequestHeader, estamos verificando si los encabezados mencionados están en la respuesta o no, si es así, tomamos el valor de esos encabezados y repetimos los reintentos hasta obtener todos los encabezados.
Luego, la parte más importante es que usamos XMLHttpRequest.prototype.open para extraer los datos de autenticación y realizar llamadas sin usar navegadores ni exponer la actividad del bot.
Al final de createSessionFunction, devuelve una sesión con los encabezados requeridos.
Ahora, pasando a nuestro código principal, usaremos CheerioCrawler y usaremos prenavigationHooks para inyectar los encabezados que obtuvimos de la función anterior en 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, });
Finalmente, en el controlador de solicitudes hacemos la llamada usando los encabezados y nos aseguramos de cuántas llamadas son necesarias para recuperar todos los datos de la paginación de manejo.
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 = counterUna cosa importante a tener en cuenta aquí es que estamos creando este código de manera que podamos realizar cualquier cantidad de llamadas API.
En este ejemplo en particular, solo realizamos una solicitud y una única sesión, pero puedes hacer más si lo necesitas. Cuando se complete la primera llamada API, se creará la segunda llamada API. Nuevamente, puedes hacer más llamadas si es necesario, pero nos detuvimos en dos.
Para aclarar las cosas, así es como se ve el flujo de código:
Conclusión
Este enfoque nos ayuda a obtener una tercera forma de extraer los datos de autenticación sin usar un navegador y pasar los datos a CheerioCrawler. Esto mejora significativamente el rendimiento y reduce el requisito de RAM en un 50%, y mientras que el rendimiento del scraping basado en navegador es diez veces más lento que el de Cheerio puro, JSDOM lo hace sólo 3 o 4 veces más lento, lo que lo hace 2 o 3 veces más rápido que el navegador. raspado basado.
El código base del proyecto ya está subido aquí. El código está escrito como Apify Actor; Puedes encontrar más información al respecto aquí, pero también puedes ejecutarlo sin usar Apify SDK.
Si tiene alguna duda o pregunta sobre este enfoque, comuníquese con nosotros en nuestro servidor de Discord.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3