Por qué tu scraper de Elixir es bloqueado y cómo los proxies residenciales + el navegador en la nube lo solucionan.
Scraping and Proxy Management Expert
Conclusiones Clave:
- Elixir está diseñado para el raspado concurrente. El entorno de ejecución BEAM teje miles de procesos ligeros en un solo nodo, por lo que un rastreo que se expande a través de cientos de URL se ejecuta como un simple
Task.async_streamen lugar de un grupo de hilos que debes supervisar. - Req y HTTPoison recuperan, Floki analiza.
Reqes el cliente HTTP moderno, con todo incluido;HTTPoisones la opción de respaldo tradicional;Flokiconvierte HTML en bruto en un árbol que consultas con selectores CSS. Juntos cubren cualquier página que envíe markup renderizado. - Crawly es el marco completo de rastreo. Programa solicitudes entre trabajadores, maneja paginación a través de solicitudes de seguimiento, aplica middleware para rotación de agente de usuario y opciones de solicitud, y empuja elementos analizados por un pipeline — al estilo de Scrapy, pero en BEAM.
- Proxies residenciales de Scrapeless dirigen las solicitudes. Un único host de proxy, puerto y encabezado de autenticación básica se conectan directamente a las
connect_optionsdeReq,:proxy/:proxy_authde HTTPoison, o al middlewareRequestOptionsde Crawly, otorgando a cada solicitud una IP residencial y fijando la geografía de salida. - Los sitios con mucho JS y objetivos anti-bots escalan al Navegador de Raspado de Scrapeless. Elixir no controla el Protocolo de Herramientas para Desarrolladores de Chrome tan fácilmente como Node o Python, por lo que el navegador en la nube se alcanza de dos maneras: solicitudes HTTP a través de proxies residenciales de Scrapeless para la mayoría de los renderizados, y una pequeña llamada de navegador en la nube para la minoría renderizada del lado del cliente.
- Gratis para comenzar. Las nuevas cuentas de Scrapeless incluyen tiempo de ejecución gratuito del Navegador de Raspado — regístrate en app.scrapeless.com.
Introducción: ¿Por qué Elixir y dónde comienza la fricción?
Elixir se ejecuta en BEAM, la misma máquina virtual que ha mantenido en línea a los conmutadores de telecomunicaciones Erlang durante décadas. Su rasgo definitorio para el raspado es la concurrencia económica: generar diez mil procesos es rutinario, cada uno aislado, cada uno capaz de manejar una única solicitud HTTP en vuelo sin bloquear a los demás. Un rastreo que requeriría un marco asíncrono y una cuidadosa sintonización del grupo en otro lenguaje es un Task.async_stream con un límite de max_concurrency en Elixir.
La historia de las bibliotecas es madura. Req y HTTPoison obtienen páginas, Floki las analiza con selectores CSS, y Crawly envuelve todo el ciclo — programación, desduplicación, paginación y pipelines de elementos — en un marco al estilo de Scrapy que aún se siente idiomático de Elixir. Para catálogos estáticos, sitemaps y páginas renderizadas en servidor, ese stack está completo por sí solo.
Dos cosas lo rompen. Primero, reputación de IP: una dirección limpia de centro de datos se marca en el momento en que un objetivo ejecuta incluso un gestor básico de bots, y ninguna cantidad de ajustes de encabezados soluciona una IP de salida bloqueada. Segundo, renderización del lado del cliente: una aplicación de una sola página devuelve HTTP 200 con un <div id="app"> vacío, y Floki analiza exactamente lo que llegó — nada. La página parece completa en un navegador y vacía para el raspador.
Esta guía construye el stack de Elixir en capas. La capa HTTP utiliza Req, HTTPoison y Crawly enrutados a través de proxies residenciales de Scrapeless en más de 195 países. La capa de JS renderizado escala al Navegador de Raspado de Scrapeless, alcanzado desde Elixir sin pedirle al BEAM que hable CDP directamente. Para la capa de proxy residencial por la que se enrutaron estas recuperaciones, consulta ¿Qué es un Proxy SSL?.
Lo Que Puedes Construir
El patrón de dos capas — bibliotecas de Elixir al frente, Scrapeless detrás de la escalada — cubre la mayoría de los trabajos que derrotan a un raspador HTTP simple:
- Rastreo de catálogos concurrentes. Sitemaps, archivos de artículos, listados de productos —
Task.async_streamse expande a través del conjunto de URL con un número limitado de trabajadores y analiza cada página con Floki. - Monitoreo programado con Crawly. Define una araña una vez, deja que revise los listados en un horario y empuja elementos analizados por un pipeline de validación y almacenamiento.
- Instantáneas geoespecíficas. Fija el país del proxy de Scrapeless para que precios, disponibilidad y muros de consentimiento devuelvan lo que un usuario local ve, no lo que sea que la IP de tu servidor resuelva.
- Extracción resistente detrás de gestores de bots. Enruta la solicitud a través de salida residencial para que una IP de hogar ordinaria, no un rango de centro de datos, realice la solicitud.
- Ingesta de RAG y LLM. Renderiza páginas de publicación y documentación a texto limpio, luego alimenta el contenido extraído en un pipeline de embedding.
- Páginas SPA e infinitas desplazamientos. Escala la minoría renderizada del lado del cliente al Navegador de Raspado de Scrapeless, que ejecuta el JavaScript en la nube antes de que analices el resultado.
Por Qué Emparejar Elixir con Scrapeless
Elixir te ofrece concurrencia, análisis y un marco de rastreo; el Navegador de Raspado Sin Reglas proporciona la infraestructura de salida y renderizado que un cliente HTTP del lado del servidor no puede. Los dos se complementan porque la transferencia es un proxy HTTP estándar por un lado y un endpoint de navegador en la nube documentado por el otro.
- Proxies residenciales en más de 195 países. Expuestos como un solo host de proxy, puerto y credenciales de autenticación básica, se integran directamente en
Req,HTTPoisono el middlewareRequestOptionsde Crawly. - Geolocalización por solicitud. Un código de país en el nombre de usuario del proxy controla la geografía de salida sin apretón de manos adicional, por lo que el mismo código obtiene vistas de EE. UU., GB, DE o JP al intercambiar un segmento.
- Navegador en la nube anti-detección. Para páginas renderizadas del lado del cliente, el Navegador de Raspado Sin Reglas ejecuta un Chromium desarrollado internamente con renderizado de JavaScript completo del lado de la nube y aleatorización de huellas digitales por sesión, de modo que las aplicaciones de una sola página (SPA) y los paneles cargados de forma perezosa se hidratan antes de la extracción.
- Una clave API para ambos niveles. Los proxies residenciales y el Navegador de Raspado facturan contra la misma cuenta de Scrapeless, por lo que el nivel HTTP y el nivel renderizado comparten una credencial.
- Opción de sesión persistente. Mantén la misma IP residencial a través de un recorrido de múltiples pasos cuando un flujo necesita continuidad, o rota por solicitud para todo lo demás.
El tiempo de ejecución es gratuito para comenzar y se escala con el uso: consulta precios de Scrapeless para los niveles, y obtén tu clave API en el plan gratuito en app.scrapeless.com.
Requisitos previos
- Elixir 1.16+ y Erlang/OTP 26+ — verifica con
elixir --version. - Una cuenta de Scrapeless y clave API — regístrate para el plan gratuito en app.scrapeless.com, luego obtén tu clave en Configuración → Gestión de Claves API.
- Credenciales de proxy residencial — visibles en el panel bajo Proxies → Residencial en app.scrapeless.com.
- Familiaridad básica con
mix, selectores CSS y la terminal.
Instalar: configurar el proyecto mix y las dependencias
Crea un nuevo proyecto y añade las bibliotecas de raspado. mix new crea la estructura; las cuatro dependencias cubren la obtención (req, httpoison), el análisis (floki), y el marco de rastreo completo (crawly):
bash
mix new elixir_scraper --sup
cd elixir_scraper
Agrega las dependencias a mix.exs:
elixir
# mix.exs
defp deps do
[
{:req, "~> 0.5"}, # cliente HTTP moderno (Finch/Mint bajo el capó)
{:httpoison, "~> 2.2"}, # cliente HTTP basado en hackney, opción de larga data
{:floki, "~> 0.36"}, # analizador HTML con consultas de selectores CSS
{:crawly, "~> 0.17"} # marco de rastreo completo, estilo Scrapy
]
end
Luego descárgalas:
bash
mix deps.get
No necesitas los cuatro en un proyecto real: req más floki es la pareja mínima de obtención y análisis. La guía muestra cada uno para que puedas elegir el cliente que se ajuste a tu stack.
Configurar: almacenar tus credenciales de Scrapeless
Exporta tu clave API y las credenciales del proxy residencial como variables de entorno para que no se incluyan en el control de versiones. En el panel bajo Proxies → Residencial, haz clic en Generar y el panel imprime una cadena de conexión delimitada por dos puntos en la forma <GATEWAY>:<PORT>:<CHANNEL_ID>-proxy-country_US-r_10m-s_<SESSION_ID>:<PASSWORD>:
bash
export SCRAPELESS_API_KEY="tu_token_api_aqui"
export SCRAPELESS_CHANNEL_ID="tu_id_de_canal" # impreso al inicio del nombre de usuario
export SCRAPELESS_PROXY_PASS="tu_contraseña_de_canal"
export SCRAPELESS_PROXY_GATEWAY="gw-us.scrapeless.io" # ver gateways regionales a continuación
Gateways regionales: gw-us.scrapeless.io (Américas), gw-eu.scrapeless.io (Europa), gw-ap.scrapeless.io (Asia-Pacífico). Elige el gateway más cercano a tu tiempo de ejecución para mantener baja la latencia de apretón de manos; el país de salida sigue siendo controlado por el segmento de nombre de usuario country_<CC> independientemente del gateway a través del cual te conectes. El puerto es 8789 para todos.
El nombre de usuario del proxy residencial se construye a partir de cuatro parámetros:
<CHANNEL_ID>— tu identificador de canal (impreso al inicio del nombre de usuario en el panel).country_<CC>— pin de país como el código ISO de dos letras:country_US,country_GB,country_DE,country_JP, etc. (usa el código que se muestra en el selector de ubicación del panel).r_<duration>— intervalo de rotación de sesión persistente (por ejemplo,r_10mmantiene la misma IP durante 10 minutos antes de rotar).s_<SESSION_ID>— identificador de sesión persistente; reutiliza el mismos_<id>para mantener una IP a través de las solicitudes durante la ventana de rotación.
Elimina los segmentos r_ y s_ para obtener una nueva IP residencial por solicitud; manténlos cuando un recorrido paginado necesite la misma IP durante todo el proceso.
Básico: obtener con Req a través de un proxy residencial, analizar con Floki
Req se enruta a través de un proxy HTTP con la clave :connect_options, que se reenvía a Finch y Mint por debajo. La autenticación del proxy se incluye en :proxy_headers como un solo encabezado Basic-auth: Mint lo fusiona en la solicitud CONNECT. El nombre de usuario lleva el código del país, por lo que la línea del proxy selecciona la geografía de salida:
elixir
defmodule ElixirScraper.ReqClient do
@gateway System.get_env("SCRAPELESS_PROXY_GATEWAY") || "gw-us.scrapeless.io"
@port 8789
# Construye el nombre de usuario del proxy residencial con el código del país incluido.
defp proxy_username(country) do
channel = System.fetch_env!("SCRAPELESS_CHANNEL_ID")
"#{channel}-proxy-country_#{country}"
end
defp proxy_auth_header(country) do
user = proxy_username(country)
pass = System.fetch_env!("SCRAPELESS_PROXY_PASS")
"Basic " <> Base.encode64("#{user}:#{pass}")
end
@doc "Obtiene una URL a través de la salida residencial de Scrapeless en `country`."
def fetch(url, country \\ "US") do
Req.get(url,
connect_options: [
proxy: {:http, @gateway, @port, []},
proxy_headers: [{"proxy-authorization", proxy_auth_header(country)}]
],
headers: [{"user-agent", "Mozilla/5.0 (compatible; ElixirScraper/1.0)"}]
)
end
end
Llama a esto y entrega el cuerpo directamente a Floki. Floki.parse_document/1 convierte la cadena HTML en un árbol; Floki.find/2 lo consulta con selectores CSS; Floki.text/1 y Floki.attribute/2 extraen valores:
elixir
{:ok, resp} = ElixirScraper.ReqClient.fetch("https://books.toscrape.com/")
{:ok, document} = Floki.parse_document(resp.body)
titles =
document
|> Floki.find("article.product_pod h3 a")
|> Floki.attribute("title")
prices =
document
|> Floki.find("article.product_pod p.price_color")
|> Floki.text()
IO.inspect(Enum.zip(titles, prices), label: "primera página")
Tres cosas que esto fija desde el principio:
- El proxy se configura por solicitud, no globalmente. Eso permite que un cliente esté libre para obtener diferentes países pasando un argumento
countrydiferente. - El encabezado Basic-auth es la línea que soporta carga. Sin
proxy_headers, el túnel CONNECT al gateway residencial es rechazado por falta de credenciales. - Floki consulta el árbol analizado, no la cadena sin procesar. Siempre
parse_document/1primero, luegofind/2— los selectores se ejecutan contra el árbol.
Avanzado 1: la variante de HTTPoison
HTTPoison precede a Req y sigue siendo común en bases de código existentes. Se apoya en hackney, que expone proxies a través de dos opciones de solicitud: :proxy como una tupla {host, port} y :proxy_auth como una tupla {user, password}. No se necesita Base64 manual: hackney construye el encabezado:
elixir
defmodule ElixirScraper.HTTPoisonClient do
@gateway System.get_env("SCRAPELESS_PROXY_GATEWAY") || "gw-us.scrapeless.io"
@port 8789
defp proxy_username(country) do
channel = System.fetch_env!("SCRAPELESS_CHANNEL_ID")
"#{channel}-proxy-country_#{country}"
end
def fetch(url, country \\ "US") do
opts = [
proxy: {@gateway, @port},
proxy_auth: {proxy_username(country), System.fetch_env!("SCRAPELESS_PROXY_PASS")},
recv_timeout: 30_000
]
headers = [{"User-Agent", "Mozilla/5.0 (compatible; ElixirScraper/1.0)"}]
case HTTPoison.get(url, headers, opts) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} -> {:ok, body}
{:ok, %HTTPoison.Response{status_code: code}} -> {:error, {:http, code}}
{:error, reason} -> {:error, reason}
end
end
end
La parte de análisis es idéntica: HTTPoison devuelve una cadena de cuerpo, y Floki hace el resto. Elige Req para código nuevo (viene con decodificación JSON, redireccionamientos y agrupamiento de conexiones por defecto) y HTTPoison cuando estés extendiendo un proyecto ya construido sobre él.
Avanzado 2: una araña de Crawly con paginación y salida a proxy
Para cualquier cosa más allá de un puñado de URLs, Crawly reemplaza el bucle hecho a mano. Una araña declara sus URLs de inicio y un callback parse_item/1; Crawly programa solicitudes a través de trabajadores, sigue las nuevas solicitudes que devuelve el callback (así es como funciona la paginación) y envía elementos analizados a una canalización.
Configura el proxy a través del middleware RequestOptions. Este pasa su lista de palabras clave directamente al extractor HTTPoison subyacente, por lo que las mismas opciones :proxy y :proxy_auth de la variante HTTPoison se aplican a cada solicitud que hace la araña:
elixir
# config/config.exs
import Config
config :crawly,
closespider_itemcount: 200,
concurrent_requests_per_domain: 3,
middlewares: [
Crawly.Middlewares.DomainFilter,
Crawly.Middlewares.UniqueRequest,
{Crawly.Middlewares.UserAgent,
elixir
user_agents: ["Mozilla/5.0 (compatible; ElixirScraper/1.0)"]},
{Crawly.Middlewares.RequestOptions,
[
proxy: {System.get_env("SCRAPELESS_PROXY_GATEWAY", "gw-us.scrapeless.io"), 8789},
proxy_auth:
{"#{System.fetch_env!("SCRAPELESS_CHANNEL_ID")}-proxy-country_US",
System.fetch_env!("SCRAPELESS_PROXY_PASS")},
recv_timeout: 30_000
]}
],
pipelines: [
Crawly.Pipelines.Validate,
{Crawly.Pipelines.DuplicatesFilter, item_id: :title},
Crawly.Pipelines.JSONEncoder,
{Crawly.Pipelines.WriteToFile, extension: "jl", folder: "./output"}
]
La araña misma implementa tres callbacks. parse_item/1 realiza dos tareas a la vez: extrae los ítems de la página actual y construye solicitudes de seguimiento para la siguiente página — esa segunda lista es lo que impulsa la paginación:
elixir
defmodule BooksSpider do
use Crawly.Spider
@impl Crawly.Spider
def base_url, do: "https://books.toscrape.com/"
@impl Crawly.Spider
def init, do: [start_urls: ["https://books.toscrape.com/"]]
@impl Crawly.Spider
def parse_item(response) do
{:ok, document} = Floki.parse_document(response.body)
# Extraer un ítem por tarjeta de producto en esta página.
items =
document
|> Floki.find("article.product_pod")
|> Enum.map(fn card ->
%{
title: card |> Floki.find("h3 a") |> Floki.attribute("title") |> List.first(),
price: card |> Floki.find("p.price_color") |> Floki.text()
}
end)
# Construir la solicitud de la siguiente página: esta lista es cómo Crawly paginó.
next_requests =
document
|> Floki.find("li.next a")
|> Floki.attribute("href")
|> Enum.map(fn href ->
href
|> Crawly.Utils.build_absolute_url(response.request_url)
|> Crawly.Utils.request_from_url()
end)
%Crawly.ParsedItem{items: items, requests: next_requests}
end
end
Ejecuta desde iex -S mix:
elixir
Crawly.Engine.start_spider(BooksSpider)
Crawly recorre cada página siguiendo el enlace li.next a que devuelve el callback, escribe cada ítem validado y deduplicado en ./output/BooksSpider.jl, y se detiene en closespider_itemcount. Cada solicitud sale a través del proxy residencial de Scrapeless porque el middleware RequestOptions inyectó las opciones :proxy y :proxy_auth en la solicitud antes de que el recuperador se ejecutara.
Obtén tu clave API en el plan gratuito: app.scrapeless.com
Evitando bloqueos: egreso residencial, geoposicionamiento y contrapresión
Un raspador se bloquea por razones predecibles y la mayoría de ellas son abordables desde la configuración que ya tienes:
- Reputación del IP del centro de datos. El rango de IP de un servidor es la primera señal que verifica un administrador de bots. Enrutar a través de proxies residenciales de Scrapeless hace que la solicitud parezca una conexión doméstica ordinaria, que es la mayor palanca contra los bloqueos de reputación de IP.
- Geografía de egreso. Las páginas restringen contenido por región — precios, stock, muros de consentimiento. Fija el país con el segmento de nombre de usuario
country_<CC>para que el resultado coincida con la localidad que pretendes leer. - Concurrencia que parece un ataque. Limitando la paralelización a ≤3 solicitudes por host. Con
Task.async_stream, eso esmax_concurrency: 3; con Crawly, esconcurrent_requests_per_domain: 3. Más allá de eso, un grupo limitado en tránsito es indistinguible de una inundación. - Un user-agent por defecto.
ReqyHTTPoisonenvían un UA por defecto de la biblioteca que es trivial de filtrar. Establece un user-agent de navegador realista (como lo hacen los fragmentos anteriores) o rota una lista a través del middlewareUserAgentde Crawly. - Ritmo. Para bucles que no son de Crawly, distribuye las solicitudes con un pequeño
Process.sleep/1entre grupos en lugar de lanzar todo el conjunto a la vez. Crawly rige el ritmo de las solicitudes por ti a través de su programador.
Nada de esto rescata una página cuyo contenido llega por JavaScript después del primer renderizado — esa es la siguiente sección.
Objetivos con mucho JS y anti-bots: ruta a través del Navegador de Raspado de Scrapeless
Req, HTTPoison y Crawly devuelven cualquier byte que envíe el origen. Para una aplicación de React, Vue o Next.js, esos bytes son una estructura vacía más una etiqueta de script — el contenido se renderiza del lado del cliente y Floki analiza un árbol vacío. Un cliente HTTP del lado del servidor no puede ejecutar ese JavaScript; un navegador en la nube puede.
Hay dos formas de llegar al Navegador de Raspado de Scrapeless desde Elixir, y se corresponden con las dos mitades de una carga de trabajo real.
(a) Solicitudes HTTP a través de proxies residenciales de Scrapeless — la mayoría renderizada
La mayoría de las páginas en la mayoría de los sitios envían HTML renderizado por el servidor. Para esas, el nivel de proxy residencial anterior es toda la respuesta: los clientes `Req` y `HTTPoison` ya salen a través de una IP residencial, lo que elimina las puertas de reputación de IP y las restricciones geográficas sin necesidad de un navegador. Manten este nivel en la mayoría de las páginas que devuelven contenido directamente: es el camino más económico, y la concurrencia de Elixir lo hace rápido.
### (b) Llamando al navegador en la nube — la minoría renderizada en el lado del cliente
Elixir no impulsa el Protocolo de Chrome DevTools tan limpiamente como Node o Python, por lo que para la minoría renderizada en JavaScript, el movimiento idiomático es mantener a Elixir como el orquestador y llamar al Scrapeless Scraping Browser a través de un pequeño helper de renderizado. Elixir genera el helper como un proceso externo con `System.cmd/3`, el helper se conecta al endpoint WebSocket documentado del navegador en la nube, ejecuta la página y devuelve el HTML renderizado a Elixir, que lo analiza con Floki exactamente como antes.
El endpoint del navegador en la nube es una única URL WebSocket con tu clave API y parámetros de sesión como valores de cadena de consulta. Un renderer de Python mínimo (guardado como `render.py`) se conecta a él con Playwright:
```python
# render.py — invocado por Elixir a través de System.cmd/3 para la minoría renderizada en JS.
import os
import sys
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright
def scraping_browser_url(proxy_country="US", session_ttl=240):
params = urlencode({
"token": os.environ["SCRAPELESS_API_KEY"],
"sessionTTL": session_ttl,
"proxyCountry": proxy_country,
})
return f"wss://browser.scrapeless.com/api/v2/browser?{params}"
def render(url, country="US"):
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(scraping_browser_url(country))
context = browser.contexts[0] if browser.contexts else browser.new_context()
page = context.pages[0] if context.pages else context.new_page()
# Calienta la página de inicio primero, luego navega a la página objetivo.
page.goto("https://quotes.toscrape.com/", wait_until="load")
page.goto(url, wait_until="networkidle")
html = page.content()
browser.close()
return html
if __name__ == "__main__":
sys.stdout.write(render(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "US"))
La renderización se ejecuta del lado de la nube en el navegador de anti-detección de Scrapeless; la instalación local de Playwright es solo el cliente de protocolo. Calentar la página de inicio antes de la página objetivo siembra cookies y estado de navegación, lo que produce un renderizado más limpio en páginas que restringen a los visitantes por primera vez.
Desde Elixir, la llamada y el análisis permanecen en una función. System.cmd/3 bloquea el proceso que llama hasta que el helper regresa, lo cual está bien dentro de un Task, ya que el BEAM mantiene todos los demás procesos en ejecución:
elixir
defmodule ElixirScraper.CloudBrowser do
@doc """
Renderiza una `url` pesada en JS a través del Scrapeless Scraping Browser y
devuelve el HTML post-pintado para que Floki lo analice.
"""
def render(url, country \\ "US") do
case System.cmd("python", ["render.py", url, country], stderr_to_stdout: true) do
{html, 0} -> {:ok, html}
{output, code} -> {:error, {:render_failed, code, output}}
end
end
def quotes(url) do
with {:ok, html} <- render(url),
{:ok, document} <- Floki.parse_document(html) do
texts = document |> Floki.find("span.text") |> Floki.text()
authors = document |> Floki.find("small.author") |> Floki.text()
{:ok, %{quotes: texts, authors: authors}}
end
end
end
El antes/después es todo el punto. Un simple Req.get en https://quotes.toscrape.com/js/ devuelve 0 elementos de cita, porque HTTP no puede ejecutar el JavaScript de la página. La misma URL a través de ElixirScraper.CloudBrowser.render/2 devuelve el DOM completamente renderizado con todas 10 citas, porque el navegador en la nube ejecutó el JavaScript primero. Ese es el comportamiento de la plataforma, no un truco de ajuste.
Para un pipeline en niveles, primero haz una búsqueda con Req, cuenta los elementos que esperas y solo eleva las páginas que regresen vacías a CloudBrowser.render/2. Task.async_stream de Elixir ejecuta la capa HTTP a gran escala y la capa del navegador a pequeña escala, ya que las sesiones del navegador en la nube son más escasas que las solicitudes HTTP — mantén la capa del navegador con max_concurrency: 3.
Resolución de problemas
| Síntoma | Causa probable | Solución |
|---|---|---|
Floki devuelve [] para un selector que existe en el navegador |
La página rinde contenido del lado del cliente; HTTP devolvió un shell de aplicación | Eleva la URL a CloudBrowser.render/2; analiza el HTML renderizado |
| Proxy CONNECT rechazado / 407 de la puerta de enlace | Credenciales de autenticación básica faltantes o incorrectas | Confirma que proxy_headers (Req) o :proxy_auth (HTTPoison) llevan el nombre de usuario y la contraseña del canal |
Floki.parse_document devuelve {:error, ...} |
El cuerpo no es HTML (API JSON, página de redirección o vacío) | Revisa resp.status; para endpoints JSON decodifica el cuerpo en lugar de analizarlo como HTML |
Mismo contenido independientemente del country_<CC> |
La página no varía según la región, o la segmentación por país no se actualizó | Verifique que el segmento del nombre de usuario cambió; algunas páginas no están geocercadas en absoluto |
| Acceso denegado o intersticial de desafío en lugar de contenido | Salida del centro de datos o puerta de entrada de primera visita | Enrutamiento a través de salida residencial y calentar la página de inicio del sitio en la misma sesión antes de la página objetivo |
| Crawly se detiene después de una página | parse_item/1 no devolvió solicitudes de seguimiento |
Confirme que el selector de siguiente página coincide y que Crawly.Utils.request_from_url/1 envuelve cada URL absoluta |
Errores de System.cmd con :enoent |
El ejecutable de python no está en PATH |
Utilice la ruta completa del intérprete, o invoque a través de un shell que lo resuelva |
Una nota sobre la deriva de selectores: cuando un sitio objetivo reorganiza su marcado, sus llamadas a Floki.find/2 devuelven silenciosamente listas vacías en lugar de generar errores. Verifique nuevamente y ajuste los selectores contra el nuevo DOM siempre que un scraper que anteriormente funcionaba empiece a devolver en blanco: trate una lista vacía como una señal para inspeccionar, no como un resultado normal.
Conclusión: escala tu canal de scraping en Elixir
El patrón de Elixir se reduce a cuatro movimientos. Obtén datos con Req o HTTPoison (o deja que Crawly programe las búsquedas) a través de proxies residenciales de Scrapeless; analiza con los selectores CSS de Floki; pagine devolviendo solicitudes de seguimiento de parse_item/1; y eleva la minoría renderizada por JavaScript al Scrapeless Scraping Browser, al que se accede desde Elixir como una llamada de renderizado externo en lugar de pedir al BEAM que hable CDP directamente.
A partir de aquí, la misma forma se compone en sistemas más grandes. Para una discusión en profundidad sobre la capa de proxy residencial, consulte ¿Qué es un proxy SSL?. Antes de que lo envíes: establece country_<CC> para páginas geobaseadas, mantén la concurrencia en ≤3 por host, establece un agente de usuario realista, trata los selectores ausentes como anulables y mantiene la capa HTTP amplia y la capa de navegador en la nube estrecha.
¿Listo para construir tu canal de datos impulsado por IA?
Únete a nuestra comunidad para reclamar un plan gratuito y conectarte con desarrolladores que construyen canales de scraping en Elixir: Discord · Telegram.
Regístrate en app.scrapeless.com para obtener un tiempo de ejecución gratuito del Scraping Browser y adapta los patrones anteriores a las páginas y regiones que necesite tu canal. Referencia completa en docs.scrapeless.com.
Preguntas Frecuentes
P: ¿Es legal hacer scraping web con Elixir?
El lenguaje es irrelevante para la legalidad. Hacer scraping de datos públicamente disponibles es generalmente permisible en muchas jurisdicciones, pero la ley no es uniforme: revisa los Términos de Servicio de cada sitio, evita recoger datos personales o protegidos por derechos de autor a los que no tienes derecho y recuerda que las reglas varían según la jurisdicción. Cuando tengas dudas, obtén asesoramiento legal para tu caso de uso específico. Scrapeless accede únicamente a datos públicamente disponibles.
P: ¿Necesito un proxy para hacer scraping con Elixir?
Para cualquier cosa a gran escala, sí. La IP del centro de datos de un servidor es una de las primeras cosas que un gestor de bots señala, y la salida residencial reduce drásticamente esos bloqueos. También se requiere un proxy cada vez que una página limita el contenido por región. Scrapeless proporciona proxies residenciales en más de 195 países: establece el segmento de nombre de usuario country_<CC> y enrutalo a través de la puerta de enlace con Req, HTTPoison o Crawly, para que no tengas que obtener y rotar IPs tú mismo.
P: ¿Req o HTTPoison — cuál debería usar?
Para nuevo código, Req: incluye decodificación de JSON, seguimiento de redireccionamientos y agrupamiento de conexiones a través de Finch, con menos código repetitivo. Elige HTTPoison cuando estés ampliando un proyecto que ya está construido sobre él o cuando quieras las opciones de :proxy / :proxy_auth de hackney directamente. Ambos analizan de manera idéntica con Floki, así que la elección depende de la ergonomía del cliente, no del scraping.
P: ¿Cuándo necesito el Scrapeless Scraping Browser en lugar de HTTP simple?
Cuando el HTML que tu cliente devuelve es un contenedor de aplicación JavaScript sin contenido. La señal: un selector que puedes ver en un navegador real devuelve una lista vacía de Floki. Esa página se renderiza del lado del cliente, por lo que un cliente HTTP del lado del servidor nunca ve los datos. Dirige esas URLs a través del navegador en la nube, que ejecuta el JavaScript antes de que lo analices — y mantén HTTP simple en las páginas que ya devuelven contenido.
P: ¿Por qué llamar a un asistente de renderizado en lugar de controlar el navegador directamente desde Elixir?
Elixir no tiene un controlador del Protocolo de Herramientas de Desarrollo de Chrome de primera clase como lo tienen Node y Python, por lo que el patrón más limpio mantiene a Elixir como el orquestador y delega la renderización a un pequeño ayudante externo invocado con System.cmd/3. Elixir todavía posee el bucle de rastreo, los límites de concurrencia, la paginación y el análisis de Floki; solo la ejecución de JavaScript se traslada al navegador en la nube. Esto mantiene el código de Elixir idiomático y la integración en una única llamada externa.
P: ¿Cuántas solicitudes concurrentes debo ejecutar?
Mantener en ≤3 por host. Con Task.async_stream, configura max_concurrency: 3; con Crawly, establece concurrent_requests_per_domain: 3. Los catálogos públicos toleran un poco más, los orígenes protegidos contra bots quieren menos, pero 3 es un valor predeterminado seguro que evita que el grupo en vuelo parezca un ataque. Mantén la capa del navegador en la nube aún más estrecha, ya que las sesiones renderizadas son más escasas que las solicitudes HTTP.
En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.



