🎯 一款可定制、具备反检测功能的云浏览器,由自主研发的 Chromium驱动,专为网页爬虫AI 代理设计。👉立即试用
返回博客

为什么您的 Elixir 爬虫会被封锁,以及如何通过住宅代理 + 云浏览器解决这个问题

James Thompson
James Thompson

Scraping and Proxy Management Expert

03-Jun-2026

关键要点:

  • Elixir 为并发抓取而构建。 BEAM 运行时将数千个轻量级进程编织到一个节点上,因此跨越数百个 URL 的抓取就像普通的 Task.async_stream 一样,而不是你需要精心管理的线程池。
  • Req 和 HTTPoison 用于获取,Floki 用于解析。 Req 是现代的、内建功能的 HTTP 客户端;HTTPoison 是长期使用的 Hackney-backed 选项;Floki 将原始 HTML 转换成可以用 CSS 选择器query 的树。三者结合覆盖任何提供呈现标记的页面。
  • Crawly 是完整的抓取框架。 它在工作者之间调度请求,通过后续请求处理分页,应用中间件以进行用户代理轮换和请求选项,并将解析的项目推送到管道中——类似 Scrapy 的样式,但运行在 BEAM 上。
  • Scrapeless 住宅代理路由获取。 单个代理主机、端口和 Basic-auth 头文件直接插入 Reqconnect_options,HTTPoison 的 :proxy / :proxy_auth,或 Crawly 的 RequestOptions 中间件,从而为每个请求提供一个住宅 IP 并固定出口地理位置。
  • 重 JS 和反机器人目标升级到 Scrapeless 抓取浏览器。 Elixir 没有像 Node 或 Python 那样干净地驱动 Chrome DevTools Protocol,因此通过两种方式访问云浏览器:通过 Scrapeless 住宅代理的 HTTP 请求获取大多数渲染内容,以及针对客户端渲染较少部分的小型云浏览器调用。
  • 免费开始。 新的 Scrapeless 账户包含免费的抓取浏览器运行时 — 请在 app.scrapeless.com 注册。

介绍:为什么选择 Elixir,以及摩擦从何而来

Elixir 运行在 BEAM 上,这是与 Erlang 电信交换机在线保持数十年的相同虚拟机。它在抓取中的决定性特征是廉价并发:生成一万个进程是常规操作,每个进程相互独立,每个进程能够保持一个正在进行的 HTTP 请求而不阻塞其他请求。在其他语言中需要一个异步框架和仔细的池调优的抓取,在 Elixir 中只是一个具有 max_concurrency 上限的 Task.async_stream

库的故事已经成熟。ReqHTTPoison 获取页面,Floki 用 CSS 选择器解析它们,Crawly 将整个循环 — 调度、去重、分页和项目管道 — 包装成一个仍然感觉像是习惯用法的 Elixir 的 Scrapy 风格框架。对于静态目录、网站地图和服务器生成的页面,这个堆栈本身是完整的。

有两件事打破了这个堆栈。首先,IP 声誉:当目标运行甚至是基本的机器人管理器时,一个干净的数据中心地址会被标记,任何数量的头部调整都无法修复被阻塞的出口 IP。其次,客户端渲染:单页应用程序返回 HTTP 200,但 <div id="app"> 为空,Floki 解析到的正是到达的内容 — 什么都没有。该页面在浏览器中看起来是完整的,但在抓取器中却是空的。

本指南在各个层级上构建 Elixir 堆栈。HTTP 层使用 Req, HTTPoison, 和 Crawly,通过 195+ 个国家的 Scrapeless 住宅代理进行路由。渲染 JS 层升级到 Scrapeless 抓取浏览器,可以从 Elixir 访问,而无需要求 BEAM 直接与 CDP 通信。有关这些抓取路由通过的住宅代理层的信息,请参见 什么是 SSL 代理?


你可以构建的内容

