Tengo un raspador que busca texto en sitios de una búsqueda de Google. Sin embargo, en ocasiones, las URL para la búsqueda son archivos GRANDES sin nombres de extensión (es decir, https://myfile.com/myfile/ ).
Tengo un mecanismo de tiempo de espera en su lugar, pero cuando se agota, el archivo ya ha sobrecargado la memoria. ¿Hay alguna forma de detectar el tamaño o el tipo de archivo mientras se descarga ?
Aquí está mi función de solicitud:
const getHtml = async (url, { timeout = 10000, ...opts } = {}) => { const CancelToken = axios.CancelToken const source = CancelToken.source() try { const timeoutId = setTimeout(() => source.cancel('Request cancelled due to timeout'), timeout) let site = await axios.get(url, { headers: { 'user-agent': userAgent().toString(), connection: 'keep-alive', // self note: Isn't this prohibited on http/2? }, cancelToken: source.token, ...opts, }) clearTimeout(timeoutId) return site.data } catch (err) { throw err } }PD : He visto preguntas similares, pero ninguna tenía una respuesta que pudiera aplicarse.
Ok, esto no es tan fácil de resolver como cabría esperar. Idealmente, los encabezados http 'Content-length' y 'Content-type' existen para que el usuario pueda saber qué debe esperar, pero estos no son encabezados obligatorios. Sin embargo, a menudo son inexactos o faltan.
La solución que encontré para este problema, que parece ser muy confiable, implica dos cosas:
Una excelente manera de usar estas dos cosas es transmitir la respuesta y leer los primeros bytes para verificar la firma del archivo; Una vez que sepa si el archivo está en el formato que admite/desea, puede procesarlo como lo haría normalmente o cancelar la solicitud antes de leer la siguiente parte de la transmisión, lo que debería evitar la sobrecarga de su sistema (y que también puede usar para medir el tamaño del archivo con mayor precisión, lo cual muestro en el siguiente fragmento)
Así es como implementé la solución mencionada anteriormente:
const getHtml = async (url, { timeout = 10000, ...opts } = {}) => { const CancelToken = axios.CancelToken const source = CancelToken.source() try { const timeoutId = setTimeout(() => source.cancel('Request cancelled due to timeout'), timeout) const res = await axios.get(url, { headers: { connection: 'keep-alive', }, cancelToken: source.token, // Use stream mode so we can read the first chunk before getting the rest(1.6kB/chunk(highWatermark)) responseType: 'stream', ...opts, }) const stream = res.data; let firstChunk = true let size = 0 // Not to be confused with arrayBuffer(the object) ;) const bufferArray = [] // Async iterator syntax for consuming the stream. Iterating over a stream will consume it fully, but returning or breaking the loop in any way will destroy it for await (const chunk of stream) { if (firstChunk) { firstChunk = false // Only check the first 100(relevant, spaces excl.) chars of the chunk for html. This would possibly only fail in a raw text file which contains the word html at the very top(very unlikely and even then, wouldn't break anything) const stringChunk = String(chunk).replace(/\s+/g, '').slice(0, 100).toLowerCase() if (!stringChunk.includes('html')) return { error: `Requested URL is detected as a file. URL: ${url}\nChunk's magic 100: ${stringChunk}` }; } size += Buffer.byteLength(chunk); if (size > sizeLimit) return { error: `Requested URL is too large.\nURL: ${url}\nSize: ${size}` }; const buff = new Buffer.from(chunk) bufferArray.push(buff) } // After the stream is fully consumed, we clear the timeout and create one big buffer to convert to str and return that clearTimeout(timeoutId) return { html: Buffer.concat(bufferArray).toString() } } catch (err) { throw err } }