なぜあなたのElixirスクレイパーがブロックされるのか、そして家庭用プロキシとクラウドブラウザがそれをどう修正するのか
Scraping and Proxy Management Expert
主要なポイント:
- Elixirは同時実行スクレイピングのために作られています。 BEAMランタイムは、数千の軽量プロセスを1つのノード上に組み合わせます。そのため、何百ものURLにわたるクローリングは、世話をする必要のあるスレッドプールではなく、単純な
Task.async_streamとして実行されます。 - ReqとHTTPoisonが取得し、Flokiが解析します。
Reqは最新のバッテリー内蔵HTTPクライアントであり、HTTPoisonは長年のハックニーバックの選択肢で、Flokiは生のHTMLをCSSセレクタでクエリするツリーに変換します。これらは一緒に、レンダリングされたマークアップを送信するページをカバーします。 - Crawlyは完全なクローリングフレームワークです。 ワーカー間でリクエストをスケジュールし、フォローアップリクエストを通じてページネーションを処理し、ユーザーエージェントのローテーションとリクエストオプションのためのミドルウェアを適用し、解析されたアイテムをパイプラインにプッシュします — Scrapyスタイルですが、BEAM上で実行されます。
- Scrapelessの住宅プロキシが取得をルーティングします。 単一のプロキシホスト、ポート、およびBasic-authヘッダーが直接
Reqのconnect_options、HTTPoisonの:proxy/:proxy_auth、またはCrawlyのRequestOptionsミドルウェアにプラグインされ、すべてのリクエストに住宅IPを提供し、出口ジオグラフィを固定します。 - JS重視およびボット対策対象はScrapeless Scraping Browserにエスカレートします。 ElixirはChrome DevToolsプロトコルをNodeやPythonほどクリーンにドライブしないため、クラウドブラウザには2つの方法で到達します:レンダリングされた大部分用のScrapeless住宅プロキシを通じたHTTPリクエストと、クライアントサイドでレンダリングされた少数用の小さなクラウドブラウザの呼び出しです。
- 開始は無料です。 新しいScrapelessアカウントには無料のScraping Browserランタイムが含まれています — app.scrapeless.comでサインアップしてください。
はじめに: なぜElixirか、そして摩擦が始まる場所
ElixirはBEAM上で動作し、Erlangのテレコムスイッチを数十年にわたってオンラインに保ってきた同じ仮想マシンです。スクレイピングにおけるその決定的な特性は、安価な同時実行性です:1万のプロセスを生成するのは日常茶飯事で、各プロセスが独立し、他のものをブロックすることなく、単一の進行中のHTTPリクエストを保持できます。他の言語で非同期フレームワークと注意深いプールの調整が必要なクローラーが、ElixirではTask.async_streamとmax_concurrencyの制限で済みます。
ライブラリの物語は成熟しています。ReqとHTTPoisonはページを取得し、FlokiはそれらをCSSセレクタで解析し、Crawlyはスケジューリング、重複排除、ページネーション、アイテムパイプラインを一つのループにまとめ、Scrapyスタイルのフレームワークを形成しつつ、なおElixirらしさを感じさせます。静的なカタログ、サイトマップ、サーバーレンダリングされたページ用には、このスタックは単独で完結しています。
これを破る2つの要素があります。まず、IPの評判:クリーンなデータセンターアドレスは、ターゲットが基本的なボットマネージャーを実行した瞬間にフラグが立てられ、ヘッダーの調整ではブロックされた出口IPを修正できません。次に、クライアントサイドレンダリング:シングルページアプリがHTTP 200を空の<div id="app">で返し、Flokiは到着したものを正確に解析します — 何もありません。ブラウザではページが満たされているように見え、スクレイパーには空になっています。
このガイドでは、Elixirスタックを層に分けて構築します。HTTP層はReq、HTTPoison、および195以上の国のScrapeless住宅プロキシを通じてルーティングされたCrawlyを使用します。レンダリングされたJS層は、Scrapeless Scraping Browserにエスカレートし、BEAMにCDPを直接話させずにElixirからアクセスします。住宅プロキシ層についてのこれらの取得のルーティングは、SSLプロキシとは?を参照してください。
あなたが構築できるもの
2層のパターン — Elixirライブラリが前面にあり、Scrapelessがエスカレーションの背後にある — は、通常のHTTPスクレイパーで打破できるほとんどのジョブをカバーします:
- 同時実行カタログクローリング。 サイトマップ、記事アーカイブ、商品一覧 —
Task.async_streamはURLセット全体にわたって広がり、制限されたワーカー数で各ページをFlokiで解析します。 - Crawlyを使用したスケジュール監視。 スパイダーを1度定義し、スケジュールに従って一覧をページングし、解析されたアイテムを検証および保存のパイプラインにプッシュします。
- ジオ特定のスナップショット。 Scrapelessのプロキシ国を固定し、価格、在庫、同意の壁がローカルユーザーが見るものを返します。
- ボットマネージャーの背後にあるレジリエントな抽出。 取得を住宅出口を通じてルーティングし、普通の家庭用IPがリクエストを行うようにします。
- RAGおよびLLMの取り込み。 出版社やドキュメントページをクリーンなテキストにレンダリングし、抽出されたコンテンツをエンベディングパイプラインに流します。
- SPAおよび無限スクロールページ。 クライアントサイドでレンダリングされる少数をScrapeless Scraping Browserにエスカレートさせ、このブラウザがJavaScriptをクラウド側で実行し、結果を解析する前に処理します。
なぜElixirをScrapelessと組み合わせるのか
Elixirは、同時実行性、パース、およびクロールフレームワークを提供します。一方、Scrapeless Scraping Browserは、サーバーサイドHTTPクライアントが持つことのできない出口とレンダリングの配管を供給します。両者は、一方が標準のHTTPプロキシで、他方が文書化されたクラウドブラウザのエンドポイントであるため、スロットインするように組み合わさります。
- 195以上の国における居住用プロキシ。 単一のプロキシホスト、ポート、およびBasic認証資格情報として公開され、
Req、HTTPoison、またはCrawlyのRequestOptionsミドルウェアに直接組み込むことができます。 - リクエストごとの地理的ピンニング。 プロキシユーザー名に含まれる国コードが出口の地理を制御し、追加のハンドシェイクは不要です。同じコードで、1つのセグメントを入れ替えることで、米国、英国、ドイツ、日本のビューを引き出すことができます。
- 検出回避のクラウドブラウザ。 クライアントサイドでレンダリングされるページについて、Scrapeless Scraping Browserは、完全なクラウドサイドJavaScriptレンダリングとセッションごとのフィンガープリンティングのランダム化を備えた独自のChromiumを実行し、SPAや遅延読み込みパネルが抽出前にハイドレートします。
- 両方のティアに対する1つのAPIキー。 居住用プロキシとScraping Browserは同じScrapelessアカウントに対して請求されるため、HTTPティアとレンダリングティアは1つの資格情報を共有します。
- ステイキーセッションオプション。 フローが継続性を必要とする場合、マルチステップのトラバース中に同じ居住用IPを保持するか、その他のすべての場合に対してリクエストごとに回転させます。
実行時は無料で開始でき、使用量に応じてスケールします — Scrapelessの価格を確認してティアを確認し、app.scrapeless.comで無料プランのAPIキーを取得してください。
前提条件
- Elixir 1.16以上およびErlang/OTP 26以上 —
elixir --versionで確認してください。 - ScrapelessアカウントとAPIキー — app.scrapeless.comで無料プランにサインアップした後、設定 → APIキー管理からキーを取得します。
- 居住用プロキシ資格情報 — app.scrapeless.comのダッシュボードのプロキシ → 居住用セクションで表示されます。
mix、CSSセレクター、およびターミナルの基本的な知識。
インストール:mixプロジェクトと依存関係の設定
新しいプロジェクトを作成し、スクレイピングライブラリを追加します。 mix newは構造を雛形として生成し、4つの依存関係がフェッチ(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-backed HTTPクライアント、長年のオプション
{:floki, "~> 0.36"}, # CSSセレクタークエリを持つHTMLパーサー
{:crawly, "~> 0.17"} # 完全なクロールフレームワーク、Scrapyスタイル
]
end
その後、依存関係をダウンロードします:
bash
mix deps.get
実際のプロジェクトでは、すべての4つが必要なわけではありません — 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です。
居住用プロキシのユーザー名は、以下の4つのパラメータで構成されています:
<CHANNEL_ID>— あなたのチャネル識別子(ダッシュボードのユーザー名の先頭に表示される)。country_<CC>— 二文字のISOコードとしての国ピン:country_US、country_GB、country_DE、country_JPなど(ダッシュボードの場所セレクターに表示されるコードを使用)。r_<duration>— ステイキーセッションの回転間隔(例:r_10mは10分間同じIPを保持します)。s_<SESSION_ID>— スティッキーセッション識別子; リクエストの回転ウィンドウ内で同じs_<id>を再利用して、一つのIPを保持します。
リクエストごとに新しい住宅用IPが必要な場合は r_ と s_ の部分を削除します; ペジネーションの移動が同じIPを通す必要がある場合はそれらを保持します。
基本: 住宅プロキシを使用してReqで取得し、Flokiでパース
Req は :connect_options キーでHTTPプロキシを通してルーティングし、その情報を下のFinchとMintに転送します。プロキシ認証は、単一のBasic-authヘッダーとして :proxy_headers に入ります — 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 "国 `country` のScrapeless住宅出口を通じて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引数を渡すことができ、1つのクライアントが自由に動作します。 - Basic-authヘッダーは荷重を支えるラインです。
proxy_headersがなければ、住宅ゲートウェイへのCONNECTトンネルは資格情報の不足で拒否されます。 - Flokiはパースされたツリーをクエリし、未加工の文字列をクエリしません。 まず
parse_document/1を実行し、その後find/2を実行します — セレクタはツリーに対して実行されます。
高度な1: HTTPoisonバリアント
HTTPoison は Req よりも前に存在し、既存のコードベースで一般的です。これはhackneyに支えられ、2つのリクエストオプションを通じてプロキシを公開します: :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")},
recv_timeout: 30_000
]}
],
パイプライン: [
Crawly.Pipelines.Validate,
{Crawly.Pipelines.DuplicatesFilter, item_id: :title},
Crawly.Pipelines.JSONEncoder,
{Crawly.Pipelines.WriteToFile, extension: "jl", folder: "./output"}
]
クモ自体は3つのコールバックを実装しています。`parse_item/1`は一度に2つの仕事をこなします:現在のページのアイテムを抽出し、次のページのフォローアップリクエストを構築します — その2つ目のリストがページネーションを駆動します。
```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)
# このページの各商品カードから1つのアイテムを抽出します。
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を実行できませんが、クラウドブラウザは実行できます。
ScrapelessのスクレイピングブラウザにElixirから到達する方法は2通りあり、それぞれが実際のワークロードの2つの半分にマッピングされます。
(a) Scrapeless住宅プロキシを通じたHTTPリクエスト — 大部分のレンダリング
ほとんどのサイトのほとんどのページは、サーバーでレンダリングされたHTMLを配信します。そうしたページの場合、上記の住宅プロキシTierがすべての答えです:ReqとHTTPoisonクライアントはすでに住宅IPを介して送信されており、ブラウザなしでIPの評判ゲートや地理的制限をクリアします。このTierは、コンテンツを直接返す大多数のページで維持してください — これは最も安価な方法であり、Elixirの並行性がそれを速くします。
(b) クラウドブラウザへのコールアウト — クライアントサイドでレンダリングされた少数派
Elixirは、NodeやPythonほどクリーンにChrome DevTools Protocolを駆動しません。したがって、JavaScriptでレンダリングされた少数派に対しては、Elixirをオーケストレーターとして保持し、Scrapeless Scraping Browserに小さなレンダリングヘルパーを介してコールアウトするのが慣用的な動きです。ElixirはSystem.cmd/3でヘルパーを外部プロセスとして生成し、そのヘルパーはクラウドブラウザのドキュメント化されたWebSocketエンドポイントに接続し、ページを実行し、レンダリングされたHTMLをElixirに戻します — Elixirはそれを以前と同じようにFlokiで解析します。
クラウドブラウザのエンドポイントは、APIキーとセッションパラメータをクエリ文字列の値として持つ単一のWebSocket URLです。最小限のPythonレンダラー(render.pyとして保存)は、Playwrightでそれに接続します:
python
# render.py — JSレンダリングの少数派のためにElixirによってSystem.cmd/3で呼び出されます。
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インストールはプロトコルクライアントに過ぎません。ターゲットページの前にホームページをウォームアップすることで、クッキーとナビゲーション状態が生成され、初回訪問者にゲートがかかっているページでクリーンなレンダリングが得られます。
Elixirからは、コールアウトとパースが1つの関数内に保持されます。System.cmd/3は、ヘルパーが戻るまで呼び出しプロセスをブロックします — これはTask内部で構いません。BEAMは他のすべてのプロセスを実行し続けるためです:
elixir
defmodule ElixirScraper.CloudBrowser do
@doc """
Scrapeless Scraping Browserを介して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
前後が全てのポイントです。https://quotes.toscrape.com/js/での単純なReq.getは0個の引用要素を返します。なぜなら、HTTPはページのJavaScriptを実行できないからです。ElixirScraper.CloudBrowser.render/2を通した場合、同じURLはすべての10個の引用で完全にレンダリングされたDOMを返します。これはプラットフォームの挙動であり、調整のトリックではありません。
階層型パイプラインにおいて、最初にReqで取得し、期待する要素の数を数え、空で返ってきたページだけをCloudBrowser.render/2にエスカレートします。ElixirのTask.async_streamは、HTTP Tierを広く、ブラウザTierを狭く実行します。クラウドブラウザのセッションはHTTPリクエストよりも希少であるため、ブラウザTierはmax_concurrency: 3で維持します。
トラブルシューティング
| 症状 | 考えられる原因 | 修正 |
|---|---|---|
Flokiがブラウザに存在するセレクタに対して[]を返す |
ページはクライアントサイドでコンテンツをレンダリングしている; HTTPはアプリシェルを返した | URLをCloudBrowser.render/2にエスカレートし、レンダリングされたHTMLを解析する |
| プロキシCONNECTが拒否された / ゲートウェイから407 | ベーシック認証の資格情報が不足しているか誤っている | proxy_headers(Req)または:proxy_auth(HTTPoison)にチャネルのユーザー名とパスワードが含まれていることを確認 |
Floki.parse_documentが{:error, ...}を返す |
ボディがHTMLではない(JSON API、リダイレクトページ、または空) | resp.statusをチェック; JSONエンドポイントの場合、ボディをHTMLとして解析するのではなくデコード |
同一の内容が country_<CC> にかかわらず |
ページは地域によって異ならず、または国のセグメントが更新されていない | ユーザー名のセグメントが変更されたか確認してください;一部のページは全く地理的制限がありません |
| コンテンツの代わりにアクセス denied または チャレンジ間のインタースティシャル | データセンターのエグレスまたは初回訪問ゲート | 住宅用エグレスを経由し、ターゲットページの前に同じセッションでサイトのホームページをウォームアップします |
| Crawlyが1ページで停止 | parse_item/1が続きのリクエストを返さない |
次のページセレクタが一致することを確認し、Crawly.Utils.request_from_url/1が各絶対URLをラップする |
System.cmdが:enoentエラーを返す |
python実行可能ファイルがPATHにない |
フルインタープリタパスを使用するか、解決されるシェルを介して呼び出します |
セレクタの漂流に関する注意:ターゲットサイトがマークアップを再配置すると、Floki.find/2の呼び出しは空のリストを静かに返し、エラーを発生させることはありません。以前の動作するスクレイパーが空の結果を返し始めたときは、新しいDOMに対してセレクタを再確認し、引き締めてください。空のリストは通常の結果ではなく、検査のシグナルと見なしてください。
結論:Elixirのスクレイピングパイプラインを拡張する
Elixirのパターンは、4つのステップに集約されます。ReqやHTTPoisonを使用して(またはCrawlyにフェッチをスケジュールさせて)Scrapelessの住宅用プロキシを通じて取得し、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で確認できます。
よくある質問
Q: Elixirを使ったウェブスクレイピングは合法ですか?
言語は合法性に関しては関係ありません。公開されているデータのスクレイピングは、多くの法域で一般的に許可されていますが、法律は一様ではありません:各サイトの利用規約を確認し、権利のない個人データや著作権データの収集を避け、ルールが法域によって異なることを覚えておいてください。疑問がある場合は、特定の使用例について法的アドバイスを受けてください。Scrapelessは公開されているデータのみをアクセスします。
Q: Elixirのスクレイピングにはプロキシが必要ですか?
スケールであれば必要です。サーバーのデータセンターIPは、ボット管理者が最初にフラグを立てるものの1つであり、住宅用エグレスはこれらのブロックを大きく減少させます。地域によってコンテンツがゲートされているページがある場合もプロキシが必要です。Scrapelessは195か国以上で住宅用プロキシを提供しています—country_<CC>のユーザー名セグメントを設定し、Req、HTTPoison、またはCrawlyをゲートウェイを通じてルーティングしますので、自分でIPを調達し回転させる必要はありません。
Q: ReqまたはHTTPoison—どちらを使うべきですか?
新しいコードの場合はReqです:JSONデコード、リダイレクトフォロー、Finchを介した接続プーリングを搭載し、ボイラープレートが少なくて済みます。既存のプロジェクトを拡張している場合や、hackneyの:proxy / :proxy_authタプルオプションを直接使用したい場合はHTTPoisonを選択してください。どちらもFlokiで同じように解析されますので、選択はクライアントの使いやすさに関するものであり、スクレイピングの違いではありません。
Q: プレーンHTTPの代わりにScrapeless Scraping Browserが必要な場合はどれですか?
クライアントが返すHTMLがコンテンツのないJavaScriptアプリのシェルである場合です。サインは、実際のブラウザで見えるセレクタがFlokiから空のリストを返すことです。そのページはクライアント側でレンダリングされるため、サーバー側のHTTPクライアントはデータを確認できません。そのURLをクラウドブラウザを通じてルーティングし、JavaScriptを解析する前に実行します。そして、すでにコンテンツを返すページではプレーンHTTPを保持します。
Q: なぜElixirから直接ブラウザを操作するのではなく、レンダリングヘルパーを呼び出すのですか?
Elixir は Node や Python のようにファーストクラスの Chrome DevTools Protocol ドライバーを持っていないため、最もクリーンなパターンは Elixir をオーケストレーターとして保持し、System.cmd/3 で呼び出される小さな外部ヘルパーにレンダリングを委譲します。Elixir はクロールループ、同時実行の上限、ページネーション、および Floki パースを担当し、JavaScript の実行のみがクラウドブラウザに移ります。これにより、Elixir コードは慣用的なまま保たれ、外部呼び出しは一つに統合されます。
Q: いくつの同時リクエストを実行すべきですか?
ホストあたり ≤3 に保ちます。Task.async_stream では max_concurrency: 3 に設定し、Crawly では concurrent_requests_per_domain: 3 に設定します。公共のカタログはもう少し耐えられますが、アンチボット保護されたオリジンは少なめを求めます。しかし、3は安全なデフォルトで、流入中のプールが攻撃に見えないように保ちます。レンダリングされたセッションは HTTP リクエストよりも希少なため、クラウドブラウザ層はさらに狭く保ってください。
Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。