这两层模式 — Elixir 库在前,Scrapeless 在后 — 涵盖了大多数击败普通 HTTP 抓取器的工作:

  • 并发目录抓取。 网站地图、文章存档、产品列表 — Task.async_stream 在 URL 集合上展开,工作者数量有限,并用 Floki 解析每个页面。
  • 使用 Crawly 的定时监控。 定义一个蜘蛛一次,让它按照计划浏览列表,并将解析的项目推送到验证和存储管道中。
  • 特定地区的快照。 固定 Scrapeless 代理的国家,以便定价、可用性和同意墙返回本地用户看到的内容,而不是服务器 IP 解析的内容。
  • 在机器人管理器后进行有韧性的提取。 通过住宅出口路由获取,使普通家用 IP 而不是数据中心范围发起请求。
  • RAG 和 LLM 的摄取。 渲染发布者和文档页面为干净文本,然后将提取的内容输入嵌入管道。
  • SPA 和无限滚动页面。 将客户端渲染的较少部分升级到 Scrapeless 抓取浏览器,该浏览器在解析结果之前在云端运行 JavaScript。

为什么将 Elixir 与 Scrapeless 配对

Elixir 提供了并发、解析和爬取框架;Scrapeless Scraping Browser 提供了服务器端 HTTP 客户端无法提供的出站和渲染管道。这两者可以无缝结合,因为双方的交接是一个标准的 HTTP 代理,一边是标准代理,另一边是文档化的云浏览器端点。

  • 195+个国家的住宅代理。 以单个代理主机、端口和基本身份验证凭据的形式暴露 — 直接投入 ReqHTTPoison 或 Crawly 的 RequestOptions 中间件中。
  • 按请求地理定位。 代理用户名中的国家代码控制出站地理,且无需额外的握手,因此只需切换一段代码即可获取美国、英国、德国或日本的视图。
  • 反检测云浏览器。 对于客户端渲染的页面,Scrapeless Scraping Browser 运行一个自开发的 Chromium,具有完整的云端 JavaScript 渲染和每会话指纹随机化,因此 SPA 和懒加载面板在提取之前会被充分加载。
  • 一个 API 密钥用于两个层级。 住宅代理和 Scraping Browser 在同一个 Scrapeless 账户中进行计费,因此 HTTP 层和渲染层共享一个凭据。
  • 粘性会话选项。 在需要连续性的多步骤遍历中保持相同的住宅 IP,或者对于其他所有请求进行轮换。

运行时免费开始,并根据使用量扩展 — 请查看 Scrapeless 定价 以了解各层级,并在免费计划中获取您的 API 密钥,访问 app.scrapeless.com


前提条件

  • Elixir 1.16+ 和 Erlang/OTP 26+ — 使用 elixir --version 检查。
  • Scrapeless 账户和 API 密钥 — 在 app.scrapeless.com 注册免费计划,然后从 设置 → API 密钥管理 中获取您的密钥。
  • 住宅代理凭据 — 在 app.scrapeless.com 的仪表板中的 代理 → 住宅 下可见。
  • mix、CSS 选择器和终端有基本的了解。

安装:设置 mix 项目和依赖项

创建一个新项目并添加爬取库。mix new 用于构建结构;四个依赖项覆盖获取(reqhttpoison)、解析(floki)和完整的爬取框架(crawly):

bash Copy
mix new elixir_scraper --sup
cd elixir_scraper

将依赖项添加到 mix.exs 中:

elixir Copy
# mix.exs
defp deps do
  [
    {:req, "~> 0.5"},        # 现代 HTTP 客户端(底层使用 Finch/Mint)
    {:httpoison, "~> 2.2"},  # hackney 后端 HTTP 客户端,长期以来的选项
    {:floki, "~> 0.36"},     # 具有 CSS 选择器查询的 HTML 解析器
    {:crawly, "~> 0.17"}     # 完整爬取框架,类似 Scrapy 风格
  ]
end

然后拉取它们:

bash Copy
mix deps.get

在实际项目中不需要全部四个 — req 加上 floki 是最小的获取和解析组合。本指南展示了每一个,以便您可以选择适合您的技术栈的客户端。


配置:存储您的 Scrapeless 凭据

