为什么您的 Elixir 爬虫会被封锁,以及如何通过住宅代理 + 云浏览器解决这个问题
Scraping and Proxy Management Expert
关键要点:
- Elixir 为并发抓取而构建。 BEAM 运行时将数千个轻量级进程编织到一个节点上,因此跨越数百个 URL 的抓取就像普通的
Task.async_stream一样,而不是你需要精心管理的线程池。 - Req 和 HTTPoison 用于获取,Floki 用于解析。
Req是现代的、内建功能的 HTTP 客户端;HTTPoison是长期使用的 Hackney-backed 选项;Floki将原始 HTML 转换成可以用 CSS 选择器query 的树。三者结合覆盖任何提供呈现标记的页面。 - Crawly 是完整的抓取框架。 它在工作者之间调度请求,通过后续请求处理分页,应用中间件以进行用户代理轮换和请求选项,并将解析的项目推送到管道中——类似 Scrapy 的样式,但运行在 BEAM 上。
- Scrapeless 住宅代理路由获取。 单个代理主机、端口和 Basic-auth 头文件直接插入
Req的connect_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。
库的故事已经成熟。Req 和 HTTPoison 获取页面,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+个国家的住宅代理。 以单个代理主机、端口和基本身份验证凭据的形式暴露 — 直接投入
Req、HTTPoison或 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 用于构建结构;四个依赖项覆盖获取(req、httpoison)、解析(floki)和完整的爬取框架(crawly):
bash
mix new elixir_scraper --sup
cd elixir_scraper
将依赖项添加到 mix.exs 中:
elixir
# 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
mix deps.get
在实际项目中不需要全部四个 — req 加上 floki 是最小的获取和解析组合。本指南展示了每一个,以便您可以选择适合您的技术栈的客户端。
配置:存储您的 Scrapeless 凭据
将您的 API 密钥和住宅代理凭据导出为环境变量,以便它们不被纳入源代码管理。在仪表板的 代理 → 住宅 下,单击 生成,面板将打印出形式为 <GATEWAY>:<PORT>:<CHANNEL_ID>-proxy-country_US-r_10m-s_<SESSION_ID>:<PASSWORD> 的冒号分隔连接字符串:
bash
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_US、country_GB、country_DE、country_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
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/1 和 Floki.attribute/2 提取值:
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: "第一页")
这三点早期锁定:
- 代理按请求配置,而不是全局配置。 这使得一个客户端可以通过传递不同的
country参数来提取不同的国家。 - Basic-auth 头是承重线。 没有
proxy_headers,连接到住宅网关的 CONNECT 隧道因缺少凭据而被拒绝。 - Floki 查询解析后的树,而不是原始字符串。 始终先
parse_document/1,然后find/2— 选择器对树进行操作。
高级 1:HTTPoison 变体
HTTPoison 在 Req 之前存在,并且在现有代码库中仍然常见。它由 hackney 支持,hackney 通过两个请求选项公开代理::proxy 作为 {host, port} 元组和 :proxy_auth 作为 {user, password} 元组。无须手动编码 Base64 — hackney 自动构建头:
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
解析部分是相同的——HTTPoison 返回一个主体字符串,Floki 负责其余部分。选择 Req 用于新代码(它自带 JSON 解码、重定向和连接池功能),而在扩展已有使用 HTTPoison 的项目时使用后者。
高级 2:带有分页和代理出口的 Crawly 爬虫
对于超过少数 URL 的情况,Crawly 替代手动循环。一个爬虫声明其起始 URL 和一个 parse_item/1 回调;Crawly 在工作者之间调度请求,跟进回调返回的新请求(这就是分页的工作原理),并将解析的项目推送到管道中。
通过 RequestOptions 中间件连接代理。它将其关键字列表直接传递给底层的 HTTPoison 获取器,因此来自 HTTPoison 变体的相同 :proxy 和 :proxy_auth 选项适用于爬虫发出的每个请求:
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,
用户代理: ["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"}
]
蜘蛛本身实现了三个回调。`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
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。超过这个数,紧凑的飞行池与洪水无法区分。 - 默认用户代理。
Req和HTTPoison发送一个库默认的 UA,容易被过滤。设置一个现实的浏览器用户代理(如上面的代码片段所展示)或通过 Crawly 的UserAgent中间件旋转列表。 - 节奏控制。 对于非 Crawly 循环,在批次之间用小的
Process.sleep/1间隔请求,而不是一次发送整个集合。Crawly 通过其调度器为您控制请求的节奏。
这些都无法拯救那些内容在第一次渲染后通过 JavaScript 到达的页面——这就是下一部分的内容。
JS 密集型和反爬虫目标:通过 Scrapeless 爬虫浏览器路由
Req、HTTPoison 和 Crawly 都返回源发送的字节。对于 React、Vue 或 Next.js 应用,这些字节是一个空壳加一个脚本标签——内容在客户端渲染,而 Floki 解析出一个空树。服务器端的 HTTP 客户端无法运行 JavaScript;而云浏览器可以。
从 Elixir 到达 Scrapeless 爬虫浏览器有两种方法,它们对应于实际工作负载的两个部分。
(a) 通过 Scrapeless 住宅代理进行 HTTP 请求——渲染的主体
大多数网站上的大多数页面都传送服务器渲染的 HTML。对于这些页面,上述的住宅代理层就是完整的解决方案:Req 和 HTTPoison 客户端已经通过住宅 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
# 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
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住宅代理用Req或HTTPoison(或让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>用户名部分,并通过网关路由Req、HTTPoison或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,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



