Desenvolupem la teva web presencial SPA DES DE 300€. Si, es una bogeria. Web SPA DES DE 300€ — Si, es una bogeria. Parlem →

Com evitar que et bloquegin la IP quan diversos processos fan scraping alhora

Com evitar que et bloquegin la IP quan diversos processos fan scraping alhora

Tothom qui ha automatitzat la consulta d'un lloc extern hi ha posat una pausa entre peticions i ha donat el problema per resolt. Funciona mentre només hi ha un procés demanant. Quan la teva aplicació creix i consulten alhora la cua, la petició web i una tasca programada, aquesta pausa deixa de servir i arriba el bloqueig. Aquí tens per què falla i com es coordina de debò, amb un cas real que ens va costar un 403. Els exemples van en Laravel, una de les nostres especialitats, i comparem amb spatie/crawler, que és la referència de l'ecosistema, però la coordinació entre processos es planteja igual amb qualsevol stack.

El 403 no arriba per fer moltes peticions, arriba per fer-les alhora

Gairebé tothom comença igual: un sleep(1) entre peticions i endavant. Funciona en local, funciona en preproducció, i funciona en producció fins al dia que el mateix lloc es consulta des de tres punts diferents de la teva aplicació.

Ens va passar amb el cercador de jurisprudència del Consell General del Poder Judicial. El comentari que vam deixar a la configuració de producció ho explica tal qual:

El xat, l'MCP i els tres workers de la cua li disparen a poderjudicial.es sense saber els uns dels altres. En disset minuts es van arribar a fer de l'ordre de vint sessions de navegador, perquè el model llança cerques en paral·lel per angles diferents i cadascuna reintenta tres vegades. CENDOJ va acabar retornant 403.

Cap d'aquells processos s'estava portant malament. Cadascun respectava la seva pausa. El problema és que la pausa era local a cada procés i el lloc remot veu una sola adreça IP.

Un sleep entre peticions no és un límit de velocitat

Aquesta és la part que costa veure. Si la teva manera d'anar a poc a poc és dormir un segon després de cada petició, el que tens és un límit per procés. Amb cinc processos vas a cinc vegades aquella velocitat i no te n'has adonat.

La solució intuïtiva tampoc funciona: desar en algun lloc quan va sortir l'última petició i esperar que passi l'interval. Sona bé i és una condició de cursa de manual. Tres processos que es desperten alhora llegeixen la mateixa resposta, esperen els mateixos segons i disparen en el mateix instant. Has convertit tres peticions seguides en una ràfega de tres, disfressada de petició espaiada.

El que sí que funciona és invertir l'ordre: primer reserves el torn, després dorms. La reserva és el que cal protegir amb un pany, no l'espera. Cada procés demana el seu forat, rep una marca de temps diferent i dorm fins a la seva. Tres processos simultanis surten espaiats sense haver-se parlat mai.

Aquell calendari compartit viu a la memòria cau de l'aplicació, amb una entrada per objectiu. Si fas servir Redis o Memcached, la coordinació creua màquines; amb la memòria cau de fitxer es queda en una de sola.

Quan ja t'han bloquejat, reintentar igual empitjora les coses

Una fallada de xarxa i un 403 no són la mateixa mena de problema, encara que tots dos siguin "no ha sortit bé".

Un 500 o un 504 són transitoris: esperes una mica i ho tornes a intentar pel mateix lloc. Un 403 o un 429 signifiquen que aquella sortida està cremada. Reintentar per allà després d'esperar quinze segons no arregla res i allarga el càstig.

El tractament correcte té dues parts. Primer, anotar aquella sortida com a bloquejada durant un temps que creix si torna a passar, començant en un parell de minuts i doblant fins a un sostre. Segon, no esperar abans de reintentar, perquè el reintent sortirà per una altra adreça, i a la nova ja s'aplica el calendari general.

I un detall que evita insistir en va: si no queda cap sortida sana, la fallada s'accepta. Reintentar contra l'adreça que acaba de rebutjar-te només allarga el bloqueig.

Andie recomana

Configura la clau del calendari per objectiu lògic, no per domini. En el nostre cas n'hi ha dos, el cercador i la descàrrega de documents, contra el mateix lloc i amb intervals diferents: deu segons per cercar i tres per descarregar. Si ho agrupes per domini, la part lenta li imposa el seu ritme a la ràpida sense motiu.

Mentir amb el user agent et delata més que anar de cara

Falsejar el user agent és de les primeres coses que es fan en automatitzar peticions, i es fa per una intuïció raonable: si sembles un navegador normal, et tracten com a tal. El que gairebé ningú no comprova és quantes coses més mira el servidor abans de decidir si s'ho creu.

El costum és inventar-se una cadena de user agent d'un Chrome normal perquè no es noti que és un navegador automatitzat. El problema és que un Chrome de debò no només envia aquella cadena: envia també els Client Hints, unes capçaleres que declaren marca i versió del navegador, i exposa el mateix en JavaScript.