将您的 API 密钥和住宅代理凭据导出为环境变量,以便它们不被纳入源代码管理。在仪表板的 代理 → 住宅 下,单击 生成,面板将打印出形式为 <GATEWAY>:<PORT>:<CHANNEL_ID>-proxy-country_US-r_10m-s_<SESSION_ID>:<PASSWORD> 的冒号分隔连接字符串:

bash Copy
export SCRAPELESS_API_KEY="your_api_token_here"
export SCRAPELESS_CHANNEL_ID="your_channel_id"          # 在用户名开头打印
export SCRAPELESS_PROXY_PASS="your_channel_password"
export SCRAPELESS_PROXY_GATEWAY="gw-us.scrapeless.io"   # 参见下面的区域网关

区域网关: gw-us.scrapeless.io(美洲)、gw-eu.scrapeless.io(欧洲)、gw-ap.scrapeless.io(亚太)。选择离您的运行时最近的网关以降低握手延迟;出站国家仍由 country_<CC> 用户名段控制,无论您通过哪个网关进行连接。端口对所有人都为 8789

住宅代理用户名由四个参数构成:

  • <CHANNEL_ID> — 您的频道标识符(在仪表板的用户名开头打印)。
  • country_<CC> — 作为两位字母 ISO 代码的国家定位:country_UScountry_GBcountry_DEcountry_JP 等(使用仪表板位置选择器中显示的代码)。
  • r_<duration> — 粘性会话轮换间隔(例如 r_10m 持有相同的 IP 10 分钟后轮换)。
  • s_<SESSION_ID> — 粘性会话标识符;在旋转窗口内重用相同的 s_<id> 以保持同一 IP 跨请求。

对于每个请求获取新的住宅 IP,删除 r_s_ 部分;当分页遍历需要在整个过程中使用相同 IP 时,保留它们。


基础:通过住宅代理使用 Req 获取并解析

Req 通过 :connect_options 键路由到 HTTP 代理,然后将其转发给下面的 Finch 和 Mint。代理身份验证放在 :proxy_headers 中,作为单个 Basic-auth 头—Mint 将其合并到 CONNECT 请求中。用户名携带国家代码,因此代理行本身选择出口地理位置:

elixir Copy
defmodule ElixirScraper.ReqClient 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

  defp proxy_auth_header(country) do
    user = proxy_username(country)
    pass = System.fetch_env!("SCRAPELESS_PROXY_PASS")
    "Basic " <> Base.encode64("#{user}:#{pass}")
  end

  @doc "通过 Scrapeless 住宅出口获取 `country` 中的 URL。"
  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

调用它并将主体直接传递给 Floki。 Floki.parse_document/1 将 HTML 字符串转换为树;Floki.find/2 使用 CSS 选择器查询它;Floki.text/1Floki.attribute/2 提取值:

elixir Copy
{: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: "第一页")

这三点早期锁定:

  • 代理按请求配置,而不是全局配置。 这使得一个客户端可以通过传递不同的 country 参数来提取不同的国家。
  • Basic-auth 头是承重线。 没有 proxy_headers,连接到住宅网关的 CONNECT 隧道因缺少凭据而被拒绝。
  • Floki 查询解析后的树,而不是原始字符串。 始终先 parse_document/1,然后 find/2 — 选择器对树进行操作。

高级 1:HTTPoison 变体

HTTPoisonReq 之前存在,并且在现有代码库中仍然常见。它由 hackney 支持,hackney 通过两个请求选项公开代理::proxy 作为 {host, port} 元组和 :proxy_auth 作为 {user, password} 元组。无须手动编码 Base64 — hackney 自动构建头:

elixir Copy
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

解析部分是相同的——HTTPoison 返回一个主体字符串,Floki 负责其余部分。选择 Req 用于新代码(它自带 JSON 解码、重定向和连接池功能),而在扩展已有使用 HTTPoison 的项目时使用后者。


高级 2:带有分页和代理出口的 Crawly 爬虫

对于超过少数 URL 的情况,Crawly 替代手动循环。一个爬虫声明其起始 URL 和一个 parse_item/1 回调;Crawly 在工作者之间调度请求,跟进回调返回的新请求(这就是分页的工作原理),并将解析的项目推送到管道中。

