{"id":44367,"date":"2026-05-06T12:51:19","date_gmt":"2026-05-06T12:51:19","guid":{"rendered":"https:\/\/floppydata.com\/sin-categoria\/php-web-scraping-tutorial-como-raspar-datos-con-php\/"},"modified":"2026-05-06T12:51:19","modified_gmt":"2026-05-06T12:51:19","slug":"php-web-scraping-tutorial","status":"publish","type":"post","link":"https:\/\/floppydata.com\/es\/blog\/scraping\/php-web-scraping-tutorial\/","title":{"rendered":"PHP Web Scraping Tutorial: C\u00f3mo raspar datos con PHP"},"content":{"rendered":"<h2>Introducci\u00f3n<\/h2>\n<p>PHP fue uno de mis primeros lenguajes como desarrollador web en su d\u00eda, y todav\u00eda me gusta usarlo para scraping.<\/p>\n<p>Este tutorial cubre lo que realmente uso en producci\u00f3n. Te guiar\u00e9 a trav\u00e9s de un flujo de trabajo completo de raspado web PHP usando <a href=\"https:\/\/floppydata.com\/web-unlocker\/\">Floppydata Web Unlocker<\/a> como la capa de raspado. <\/p>\n<p><strong>\u00bfPor qu\u00e9 Floppydata?<\/strong><\/p>\n<p>Porque proporciona una API de scraping que elimina la necesidad de gestionar proxies, cabeceras o l\u00f3gica anti-bot. Al final de este art\u00edculo, usted tendr\u00e1 un buen conocimiento de c\u00f3mo realizar web scraping con PHP. <\/p>\n<h2>\u00bfQu\u00e9 es el web scraping PHP?<\/h2>\n<p>PHP web scraping es el proceso de utilizar c\u00f3digo PHP para extraer datos de sitios web. No todos los sitios ofrecen una API, como Twitter por ejemplo, as\u00ed que en muchos casos la \u00fanica forma de obtener la informaci\u00f3n que necesitas es buscar la p\u00e1gina y analizar el HTML t\u00fa mismo. <\/p>\n<p>PHP tiene mucho sentido para esto si ya lo utiliza todos los d\u00edas. Puede colocar los datos raspados directamente en un backend existente, almacenarlos en MySQL o ejecutar el raspador en una tarea cron sin introducir otro lenguaje en la pila. <\/p>\n<p>La cuesti\u00f3n principal no es si PHP puede raspar. Por supuesto que puede. La verdadera pregunta es qu\u00e9 tan bien maneja su scraper las solicitudes bloqueadas, las restricciones basadas en la ubicaci\u00f3n y los CAPTCHA agresivos.  <\/p>\n<p>Esa es exactamente la raz\u00f3n por la que emparejo PHP con Floppydata Web Unlocker para ayudar a cerrar la brecha.<\/p>\n<h2>Bibliotecas PHP de web scraping que merece la pena conocer (2026)<\/h2>\n<p>PHP tiene muchas librer\u00edas de scraping, pero honestamente, me he decidido por unas pocas que realmente uso. He aqu\u00ed un r\u00e1pido vistazo a ellos. <\/p>\n<ul>\n<li><strong>Guzzle:<\/strong> Un cliente HTTP s\u00f3lido que maneja peticiones POST, cargas \u00fatiles JSON, redirecciones y cabeceras de forma limpia. Lo usaremos a lo largo de este tutorial para hablar con la API de Web Unlocker. <\/li>\n<li><strong>Symfony DomCrawler:<\/strong> Te permite navegar por HTML y XML usando selectores CSS o XPath. Cuando se combina con el componente symfony\/css-selector, proporciona un filtrado de estilo jQuery que funciona de forma fiable en HTML desordenado. Es independiente, por lo que no necesitas el resto de Symfony.  <\/li>\n<li><strong>Symfony HttpBrowser:<\/strong> Es el sustituto moderno de la ya obsoleta librer\u00eda Goutte. Est\u00e1 construida sobre BrowserKit y DomCrawler, y te permite simular clics, env\u00edos de formularios y cadenas de redireccionamiento. Ideal cuando su l\u00f3gica de raspado abarca varias p\u00e1ginas.  <\/li>\n<li><strong>DiDOM:<\/strong> DiDOM es un analizador r\u00e1pido de dependencia cero con una API similar a jQuery. Perfecto para scripts m\u00e1s peque\u00f1os en los que quieras evitar tirar de componentes de Symfony. <\/li>\n<li><strong>Symfony Panther:<\/strong> Controla un navegador Chrome o Chromium real a trav\u00e9s de WebDriver. Se utiliza cuando un sitio renderiza todo en JavaScript (React, Vue, SPAs pesadas) y una petici\u00f3n HTTP simple devuelve un shell vac\u00edo. Es m\u00e1s pesado, as\u00ed que s\u00f3lo lo uso cuando nada m\u00e1s funciona.  <\/li>\n<\/ul>\n<p>Goutte sol\u00eda ser una recomendaci\u00f3n com\u00fan, pero ahora est\u00e1 obsoleta, por lo que no recomendar\u00eda construir un nuevo proyecto en torno a ella.<\/p>\n<p>Para esta gu\u00eda, Guzzle m\u00e1s Symfony DomCrawler es suficiente. Dado que Web Unlocker ya ejecuta JavaScript y devuelve el HTML renderizado final, no necesitamos ejecutar un navegador headless en nuestro extremo. <\/p>\n<h2>Requisitos previos<\/h2>\n<p>Antes de escribir cualquier c\u00f3digo, aseg\u00farate de tener las siguientes cuatro cosas en su lugar. Si nunca has configurado un proyecto PHP desde cero, no te preocupes, te guiar\u00e9 a trav\u00e9s de cada paso. <\/p>\n<h3>1. PHP 8.2 o posterior<\/h3>\n<p>PHP viene preinstalado en muchos sistemas Mac y Linux, pero nunca est\u00e1 de m\u00e1s comprobarlo. Abre tu terminal y comprueba tu versi\u00f3n de PHP: <\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code><span style=\"color: #9333ea;\">php<\/span> <span style=\"color: #16a34a;\">-v<\/span><\/code><\/pre>\n<\/div>\n<p>Si PHP ya est\u00e1 instalado, deber\u00eda ver un n\u00famero de versi\u00f3n. Para este tutorial, utilice PHP 8.2 o m\u00e1s reciente. Ese es el punto de partida m\u00e1s seguro con las versiones de dependencia que vamos a instalar.  <\/p>\n<p>Si falta PHP, siga las instrucciones para instalarlo:<\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code><span style=\"color: #64748b;\"># Windows (Chocolatey, run PowerShell as Administrator)<\/span>\n<span style=\"color: #9333ea;\">choco<\/span> <span style=\"color: #16a34a;\">install<\/span> php\n\n<span style=\"color: #64748b;\"># macOS (Homebrew)<\/span>\n<span style=\"color: #9333ea;\">brew<\/span> <span style=\"color: #16a34a;\">install<\/span> php<\/code><\/pre>\n<\/div>\n<p>Despu\u00e9s de la instalaci\u00f3n, ejecute <code>php -v<\/code> de nuevo para confirmar la versi\u00f3n. En Homebrew, no necesita paquetes separados php-curl o php-xml para este tutorial. Esas extensiones ya est\u00e1n incluidas con la instalaci\u00f3n principal de PHP.  <\/p>\n<h3>2. Compositor<\/h3>\n<p>Composer es el gestor de paquetes est\u00e1ndar para PHP. Es b\u00e1sicamente el equivalente de npm o pip para proyectos PHP. Lo usaremos para instalar Guzzle y los paquetes del parser de Symfony.  <\/p>\n<p>En primer lugar, compruebe si ya est\u00e1 disponible:<\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code><span style=\"color: #9333ea;\">composer<\/span> <span style=\"color: #16a34a;\">--version<\/span><\/code><\/pre>\n<\/div>\n<p>Si Composer a\u00fan no est\u00e1 instalado, util\u00edcelo:<\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code><span style=\"color: #64748b;\"># Windows (Chocolatey, as Administrator)<\/span>\n<span style=\"color: #9333ea;\">choco<\/span> <span style=\"color: #16a34a;\">install<\/span> composer\n\n<span style=\"color: #64748b;\"># macOS (Homebrew)<\/span>\n<span style=\"color: #9333ea;\">brew<\/span> <span style=\"color: #16a34a;\">install<\/span> composer<\/code><\/pre>\n<\/div>\n<p>Una vez hecho esto, <code>composer --version<\/code> deber\u00eda imprimir un n\u00famero de versi\u00f3n, y ya est\u00e1 listo para crear el proyecto.<\/p>\n<h3>3. Una cuenta Floppydata<\/h3>\n<p>Crea una <a href=\"http:\/\/app.floppydata.com\">cuenta en Floppydata<\/a> y copia tu clave API desde el panel de control. Cada nueva cuenta obtiene 5 scrapes gratuitos para el Web Unlocker. <\/p>\n<p><img fetchpriority=\"high\" decoding=\"async\" class=\"alignnone size-full wp-image-44186\" src=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image5-2.png\" alt=\"  Cuenta Floppydata  \" width=\"1999\" height=\"764\" srcset=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image5-2.png 1999w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image5-2-300x115.png 300w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image5-2-1024x391.png 1024w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image5-2-768x294.png 768w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image5-2-1536x587.png 1536w\" sizes=\"(max-width: 1999px) 100vw, 1999px\" \/><\/p>\n<p>Despu\u00e9s de iniciar sesi\u00f3n en su panel de control, vaya a <strong>Administrar claves de API<\/strong> en el Desbloqueador Web y genere una clave de API. C\u00f3piala inmediatamente y gu\u00e1rdala en un lugar seguro. <\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-44195\" src=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image6-2.png\" alt=\"Gesti\u00f3n de claves API\" width=\"1999\" height=\"680\" srcset=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image6-2.png 1999w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image6-2-300x102.png 300w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image6-2-1024x348.png 1024w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image6-2-768x261.png 768w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image6-2-1536x523.png 1536w\" sizes=\"(max-width: 1999px) 100vw, 1999px\" \/><\/p>\n<p>Utilizar\u00e1s esta clave en la cabecera X-Api-Key de cada petici\u00f3n de Web Unlocker. La a\u00f1adiremos a nuestro c\u00f3digo en unos minutos. <\/p>\n<h3>4. Directorio del proyecto y dependencias<\/h3>\n<p>Ahora vamos a crear la carpeta donde vivir\u00e1 nuestro scraper e instalar las librer\u00edas PHP que necesitamos. En tu terminal: <\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code><span style=\"color: #9333ea;\">mkdir<\/span> php-scrape-countries\n<span style=\"color: #9333ea;\">cd<\/span> php-scrape-countries<\/code><\/pre>\n<\/div>\n<p>Inicialice un nuevo proyecto de Composer:<\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code><span style=\"color: #9333ea;\">composer<\/span> <span style=\"color: #16a34a;\">init<\/span> <span style=\"color: #16a34a;\">--name=<\/span><span style=\"color: #dc2626;\">\"myname\/country-scraper\"<\/span> <span style=\"color: #16a34a;\">--require=<\/span><span style=\"color: #dc2626;\">\"php:^8.2\"<\/span> <span style=\"color: #16a34a;\">--no-interaction<\/span><\/code><\/pre>\n<\/div>\n<p>Ahora instala los paquetes que necesitamos:<\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code><span style=\"color: #9333ea;\">composer<\/span> <span style=\"color: #16a34a;\">require<\/span> guzzlehttp\/guzzle symfony\/dom-crawler:^7.4 symfony\/css-selector:^7.4<\/code><\/pre>\n<\/div>\n<p>Composer descargar\u00e1 las tres librer\u00edas m\u00e1s sus dependencias en una carpeta vendor\/ y crear\u00e1 un archivo composer.json que rastrea exactamente qu\u00e9 versiones est\u00e1s utilizando.<\/p>\n<p>A partir de ahora, cada archivo PHP en el proyecto puede cargar las dependencias con:<\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code><span style=\"color: #ea580c;\">require_once<\/span> <span style=\"color: #6366f1;\">__DIR__<\/span> . <span style=\"color: #dc2626;\">'\/vendor\/autoload.php'<\/span>;<\/code><\/pre>\n<\/div>\n<p>En este punto, la configuraci\u00f3n se ha completado, y podemos pasar a la rasqueta en s\u00ed.<\/p>\n<h2>C\u00f3mo scrapear datos con PHP usando Floppydata Web Unlocker<\/h2>\n<h3>Paso n\u00ba 1: Pon a prueba tu objetivo<\/h3>\n<p>Nunca me gusta escribir c\u00f3digo a ciegas para saber exactamente qu\u00e9 selectores y estructura de datos esperar. Para este ejemplo, me centrar\u00e9 en <a href=\"https:\/\/www.scrapethissite.com\/pages\/simple\/\" rel=\"nofollow noopener\" target=\"_blank\">scrapethissite<\/a>, un sitio de demostraci\u00f3n para el scraping de datos. Contiene un listado de los 250 pa\u00edses con su capital, poblaci\u00f3n y superficie.  <\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-44177\" src=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image4-2.png\" alt=\"scrapethissite\" width=\"1999\" height=\"1184\" srcset=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image4-2.png 1999w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image4-2-300x178.png 300w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image4-2-1024x607.png 1024w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image4-2-768x455.png 768w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image4-2-1536x910.png 1536w\" sizes=\"(max-width: 1999px) 100vw, 1999px\" \/><\/p>\n<p>Para seguir el proceso, visita <a href=\"http:\/\/app.floppydata.com\/tools\/scrape\">Floppydata Web Unlocker Playground<\/a>. Esta herramienta sin c\u00f3digo est\u00e1 disponible directamente desde tu panel de control y te permite ver el HTML exacto que devolver\u00e1 la API sin necesidad de configurar un proyecto. <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-44150\" src=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image1-2.png\" alt=\"floppydata\" width=\"1057\" height=\"419\" srcset=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image1-2.png 1057w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image1-2-300x119.png 300w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image1-2-1024x406.png 1024w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image1-2-768x304.png 768w\" sizes=\"(max-width: 1057px) 100vw, 1057px\" \/><\/p>\n<p>Ahora, introduce la URL y haz clic en Scrape. En unos segundos, ver\u00e1s el HTML completo en la vista previa de salida. Ese es exactamente el mismo HTML que tu script PHP recibir\u00e1 dentro de unos pasos.  <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-44168 size-full\" src=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image3-2.png\" alt=\"Script PHP\" width=\"1010\" height=\"499\" srcset=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image3-2.png 1010w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image3-2-300x148.png 300w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image3-2-768x379.png 768w\" sizes=\"(max-width: 1010px) 100vw, 1010px\" \/><\/p>\n<p>Si los datos parecen correctos, puedes copiar el HTML o descargar la respuesta. Pero en nuestro caso, dejaremos que el script PHP lo haga autom\u00e1ticamente. <\/p>\n<h3>Paso 2: Env\u00edo de la primera solicitud de desbloqueo web con Guzzle<\/h3>\n<p>El n\u00facleo de todo el flujo de trabajo es una solicitud POST al punto final de Floppydata:<\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code>https:\/\/client-api.floppy.host\/v1\/webUnlocker<\/code><\/pre>\n<\/div>\n<p>Para ello, primero creamos un cliente Guzzle y preparamos la configuraci\u00f3n de la petici\u00f3n. Despu\u00e9s enviamos la petici\u00f3n y gestionamos la respuesta. <\/p>\n<p>Cree un archivo llamado <strong>scrape.php<\/strong> y comience con el esqueleto b\u00e1sico:<\/p>\n<div style=\"margin: 20px 0 28px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 18px; margin: 0; font-size: 13px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;\"><code><span style=\"color: #9333ea;\">&lt;?php<\/span>\n<span style=\"color: #64748b;\">\/\/ scrape.php<\/span>\n\n<span style=\"color: #ea580c;\">require_once<\/span> <span style=\"color: #6366f1;\">__DIR__<\/span> . <span style=\"color: #dc2626;\">'\/vendor\/autoload.php'<\/span>;\n\n<span style=\"color: #ea580c;\">use<\/span> GuzzleHttp\\Client;\n\n<span style=\"color: #0891b2;\">$apiKey<\/span> = <span style=\"color: #dc2626;\">'YOUR_API_KEY'<\/span>;   <span style=\"color: #64748b;\">\/\/ Replace with your real key<\/span>\n<span style=\"color: #0891b2;\">$targetUrl<\/span> = <span style=\"color: #dc2626;\">'https:\/\/www.scrapethissite.com\/pages\/simple\/'<\/span>;\n\n<span style=\"color: #0891b2;\">$client<\/span> = <span style=\"color: #ea580c;\">new<\/span> <span style=\"color: #9333ea;\">Client<\/span>([\n    <span style=\"color: #dc2626;\">'base_uri'<\/span> =&gt; <span style=\"color: #dc2626;\">'https:\/\/client-api.floppy.host'<\/span>,\n    <span style=\"color: #dc2626;\">'timeout'<\/span> =&gt; <span style=\"color: #16a34a;\">60<\/span>,\n]);<\/code><\/pre>\n<\/div>\n<p>Sustituya YOUR_API_KEY por su clave real. Ahora construimos la llamada POST real. Enviamos JSON al punto final de la API, incluimos la clave de API en las cabeceras y pasamos la URL de destino m\u00e1s algunos argumentos en el cuerpo:  <\/p>\n<div style=\"margin: 20px 0 28px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 18px; margin: 0; font-size: 13px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;\"><code><span style=\"color: #0891b2;\">$response<\/span> = <span style=\"color: #0891b2;\">$client<\/span>-&gt;<span style=\"color: #9333ea;\">post<\/span>(<span style=\"color: #dc2626;\">'\/v1\/webUnlocker'<\/span>, [\n    <span style=\"color: #dc2626;\">'headers'<\/span> =&gt; [\n        <span style=\"color: #dc2626;\">'Content-Type'<\/span> =&gt; <span style=\"color: #dc2626;\">'application\/json'<\/span>,\n        <span style=\"color: #dc2626;\">'X-Api-Key'<\/span> =&gt; <span style=\"color: #0891b2;\">$apiKey<\/span>,\n    ],\n    <span style=\"color: #dc2626;\">'json'<\/span> =&gt; [\n        <span style=\"color: #dc2626;\">'url'<\/span> =&gt; <span style=\"color: #0891b2;\">$targetUrl<\/span>,\n        <span style=\"color: #dc2626;\">'country'<\/span> =&gt; <span style=\"color: #dc2626;\">'US'<\/span>,\n        <span style=\"color: #dc2626;\">'city'<\/span> =&gt; <span style=\"color: #dc2626;\">'New York'<\/span>,\n        <span style=\"color: #dc2626;\">'difficulty'<\/span> =&gt; <span style=\"color: #dc2626;\">'low'<\/span>,\n        <span style=\"color: #dc2626;\">'expiration'<\/span> =&gt; <span style=\"color: #16a34a;\">0<\/span>,\n    ],\n]);\n\n<span style=\"color: #0891b2;\">$payload<\/span> = <span style=\"color: #9333ea;\">json_decode<\/span>((<span style=\"color: #ea580c;\">string<\/span>) <span style=\"color: #0891b2;\">$response<\/span>-&gt;<span style=\"color: #9333ea;\">getBody<\/span>(), <span style=\"color: #6366f1;\">true<\/span>);\n<span style=\"color: #0891b2;\">$html<\/span> = <span style=\"color: #0891b2;\">$payload<\/span>[<span style=\"color: #dc2626;\">'html'<\/span>] ?? <span style=\"color: #dc2626;\">''<\/span>;\n<span style=\"color: #ea580c;\">echo<\/span> <span style=\"color: #dc2626;\">\"HTML received! Length: \"<\/span> . <span style=\"color: #9333ea;\">strlen<\/span>(<span style=\"color: #0891b2;\">$html<\/span>) . <span style=\"color: #dc2626;\">\" characters\\n\"<\/span>;<\/code><\/pre>\n<\/div>\n<p>Los campos de <strong>pa\u00eds<\/strong> y <strong>ciudad<\/strong> indican al Desbloqueador Web a trav\u00e9s de qu\u00e9 ubicaci\u00f3n geogr\u00e1fica debe enrutar la petici\u00f3n. El campo de <strong>dificultad<\/strong> controla la agresividad con la que el desbloqueador maneja las protecciones anti-bot. Aqu\u00ed utilizo <strong>el valor bajo<\/strong> porque nuestro objetivo sandbox no tiene ninguna protecci\u00f3n.  <\/p>\n<p>Para objetivos protegidos detr\u00e1s de Cloudflare o DataDome, config\u00farelo como <strong>medio<\/strong> para que el desbloqueador aplique una l\u00f3gica de huella digital y resoluci\u00f3n de CAPTCHA m\u00e1s fuerte.<\/p>\n<p>Ahora, tenga en cuenta que el Web Unlocker devuelve el HTML sin procesar dentro de un objeto JSON, lo que significa que necesita decodificar el JSON y extraer el marcado real de la p\u00e1gina del campo <strong>html<\/strong>.<\/p>\n<p>Si olvidas esto y tratas todo el cuerpo de la respuesta como HTML, tu analizador se romper\u00e1. Con esto, el lado de la solicitud se hace, y podemos pasar a analizar. <\/p>\n<h3>Paso n\u00ba 3: Inspeccionar la estructura de la p\u00e1gina<\/h3>\n<p>Una vez realizada la solicitud, el siguiente paso es inspeccionar la estructura de la p\u00e1gina y seleccionar los elementos repetidos que contienen los datos que deseamos. Cada pa\u00eds de la p\u00e1gina sigue exactamente este patr\u00f3n HTML: <\/p>\n<div style=\"margin: 20px 0 28px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 18px; margin: 0; font-size: 13px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;\"><code><span style=\"color: #9333ea;\">&lt;div<\/span> <span style=\"color: #16a34a;\">class=<\/span><span style=\"color: #dc2626;\">\"col-md-4 country\"<\/span><span style=\"color: #9333ea;\">&gt;<\/span>\n    <span style=\"color: #9333ea;\">&lt;h3<\/span> <span style=\"color: #16a34a;\">class=<\/span><span style=\"color: #dc2626;\">\"country-name\"<\/span><span style=\"color: #9333ea;\">&gt;<\/span>\n        <span style=\"color: #9333ea;\">&lt;i<\/span> <span style=\"color: #16a34a;\">class=<\/span><span style=\"color: #dc2626;\">\"flag-icon flag-icon-ad\"<\/span><span style=\"color: #9333ea;\">&gt;&lt;\/i&gt;<\/span>\n        Andorra\n    <span style=\"color: #9333ea;\">&lt;\/h3&gt;<\/span>\n    <span style=\"color: #9333ea;\">&lt;div<\/span> <span style=\"color: #16a34a;\">class=<\/span><span style=\"color: #dc2626;\">\"country-info\"<\/span><span style=\"color: #9333ea;\">&gt;<\/span>\n        <span style=\"color: #9333ea;\">&lt;strong&gt;<\/span>Capital:<span style=\"color: #9333ea;\">&lt;\/strong&gt;<\/span> <span style=\"color: #9333ea;\">&lt;span<\/span> <span style=\"color: #16a34a;\">class=<\/span><span style=\"color: #dc2626;\">\"country-capital\"<\/span><span style=\"color: #9333ea;\">&gt;<\/span>Andorra la Vella<span style=\"color: #9333ea;\">&lt;\/span&gt;&lt;br&gt;<\/span>\n        <span style=\"color: #9333ea;\">&lt;strong&gt;<\/span>Population:<span style=\"color: #9333ea;\">&lt;\/strong&gt;<\/span> <span style=\"color: #9333ea;\">&lt;span<\/span> <span style=\"color: #16a34a;\">class=<\/span><span style=\"color: #dc2626;\">\"country-population\"<\/span><span style=\"color: #9333ea;\">&gt;<\/span>84000<span style=\"color: #9333ea;\">&lt;\/span&gt;&lt;br&gt;<\/span>\n        <span style=\"color: #9333ea;\">&lt;strong&gt;<\/span>Area (km<span style=\"color: #9333ea;\">&lt;sup&gt;<\/span>2<span style=\"color: #9333ea;\">&lt;\/sup&gt;<\/span>):<span style=\"color: #9333ea;\">&lt;\/strong&gt;<\/span> <span style=\"color: #9333ea;\">&lt;span<\/span> <span style=\"color: #16a34a;\">class=<\/span><span style=\"color: #dc2626;\">\"country-area\"<\/span><span style=\"color: #9333ea;\">&gt;<\/span>468.0<span style=\"color: #9333ea;\">&lt;\/span&gt;&lt;br&gt;<\/span>\n    <span style=\"color: #9333ea;\">&lt;\/div&gt;<\/span>\n<span style=\"color: #9333ea;\">&lt;\/div&gt;<\/span><\/code><\/pre>\n<\/div>\n<p>Esa estructura repetida es lo que hace que esta p\u00e1gina sea maravillosamente predecible. Cada ficha de pa\u00eds utiliza los mismos nombres de clase: <strong>.country<\/strong> para la envoltura, <strong>.country-name<\/strong> para el encabezamiento y<strong>.country-capital<\/strong>, <strong>.country-population<\/strong> y <strong>.country-area<\/strong> para los campos de datos dentro de <strong>.country-info<\/strong>. <\/p>\n<h3>Paso 4: An\u00e1lisis de datos con Symfony DomCrawler<\/h3>\n<p>Como las clases son coherentes en las 250 entradas, podemos recorrer cada elemento <strong>.country<\/strong> y extraer los valores de los selectores hijos. Pero antes, vamos a a\u00f1adir una peque\u00f1a funci\u00f3n de ayuda para limpiar el texto que extraigamos: <\/p>\n<div style=\"margin: 20px 0 28px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 18px; margin: 0; font-size: 13px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;\"><code><span style=\"color: #ea580c;\">use<\/span> Symfony\\Component\\DomCrawler\\Crawler;\n\n<span style=\"color: #ea580c;\">function<\/span> <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #ea580c;\">string<\/span> <span style=\"color: #0891b2;\">$text<\/span>): <span style=\"color: #ea580c;\">string<\/span>\n{\n    <span style=\"color: #ea580c;\">return<\/span> <span style=\"color: #9333ea;\">preg_replace<\/span>(<span style=\"color: #dc2626;\">'\/\\s+\/'<\/span>, <span style=\"color: #dc2626;\">' '<\/span>, <span style=\"color: #9333ea;\">trim<\/span>(<span style=\"color: #0891b2;\">$text<\/span>)) ?? <span style=\"color: #9333ea;\">trim<\/span>(<span style=\"color: #0891b2;\">$text<\/span>);\n}<\/code><\/pre>\n<\/div>\n<p>Si observa el c\u00f3digo HTML sin formato, ver\u00e1 que los nombres de los pa\u00edses tienen espacios en blanco y nuevas l\u00edneas a su alrededor debido a las etiquetas &lt;i&gt; icono de bandera dentro de &lt;h3&gt;.<\/p>\n<p>La funci\u00f3n <em>normalizeText(<\/em> ) ayuda a eliminar los espacios en blanco iniciales y finales y, a continuaci\u00f3n, utiliza una regex para contraer los espacios o nuevas l\u00edneas restantes, de modo que nombres como Andorra o St. John&#8217;s aparezcan de forma limpia en lugar de arrastrar los espacios en blanco sobrantes del HTML.<\/p>\n<p>Con el helper listo, creamos una instancia de Crawler y hacemos un bucle sobre cada tarjeta de pa\u00eds:<\/p>\n<div style=\"margin: 20px 0 28px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 18px; margin: 0; font-size: 13px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;\"><code><span style=\"color: #0891b2;\">$crawler<\/span> = <span style=\"color: #ea580c;\">new<\/span> <span style=\"color: #9333ea;\">Crawler<\/span>(<span style=\"color: #0891b2;\">$html<\/span>);\n<span style=\"color: #0891b2;\">$countries<\/span> = [];\n\n<span style=\"color: #0891b2;\">$crawler<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country'<\/span>)-&gt;<span style=\"color: #9333ea;\">each<\/span>(<span style=\"color: #ea580c;\">function<\/span> (<span style=\"color: #9333ea;\">Crawler<\/span> <span style=\"color: #0891b2;\">$node<\/span>) <span style=\"color: #ea580c;\">use<\/span> (&<span style=\"color: #0891b2;\">$countries<\/span>): <span style=\"color: #ea580c;\">void<\/span> {\n    <span style=\"color: #0891b2;\">$countries<\/span>[] = [\n        <span style=\"color: #dc2626;\">'name'<\/span> =&gt; <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #0891b2;\">$node<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country-name'<\/span>)-&gt;<span style=\"color: #9333ea;\">text<\/span>()),\n        <span style=\"color: #dc2626;\">'capital'<\/span> =&gt; <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #0891b2;\">$node<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country-capital'<\/span>)-&gt;<span style=\"color: #9333ea;\">text<\/span>()),\n        <span style=\"color: #dc2626;\">'population'<\/span> =&gt; <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #0891b2;\">$node<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country-population'<\/span>)-&gt;<span style=\"color: #9333ea;\">text<\/span>()),\n        <span style=\"color: #dc2626;\">'area'<\/span> =&gt; <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #0891b2;\">$node<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country-area'<\/span>)-&gt;<span style=\"color: #9333ea;\">text<\/span>()),\n    ];\n});\n\n<span style=\"color: #ea580c;\">echo<\/span> <span style=\"color: #dc2626;\">'Parsed '<\/span> . <span style=\"color: #9333ea;\">count<\/span>(<span style=\"color: #0891b2;\">$countries<\/span>) . <span style=\"color: #dc2626;\">\" countries\\n\"<\/span>;<\/code><\/pre>\n<\/div>\n<p>DomCrawler nos ofrece una forma limpia de movernos por el HTML utilizando selectores CSS. Empezamos envolviendo el HTML en un objeto Crawler y luego filtramos hasta cada bloque .country de la p\u00e1gina. Dentro de cada bloque, obtenemos el nombre, la capital, la poblaci\u00f3n y el \u00e1rea.<\/p>\n<p>En este punto, si ejecutas el script, deber\u00edas ver \u00abParsed 250 countries\u00bb impreso en el terminal.<\/p>\n<h3>Paso n\u00ba 5: Exportar los resultados a CSV y JSON<\/h3>\n<p>Una vez que el analizador sint\u00e1ctico le proporciona una matriz <strong>$countries<\/strong>, exportar los datos resulta muy sencillo.<\/p>\n<div style=\"margin: 20px 0 28px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 18px; margin: 0; font-size: 13px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;\"><code><span style=\"color: #0891b2;\">$csvHandle<\/span> = <span style=\"color: #9333ea;\">fopen<\/span>(<span style=\"color: #dc2626;\">'countries.csv'<\/span>, <span style=\"color: #dc2626;\">'w'<\/span>);\n\n<span style=\"color: #9333ea;\">fputcsv<\/span>(<span style=\"color: #0891b2;\">$csvHandle<\/span>, [<span style=\"color: #dc2626;\">'Country'<\/span>, <span style=\"color: #dc2626;\">'Capital'<\/span>, <span style=\"color: #dc2626;\">'Population'<\/span>, <span style=\"color: #dc2626;\">'Area (km2)'<\/span>], <span style=\"color: #dc2626;\">','<\/span>, <span style=\"color: #dc2626;\">'\"'<\/span>, <span style=\"color: #dc2626;\">''<\/span>);\n\n<span style=\"color: #ea580c;\">foreach<\/span> (<span style=\"color: #0891b2;\">$countries<\/span> <span style=\"color: #ea580c;\">as<\/span> <span style=\"color: #0891b2;\">$country<\/span>) {\n    <span style=\"color: #9333ea;\">fputcsv<\/span>(<span style=\"color: #0891b2;\">$csvHandle<\/span>, [\n        <span style=\"color: #0891b2;\">$country<\/span>[<span style=\"color: #dc2626;\">'name'<\/span>],\n        <span style=\"color: #0891b2;\">$country<\/span>[<span style=\"color: #dc2626;\">'capital'<\/span>],\n        <span style=\"color: #0891b2;\">$country<\/span>[<span style=\"color: #dc2626;\">'population'<\/span>],\n        <span style=\"color: #0891b2;\">$country<\/span>[<span style=\"color: #dc2626;\">'area'<\/span>],\n    ], <span style=\"color: #dc2626;\">','<\/span>, <span style=\"color: #dc2626;\">'\"'<\/span>, <span style=\"color: #dc2626;\">''<\/span>);\n}\n\n<span style=\"color: #9333ea;\">fclose<\/span>(<span style=\"color: #0891b2;\">$csvHandle<\/span>);\n\n<span style=\"color: #9333ea;\">file_put_contents<\/span>(<span style=\"color: #dc2626;\">'countries.json'<\/span>, <span style=\"color: #9333ea;\">json_encode<\/span>(<span style=\"color: #0891b2;\">$countries<\/span>, <span style=\"color: #6366f1;\">JSON_PRETTY_PRINT<\/span> | <span style=\"color: #6366f1;\">JSON_UNESCAPED_SLASHES<\/span>));<\/code><\/pre>\n<\/div>\n<p>La exportaci\u00f3n CSV es \u00fatil porque proporciona a los lectores un archivo que pueden abrir inmediatamente en Excel, Google Sheets o cualquier otra herramienta de hoja de c\u00e1lculo. La exportaci\u00f3n JSON es igual de \u00fatil si quieren introducir los datos raspados en otro script PHP o en una API m\u00e1s adelante. <\/p>\n<p>Una peque\u00f1a actualizaci\u00f3n aqu\u00ed es el argumento escape expl\u00edcito en <strong>fputcsv()<\/strong>. En las nuevas versiones de PHP, esto evita las advertencias de depreciaci\u00f3n y mantiene el ejemplo limpio cuando los lectores lo ejecutan desde el terminal. <\/p>\n<h3>Paso n\u00ba 6: Juntarlo todo en un gui\u00f3n<\/h3>\n<p>Ahora que cada parte funciona por s\u00ed sola, aqu\u00ed est\u00e1 el script completo:<\/p>\n<div style=\"margin: 20px 0 28px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 18px; margin: 0; font-size: 13px; line-height: 1.6; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;\"><code><span style=\"color: #9333ea;\">&lt;?php<\/span>\n\n<span style=\"color: #9333ea;\">declare<\/span>(<span style=\"color: #6366f1;\">strict_types<\/span>=<span style=\"color: #16a34a;\">1<\/span>);\n\n<span style=\"color: #ea580c;\">require_once<\/span> <span style=\"color: #6366f1;\">__DIR__<\/span> . <span style=\"color: #dc2626;\">'\/vendor\/autoload.php'<\/span>;\n\n<span style=\"color: #ea580c;\">use<\/span> GuzzleHttp\\Client;\n<span style=\"color: #ea580c;\">use<\/span> Symfony\\Component\\DomCrawler\\Crawler;\n\n<span style=\"color: #ea580c;\">function<\/span> <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #ea580c;\">string<\/span> <span style=\"color: #0891b2;\">$text<\/span>): <span style=\"color: #ea580c;\">string<\/span>\n{\n    <span style=\"color: #ea580c;\">return<\/span> <span style=\"color: #9333ea;\">preg_replace<\/span>(<span style=\"color: #dc2626;\">'\/\\s+\/'<\/span>, <span style=\"color: #dc2626;\">' '<\/span>, <span style=\"color: #9333ea;\">trim<\/span>(<span style=\"color: #0891b2;\">$text<\/span>)) ?? <span style=\"color: #9333ea;\">trim<\/span>(<span style=\"color: #0891b2;\">$text<\/span>);\n}\n\n<span style=\"color: #0891b2;\">$apiKey<\/span> = <span style=\"color: #dc2626;\">'YOUR_API_KEY'<\/span>;\n<span style=\"color: #0891b2;\">$targetUrl<\/span> = <span style=\"color: #dc2626;\">'https:\/\/www.scrapethissite.com\/pages\/simple\/'<\/span>;\n\n<span style=\"color: #ea580c;\">if<\/span> (<span style=\"color: #0891b2;\">$apiKey<\/span> === <span style=\"color: #dc2626;\">'YOUR_API_KEY'<\/span>) {\n    <span style=\"color: #9333ea;\">fwrite<\/span>(<span style=\"color: #6366f1;\">STDERR<\/span>, <span style=\"color: #dc2626;\">\"Replace YOUR_API_KEY before running the script.\\n\"<\/span>);\n    <span style=\"color: #ea580c;\">exit<\/span>(<span style=\"color: #16a34a;\">1<\/span>);\n}\n\n<span style=\"color: #0891b2;\">$client<\/span> = <span style=\"color: #ea580c;\">new<\/span> <span style=\"color: #9333ea;\">Client<\/span>([\n    <span style=\"color: #dc2626;\">'base_uri'<\/span> =&gt; <span style=\"color: #dc2626;\">'https:\/\/client-api.floppy.host'<\/span>,\n    <span style=\"color: #dc2626;\">'timeout'<\/span> =&gt; <span style=\"color: #16a34a;\">60<\/span>,\n]);\n\n<span style=\"color: #ea580c;\">try<\/span> {\n    <span style=\"color: #0891b2;\">$response<\/span> = <span style=\"color: #0891b2;\">$client<\/span>-&gt;<span style=\"color: #9333ea;\">post<\/span>(<span style=\"color: #dc2626;\">'\/v1\/webUnlocker'<\/span>, [\n        <span style=\"color: #dc2626;\">'headers'<\/span> =&gt; [\n            <span style=\"color: #dc2626;\">'Content-Type'<\/span> =&gt; <span style=\"color: #dc2626;\">'application\/json'<\/span>,\n            <span style=\"color: #dc2626;\">'X-Api-Key'<\/span> =&gt; <span style=\"color: #0891b2;\">$apiKey<\/span>,\n        ],\n        <span style=\"color: #dc2626;\">'json'<\/span> =&gt; [\n            <span style=\"color: #dc2626;\">'url'<\/span> =&gt; <span style=\"color: #0891b2;\">$targetUrl<\/span>,\n            <span style=\"color: #dc2626;\">'country'<\/span> =&gt; <span style=\"color: #dc2626;\">'US'<\/span>,\n            <span style=\"color: #dc2626;\">'city'<\/span> =&gt; <span style=\"color: #dc2626;\">'New York'<\/span>,\n            <span style=\"color: #dc2626;\">'difficulty'<\/span> =&gt; <span style=\"color: #dc2626;\">'low'<\/span>,\n            <span style=\"color: #dc2626;\">'expiration'<\/span> =&gt; <span style=\"color: #16a34a;\">0<\/span>,\n        ],\n    ]);\n} <span style=\"color: #ea580c;\">catch<\/span> (<span style=\"color: #9333ea;\">Throwable<\/span> <span style=\"color: #0891b2;\">$e<\/span>) {\n    <span style=\"color: #9333ea;\">fwrite<\/span>(<span style=\"color: #6366f1;\">STDERR<\/span>, <span style=\"color: #dc2626;\">\"Request failed: {<\/span><span style=\"color: #0891b2;\">$e<\/span>-><span style=\"color: #9333ea;\">getMessage<\/span>()<span style=\"color: #dc2626;\">}\\n\"<\/span>);\n    <span style=\"color: #ea580c;\">exit<\/span>(<span style=\"color: #16a34a;\">1<\/span>);\n}\n\n<span style=\"color: #0891b2;\">$payload<\/span> = <span style=\"color: #9333ea;\">json_decode<\/span>((<span style=\"color: #ea580c;\">string<\/span>) <span style=\"color: #0891b2;\">$response<\/span>-&gt;<span style=\"color: #9333ea;\">getBody<\/span>(), <span style=\"color: #6366f1;\">true<\/span>);\n\n<span style=\"color: #ea580c;\">if<\/span> (!<span style=\"color: #9333ea;\">is_array<\/span>(<span style=\"color: #0891b2;\">$payload<\/span>) || !<span style=\"color: #9333ea;\">isset<\/span>(<span style=\"color: #0891b2;\">$payload<\/span>[<span style=\"color: #dc2626;\">'html'<\/span>]) || !<span style=\"color: #9333ea;\">is_string<\/span>(<span style=\"color: #0891b2;\">$payload<\/span>[<span style=\"color: #dc2626;\">'html'<\/span>])) {\n    <span style=\"color: #9333ea;\">fwrite<\/span>(<span style=\"color: #6366f1;\">STDERR<\/span>, <span style=\"color: #dc2626;\">\"Unexpected API response. Expected JSON with an html field.\\n\"<\/span>);\n    <span style=\"color: #ea580c;\">exit<\/span>(<span style=\"color: #16a34a;\">1<\/span>);\n}\n\n<span style=\"color: #0891b2;\">$crawler<\/span> = <span style=\"color: #ea580c;\">new<\/span> <span style=\"color: #9333ea;\">Crawler<\/span>(<span style=\"color: #0891b2;\">$payload<\/span>[<span style=\"color: #dc2626;\">'html'<\/span>]);\n<span style=\"color: #0891b2;\">$countries<\/span> = [];\n\n<span style=\"color: #0891b2;\">$crawler<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country'<\/span>)-&gt;<span style=\"color: #9333ea;\">each<\/span>(<span style=\"color: #ea580c;\">function<\/span> (<span style=\"color: #9333ea;\">Crawler<\/span> <span style=\"color: #0891b2;\">$node<\/span>) <span style=\"color: #ea580c;\">use<\/span> (&<span style=\"color: #0891b2;\">$countries<\/span>): <span style=\"color: #ea580c;\">void<\/span> {\n    <span style=\"color: #0891b2;\">$countries<\/span>[] = [\n        <span style=\"color: #dc2626;\">'name'<\/span> =&gt; <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #0891b2;\">$node<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country-name'<\/span>)-&gt;<span style=\"color: #9333ea;\">text<\/span>()),\n        <span style=\"color: #dc2626;\">'capital'<\/span> =&gt; <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #0891b2;\">$node<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country-capital'<\/span>)-&gt;<span style=\"color: #9333ea;\">text<\/span>()),\n        <span style=\"color: #dc2626;\">'population'<\/span> =&gt; <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #0891b2;\">$node<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country-population'<\/span>)-&gt;<span style=\"color: #9333ea;\">text<\/span>()),\n        <span style=\"color: #dc2626;\">'area'<\/span> =&gt; <span style=\"color: #9333ea;\">normalizeText<\/span>(<span style=\"color: #0891b2;\">$node<\/span>-&gt;<span style=\"color: #9333ea;\">filter<\/span>(<span style=\"color: #dc2626;\">'.country-area'<\/span>)-&gt;<span style=\"color: #9333ea;\">text<\/span>()),\n    ];\n});\n\n<span style=\"color: #ea580c;\">if<\/span> (<span style=\"color: #0891b2;\">$countries<\/span> === []) {\n    <span style=\"color: #9333ea;\">fwrite<\/span>(<span style=\"color: #6366f1;\">STDERR<\/span>, <span style=\"color: #dc2626;\">\"No countries were parsed.\\n\"<\/span>);\n    <span style=\"color: #ea580c;\">exit<\/span>(<span style=\"color: #16a34a;\">1<\/span>);\n}\n\n<span style=\"color: #0891b2;\">$csvHandle<\/span> = <span style=\"color: #9333ea;\">fopen<\/span>(<span style=\"color: #6366f1;\">__DIR__<\/span> . <span style=\"color: #dc2626;\">'\/countries.csv'<\/span>, <span style=\"color: #dc2626;\">'w'<\/span>);\n\n<span style=\"color: #ea580c;\">if<\/span> (<span style=\"color: #0891b2;\">$csvHandle<\/span> === <span style=\"color: #6366f1;\">false<\/span>) {\n    <span style=\"color: #9333ea;\">fwrite<\/span>(<span style=\"color: #6366f1;\">STDERR<\/span>, <span style=\"color: #dc2626;\">\"Could not create countries.csv.\\n\"<\/span>);\n    <span style=\"color: #ea580c;\">exit<\/span>(<span style=\"color: #16a34a;\">1<\/span>);\n}\n\n<span style=\"color: #9333ea;\">fputcsv<\/span>(<span style=\"color: #0891b2;\">$csvHandle<\/span>, [<span style=\"color: #dc2626;\">'Country'<\/span>, <span style=\"color: #dc2626;\">'Capital'<\/span>, <span style=\"color: #dc2626;\">'Population'<\/span>, <span style=\"color: #dc2626;\">'Area (km2)'<\/span>], <span style=\"color: #dc2626;\">','<\/span>, <span style=\"color: #dc2626;\">'\"'<\/span>, <span style=\"color: #dc2626;\">''<\/span>);\n\n<span style=\"color: #ea580c;\">foreach<\/span> (<span style=\"color: #0891b2;\">$countries<\/span> <span style=\"color: #ea580c;\">as<\/span> <span style=\"color: #0891b2;\">$country<\/span>) {\n    <span style=\"color: #9333ea;\">fputcsv<\/span>(<span style=\"color: #0891b2;\">$csvHandle<\/span>, [\n        <span style=\"color: #0891b2;\">$country<\/span>[<span style=\"color: #dc2626;\">'name'<\/span>],\n        <span style=\"color: #0891b2;\">$country<\/span>[<span style=\"color: #dc2626;\">'capital'<\/span>],\n        <span style=\"color: #0891b2;\">$country<\/span>[<span style=\"color: #dc2626;\">'population'<\/span>],\n        <span style=\"color: #0891b2;\">$country<\/span>[<span style=\"color: #dc2626;\">'area'<\/span>],\n    ], <span style=\"color: #dc2626;\">','<\/span>, <span style=\"color: #dc2626;\">'\"'<\/span>, <span style=\"color: #dc2626;\">''<\/span>);\n}\n\n<span style=\"color: #9333ea;\">fclose<\/span>(<span style=\"color: #0891b2;\">$csvHandle<\/span>);\n\n<span style=\"color: #9333ea;\">file_put_contents<\/span>(<span style=\"color: #6366f1;\">__DIR__<\/span> . <span style=\"color: #dc2626;\">'\/countries.json'<\/span>, <span style=\"color: #9333ea;\">json_encode<\/span>(<span style=\"color: #0891b2;\">$countries<\/span>, <span style=\"color: #6366f1;\">JSON_PRETTY_PRINT<\/span> | <span style=\"color: #6366f1;\">JSON_UNESCAPED_SLASHES<\/span>));\n\n<span style=\"color: #ea580c;\">echo<\/span> <span style=\"color: #dc2626;\">'Done! Parsed '<\/span> . <span style=\"color: #9333ea;\">count<\/span>(<span style=\"color: #0891b2;\">$countries<\/span>) . <span style=\"color: #dc2626;\">\" countries.\\n\"<\/span>;\n<span style=\"color: #ea580c;\">echo<\/span> <span style=\"color: #dc2626;\">\"Saved countries.csv and countries.json\\n\"<\/span>;<\/code><\/pre>\n<\/div>\n<p>Sustituye &#8216;YOUR_API_KEY&#8217; y ejec\u00fatalo as\u00ed:<\/p>\n<div style=\"margin: 18px 0 26px 0;\">\n<pre style=\"background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px 18px; margin: 0; font-size: 14px; line-height: 1.7;\"><code><span style=\"color: #9333ea;\">php<\/span> scrape.php<\/code><\/pre>\n<\/div>\n<p>Cuando todo est\u00e9 configurado correctamente, el script obtendr\u00e1 la p\u00e1gina a trav\u00e9s de Web Unlocker, analizar\u00e1 los 250 pa\u00edses y escribir\u00e1 countries.csv y countries.json en la carpeta del proyecto.<\/p>\n<h3>Ver los resultados<\/h3>\n<p>Una vez finalizado el script, puede abrir countries.csv inmediatamente. Las primeras filas tendr\u00e1n el siguiente aspecto: <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-44159\" src=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image2-2.png\" alt=\"pa\u00edses.csv\" width=\"1152\" height=\"1148\" srcset=\"https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image2-2.png 1152w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image2-2-300x300.png 300w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image2-2-1024x1020.png 1024w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image2-2-150x150.png 150w, https:\/\/floppydata.com\/wp-content\/uploads\/2026\/05\/image2-2-768x765.png 768w\" sizes=\"(max-width: 1152px) 100vw, 1152px\" \/><\/p>\n<p>Ahora puede importar el CSV a una hoja de c\u00e1lculo o enviar el JSON a otra aplicaci\u00f3n. Si quieres utilizar este flujo de trabajo para el seguimiento de precios, puedes emparejarlo con <a href=\"https:\/\/floppydata.com\/use-case\/price-monitoring-proxy\/\">los proxies de seguimiento de precios<\/a> de Floppydata, as\u00ed que haz bien en comprobarlo. <\/p>\n<h2>Medidas contra el chantaje<\/h2>\n<p>Una p\u00e1gina simple y est\u00e1tica no es dif\u00edcil de analizar, como acabamos de ver. Pero los sitios protegidos pueden ser un quebradero de cabeza. <\/p>\n<p>Podr\u00edas encontrarte con bloqueos, datos que faltan, CAPTCHAs, renderizado de JavaScript o l\u00edmites de velocidad. Ah\u00ed es donde un scraper PHP normal empieza a tener problemas. <\/p>\n<p>Estos son los problemas m\u00e1s comunes a los que puede enfrentarse:<\/p>\n<ul>\n<li><strong>Bloqueo de IP:<\/strong> Los sitios web pueden bloquear tu direcci\u00f3n IP si detectan varias solicitudes procedentes de la misma IP en un breve periodo de tiempo.<\/li>\n<li><strong>CAPTCHAs:<\/strong> Los sistemas CAPTCHA se utilizan para diferenciar entre bots y humanos presentando retos dif\u00edciles de resolver para los bots.<\/li>\n<li><strong>Limitaci\u00f3n de la tasa:<\/strong> Los sitios web suelen limitar el n\u00famero de solicitudes que se pueden realizar en un periodo de tiempo determinado para evitar un scraping excesivo.<\/li>\n<li><strong>Detecci\u00f3n de agentes de usuario:<\/strong> Los agentes de usuario ajenos al navegador se bloquean porque no se parecen a los visitantes reales.<\/li>\n<li><strong>Desaf\u00edos de JavaScript:<\/strong> El contenido s\u00f3lo se carga tras la ejecuci\u00f3n de JavaScript, algo que una petici\u00f3n HTTP simple puede pasar por alto.<\/li>\n<\/ul>\n<p>Puede intentar resolver estos problemas manualmente, pero no es conveniente ni escalable.<\/p>\n<p>Ah\u00ed es donde entra Floppydata Web Unlocker. En lugar de resolver cada desaf\u00edo usted mismo, puede descargar toda la capa anti-bot y centrarse en extraer y almacenar los datos. <\/p>\n<p><strong>Floppydata Web Unlocker se encarga:<\/strong><\/p>\n<ul>\n<li>Rotaci\u00f3n de IP con un gran grupo de proxies residenciales y de centros de datos<\/li>\n<li>Huella digital del navegador y navegadores sin cabeza<\/li>\n<li>Procesamiento de JavaScript para p\u00e1ginas din\u00e1micas<\/li>\n<li>Reintentos autom\u00e1ticos y resoluci\u00f3n de CAPTCHA<\/li>\n<li>Segmentaci\u00f3n geogr\u00e1fica por ciudades<\/li>\n<\/ul>\n<p>Si necesita m\u00e1s control, Floppydata tambi\u00e9n ofrece <a href=\"https:\/\/floppydata.com\/proxy-type\/isp-proxy\/\">proxies residenciales est\u00e1ticos<\/a> para el scraping de sesiones largas y <a href=\"https:\/\/floppydata.com\/proxy-type\/datacenter-proxy\/\">proxies de centros de datos<\/a> para el trabajo de volumen a alta velocidad.<\/p>\n<p>Pero para la mayor\u00eda de las p\u00e1ginas protegidas, Web Unlocker es la forma m\u00e1s r\u00e1pida de pasar de una petici\u00f3n bloqueada a HTML analizable.<\/p>\n<h2>Reflexiones finales<\/h2>\n<p>PHP es un lenguaje muy capaz para el web scraping, y por ahora, usted debe tener una base s\u00f3lida en web scraping con PHP.<\/p>\n<p>Detendr\u00e9 este tutorial en este punto ya que es una introducci\u00f3n al web scraping con PHP. En futuros tutoriales, vamos a ampliar nuestro scraper para que pueda seguir enlaces, manejar la paginaci\u00f3n, y raspar objetivos m\u00e1s complejos. <\/p>\n<p>Si mientras tanto quieres saber m\u00e1s sobre el web scraping, consulta estos recursos:<\/p>\n<ul>\n<li><a href=\"https:\/\/floppydata.com\/blog\/what-is-web-unblocker\/\">\u00bfQu\u00e9 es un desbloqueador web?<\/a><\/li>\n<li><a href=\"https:\/\/floppydata.com\/blog\/best-proxies-for-web-scrapers\/\">Los mejores proxies para Web Scrapers<\/a><\/li>\n<\/ul>\n<p>\u00bfListo para probar Web Unlocker? <a href=\"https:\/\/app.floppydata.com\/\">Empiece hoy mismo<\/a> con 5 raspados gratuitos y raspe cualquier cosa sin quebraderos de cabeza.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introducci\u00f3n PHP fue uno de mis primeros lenguajes como desarrollador web en su d\u00eda, y todav\u00eda me gusta usarlo para scraping. Este tutorial cubre lo que realmente uso en producci\u00f3n. Te guiar\u00e9 a trav\u00e9s de un flujo de trabajo completo de raspado web PHP usando Floppydata Web Unlocker como la capa de raspado. \u00bfPor qu\u00e9 [&hellip;]<\/p>\n","protected":false},"author":15,"featured_media":44220,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[563,149,545],"tags":[],"class_list":["post-44367","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scraping","category-blog","category-how-to"],"acf":[],"_links":{"self":[{"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/posts\/44367","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/comments?post=44367"}],"version-history":[{"count":0,"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/posts\/44367\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/media\/44220"}],"wp:attachment":[{"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/media?parent=44367"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/categories?post=44367"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/floppydata.com\/es\/wp-json\/wp\/v2\/tags?post=44367"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}