Aquestes dades les omple el navegador real, i no es falsegen escrivint una cadena. Així que quan inventes el user agent el que aconsegueixes és una contradicció: una capçalera que diu ser Chrome 148 al costat d'uns Client Hints que diuen una altra cosa. Això no és passar desapercebut, és aixecar la mà.

Surt millor preguntar al navegador quina és la seva cadena real i treure-li només la paraula que delata el mode sense finestra. Tota la resta queda coherent perquè és coherent.

Separar la petició del parseig canvia com es depura

Hi ha un benefici menys vistós que s'agraeix al cap de sis mesos: distingir dos tipus de fallada que sempre es confonen.

Que la petició no surti (la xarxa, un temps esgotat, un status d'error després dels reintents) és una fallada de transport, i el natural és que llanci una excepció.

Que la pàgina no contingui el que esperaves (un captcha, cap resultat, el disseny canviat) no és un error del programa, és una dada. I mereix tornar com a resultat, no com a excepció.

Confondre els dos té conseqüències reals. A la nostra plataforma legal, "aquesta cerca no ha retornat sentències" i "no hem pogut preguntar" acabaven al mateix lloc, i el copilot li deia a un advocat que certes resolucions no existien quan la veritat és que el cercador estava bloquejat. Són respostes diferents i l'usuari mereix saber quina és.

Què hi ha al mercat i en què es diferencia

A Laravel la referència és spatie/crawler, i és un paquet excel·lent per al que fa. La diferència és que resol un altre problema.

Aquell paquet és un rastrejador de descobriment: li dones una URL, extreu els enllaços, els encua i recorre el lloc sencer, respectant robots.txt i amb un pool de peticions concurrents. Si el que necessites és recórrer un web, és l'eina.

Larascraper, que és el que mantenim, no descobreix res: parteix de rutes que ja coneixes i va a buscar dades concretes. A canvi porta tres coses que l'altre no planteja perquè no les necessita: el calendari compartit entre processos del qual va aquest article, el bloqueig per sortida amb retrocés exponencial, i una cadena d'accions per operar la pàgina viva, amb clics, formularis, condicions i bucles acotats.

Aquest últim punt és el que decideix a la pràctica. Renderitzar JavaScript i retornar l'HTML final ho fan tots dos. Omplir un formulari, resoldre un captcha de text, enviar i capturar el PDF que retorna el servidor, repetint fins a vuit vegades si el captcha falla, és un altre problema. Va néixer d'això: butlletins oficials amb visors de PDF darrere de captcha i documents escanejats sense capa de text.

Tres coses que cal deixar muntades

Cap és complicada, però convé resoldre-les abans de la primera execució en producció.

Ajusta la concurrència segons el mode. Quan l'extracció no necessita navegador, les peticions se solapen de debò. Amb navegador, cada execució és un Chromium aïllat i van d'una en una. Configurar-ne vint en paral·lel amb navegador no accelera res.

Cal instal·lar coses fora de Composer. Node, els paquets de Puppeteer i un binari de Chrome, al mateix entorn on s'executa. Si treballes amb contenidors, dins del contenidor. I per extreure text de PDF calen utilitats del sistema.

I el parany que ens va costar una tarda: llançar el navegador necessita executar un procés extern, i en molts desplegaments aquesta funció està desactivada al PHP que serveix les peticions web per seguretat. A la línia de comandes no ho està. La solució no és obrir-la al servidor web, és moure l'extracció a una cua, que és on hauria d'estar de totes maneres.

I si no treballes amb Laravel

El disseny es trasllada sencer, perquè el problema no és del llenguatge.

Necessites tres coses: un calendari compartit en un magatzem que vegin tots els processos, amb reserva de torn abans d'esperar; un registre de sortides bloquejades amb retrocés exponencial; i separar la fallada de transport de la fallada de contingut per no mentir a qui consumeix les dades.

Si la teva extracció viu en un sol procés, res d'això cal i un sleep et val. Així que hi hagi un segon procés, el teu límit de velocitat ha deixat d'existir encara que el codi no hagi canviat.

On és el codi

El paquet està publicat a Packagist, el codi a GitHub i la documentació del paquet recull totes les opcions. És la capa d'extracció que hi ha darrere de Crowd Legal, on recorre butlletins oficials de diversos països.

Si has d'integrar dades de fonts que no ofereixen API, és de les coses que més vegades hem resolt. Ho treballem a aplicacions web a mida i desenvolupament Laravel, i si ens expliques el cas et diem per on aniria.

Escrit per
Edu Lazaro
Edu Lazaro
Founder & Lead Developer en AndorraDev

Desenvolupador full-stack amb més de 15 anys d'experiència en Laravel, React, Node.js i arquitectures cloud. Ajudo empreses a Andorra a construir la seva presència digital.

Partner de diseño · ionospace.
Necessites ajuda? ×
Andie by AndorraDev
Assistent IA + equip humà
Assistent IA d'AndorraDev
Andie
Hola! Soc Andie, l'assistent IA d'AndorraDev. En què et puc ajudar? Si necessites parlar amb Edu, només demana-ho.
17:27