通过 RequestOptions 中间件连接代理。它将其关键字列表直接传递给底层的 HTTPoison 获取器,因此来自 HTTPoison 变体的相同 :proxy:proxy_auth 选项适用于爬虫发出的每个请求:

elixir Copy
# 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,

用户代理: ["Mozilla/5.0 (compatible; ElixirScraper/1.0)"]},
{Crawly.Middlewares.RequestOptions,
[
代理: {System.get_env("SCRAPELESS_PROXY_GATEWAY", "gw-us.scrapeless.io"), 8789},
代理认证:
{"#{System.fetch_env!("SCRAPELESS_CHANNEL_ID")}-proxy-country_US",
System.fetch_env!("SCRAPELESS_PROXY_PASS")},
接收超时: 30_000
]}
],
管道: [
Crawly.Pipelines.Validate,
{Crawly.Pipelines.DuplicatesFilter, item_id: :title},
Crawly.Pipelines.JSONEncoder,
{Crawly.Pipelines.WriteToFile, extension: "jl", folder: "./output"}
]

Copy
蜘蛛本身实现了三个回调。`parse_item/1` 同时完成两个任务:提取当前页面上的项目,并为下一页构建后续请求——第二个列表便是驱动分页的内容:

```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)

    # 提取此页面每个产品卡片的一个项目。
    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)

    # 构建下一页的请求:此列表便是 Crawly 分页的方式。
    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

iex -S mix 运行:

elixir Copy
Crawly.Engine.start_spider(BooksSpider)

Crawly 通过回调返回的 li.next a 链接遍历每一页,将每个经过验证、去重的项目写入 ./output/BooksSpider.jl,并在达到 closespider_itemcount 时停止。每个请求都通过 Scrapeless 的住宅代理续出,因为 RequestOptions 中间件在提取器运行前将 :proxy:proxy_auth 选项注入了请求中。

在免费计划中获取您的 API 密钥:app.scrapeless.com


避免阻塞:住宅出口、地理定位和反压力

爬虫被阻塞的原因是可以预测的,大多数问题可以通过您已有的配置来解决:

  • 数据中心 IP 的声誉。 服务器的 IP 范围是机器人管理者检查的第一个信号。通过 Scrapeless 住宅代理路由请求使其看起来像普通家庭连接,这是对抗 IP 声誉阻塞的最大杠杆。
  • 出口地理。 页面根据地区限制内容——定价、库存、同意墙。通过 country_<CC> 用户名段锁定国家,以便结果与您打算读取的区域匹配。
  • 看起来像攻击的并发性。 每个主机的并行请求限制为 ≤3 个请求。使用 Task.async_stream 时,要设置 max_concurrency: 3;使用 Crawly 时,设置 concurrent_requests_per_domain: 3。超过这个数,紧凑的飞行池与洪水无法区分。
  • 默认用户代理。 ReqHTTPoison 发送一个库默认的 UA,容易被过滤。设置一个现实的浏览器用户代理(如上面的代码片段所展示)或通过 Crawly 的 UserAgent 中间件旋转列表。
  • 节奏控制。 对于非 Crawly 循环,在批次之间用小的 Process.sleep/1 间隔请求,而不是一次发送整个集合。Crawly 通过其调度器为您控制请求的节奏。

这些都无法拯救那些内容在第一次渲染后通过 JavaScript 到达的页面——这就是下一部分的内容。


JS 密集型和反爬虫目标:通过 Scrapeless 爬虫浏览器路由

ReqHTTPoison 和 Crawly 都返回源发送的字节。对于 React、Vue 或 Next.js 应用,这些字节是一个空壳加一个脚本标签——内容在客户端渲染,而 Floki 解析出一个空树。服务器端的 HTTP 客户端无法运行 JavaScript;而云浏览器可以。

从 Elixir 到达 Scrapeless 爬虫浏览器有两种方法,它们对应于实际工作负载的两个部分。

(a) 通过 Scrapeless 住宅代理进行 HTTP 请求——渲染的主体

大多数网站上的大多数页面都传送服务器渲染的 HTML。对于这些页面,上述的住宅代理层就是完整的解决方案:ReqHTTPoison 客户端已经通过住宅 IP 进行出站,这样在没有任何浏览器的情况下就能清除 IP 声誉检查和地理限制。对于大多数直接返回内容的页面,保持此层是最经济的选择,而 Elixir 的并发性使其运行快速。

(b) 调用云浏览器 — 客户端渲染的少数

Elixir 没有像 Node 或 Python 那样干净地驱动 Chrome DevTools 协议,因此对于 JavaScript 渲染的少数情况,惯用的做法是将 Elixir 保持为协调者,通过一个小的渲染助手调用 Scrapeless 抓取浏览器。Elixir 使用 System.cmd/3 作为外部进程生成助手,助手连接到云浏览器的文档化 WebSocket 端点,运行页面,并将渲染的 HTML 打印回 Elixir — Elixir 再用 Floki 解析它,正如之前一样。

云浏览器端点是一个单一的 WebSocket URL,您的 API 密钥和会话参数作为查询字符串值。一个最小的 Python 渲染器(保存为 render.py)使用 Playwright 连接到它:

python Copy
# render.py — 由 Elixir 通过 System.cmd/3 调用的 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()
        # 首先预热首页,然后导航到目标页面。
        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"))

渲染在 Scrapeless 的反检测浏览器中运行;本地的 Playwright 安装只是协议客户端。在目标页面之前预热首页可以播种cookie和导航状态,这样在需要防止首次访问者的页面上产生更干净的渲染。

在 Elixir 中,调用和解析保持在一个函数中。System.cmd/3 阻塞调用进程,直到助手返回 — 在 Task 中是可以的,因为 BEAM 保持每个其他进程运行:

elixir Copy
defmodule ElixirScraper.CloudBrowser do
  @doc """
  通过 Scrapeless 抓取浏览器渲染一个 JS 重的 `url`,
  返回供 Floki 解析的后绘制 HTML。
  """
  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

前后对比是关键。一条简单的 Req.get 请求 https://quotes.toscrape.com/js/ 返回 0 个引用元素,因为 HTTP 无法执行页面的 JavaScript。而通过 ElixirScraper.CloudBrowser.render/2 访问相同的 URL 返回完全渲染的 DOM,包含所有 10 个引用,因为云浏览器首先运行了 JavaScript。这是平台行为,而不是调优技巧。

对于分层管道,先使用 Req 获取,计算预期的元素数量,只对返回为空的页面升级到 CloudBrowser.render/2。Elixir 的 Task.async_stream 在广泛的 HTTP 层和狭窄的浏览器层运行,因为云浏览器会话比 HTTP 请求稀缺 — 将浏览器层保持在 max_concurrency: 3


故障排除

症状 可能原因 修复
Floki 对于浏览器中存在的选择器返回 [] 页面在客户端渲染内容;HTTP 返回了应用壳 将 URL 升级到 CloudBrowser.render/2;解析渲染的 HTML
代理连接被拒绝 / 从网关返回 407 缺少或错误的基本身份验证凭据 确认 proxy_headers(Req)或 :proxy_auth(HTTPoison)携带频道用户名和密码
Floki.parse_document 返回 {:error, ...} 主体不是 HTML(JSON API、重定向页面或空) 检查 resp.status;对于 JSON 端点,解码主体而不是将其解析为 HTML
内容与country_<CC>无关 页面在地区上没有差异,或国家部分未更新 验证用户名部分是否已更改;某些页面根本没有区域限制
访问被拒或出现挑战插页而不是内容 数据中心出口或首次访问限制 通过住宅出口路由,并在同一会话中预热网站的主页,然后访问目标页面
Crawly在一页后停止 parse_item/1未返回后续请求 确认下一页选择器匹配,并且Crawly.Utils.request_from_url/1包装每个绝对URL
System.cmd错误enoent python可执行文件不在PATH中 使用完整的解释器路径,或通过能够解析的shell调用

关于选择器漂移的注意事项:当目标网站重新排列其标记时,您的Floki.find/2调用将默默返回空列表而不是引发错误。每当之前有效的抓取器开始返回空白时,重新检查并紧缩选择器,以适应新的DOM—将空列表视为检查信号,而非正常结果。


结论:扩展您的Elixir抓取管道

Elixir模式归结为四个步骤。通过Scrapeless住宅代理用ReqHTTPoison(或让Crawly调度抓取)获取;使用Floki的CSS选择器进行解析;通过从parse_item/1返回后续请求进行分页;并将JavaScript渲染的次要内容上升到Scrapeless抓取浏览器,通过外部渲染调用从Elixir访问,而不是直接要求BEAM与CDP交互。

从这里开始,相同的形状可以组合成更大的系统。有关住宅代理层的深入信息,请参见什么是SSL代理?。在发布之前:固定country_<CC>用于地理限制页面,保持主机并发 ≤3,设置现实的用户代理,将缺失选择器视为可空,并保持HTTP层宽泛而云浏览器层狭窄。


准备构建您的AI驱动数据管道了吗?

加入我们的社区,获取免费计划并与正在构建Elixir抓取管道的开发者联系:Discord · Telegram

app.scrapeless.com注册,获取免费的抓取浏览器运行时间,并将上述模式适应于您的管道所需的页面和地区。完整参考见docs.scrapeless.com


常见问题解答

问:使用Elixir进行网页抓取合法吗?

语言与合法性无关。抓取公开可用的数据在许多辖区通常是允许的,但法律并不统一:请查看每个网站的服务条款,避免收集您无权收集的个人或版权数据,并记住规则因辖区而异。如有疑问,请为您的特定用例获取法律建议。Scrapeless仅访问公开可用的数据。

问:我需要为Elixir抓取使用代理吗?

对于任何大规模操作,是的。服务器的数据中心IP是机器人管理者最先标记的对象,而住宅出口大大减少了这些封锁。每当页面按地区限制内容时,也需要使用代理。Scrapeless在195多个国家提供住宅代理—设置country_<CC>用户名部分,并通过网关路由ReqHTTPoison或Crawly,这样您就不必自己获取和轮换IP。

问:我应该使用Req还是HTTPoison?

对于新代码,使用Req:它提供JSON解码、重定向跟随和通过Finch的连接池,且样板代码更少。当您扩展一个已经建立在HTTPoison上的项目时,或者希望直接使用hackney的:proxy / :proxy_auth元组选项时,请选择HTTPoison。两者在Floki中解析是相同的,所以选择基于客户端的可用性,而不是抓取。

问:我什么时候需要使用Scrapeless抓取浏览器而不是普通的HTTP?

当客户端返回的HTML是一个没有内容的JavaScript应用外壳时。迹象:您在真实浏览器中看到的选择器从Floki返回空列表。该页面在客户端渲染,因此服务器端的HTTP客户端永远看不到数据。将这些URL通过云浏览器路由,它在您解析之前会运行JavaScript—并在已经返回内容的页面上保持使用普通HTTP。

问:为什么要调用渲染辅助程序而不是直接从Elixir驱动浏览器?

Elixir没有像Node和Python那样的第一类Chrome DevTools协议驱动,因此最干净的模式是将Elixir作为协调者,并通过System.cmd/3调用一个小的外部助手来委派渲染。Elixir仍然负责爬取循环、并发限制、分页和Floki解析;只有JavaScript执行迁移到云浏览器。这保持了Elixir代码的惯用法,并将集成限制为一次外部调用。

问:我应该运行多少个并发请求?

每个主机保持在**≤3**。使用Task.async_stream时,设置max_concurrency: 3;使用Crawly时,设置concurrent_requests_per_domain: 3。公共目录能容忍稍多一点,防机器人保护的来源则希望少一点,但3是一个安全的默认值,可以防止正在进行的请求池看起来像攻击。保持云浏览器的层级更窄,因为已渲染的会话比HTTP请求更稀缺。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录