🎯 Trình duyệt đám mây tùy chỉnh, chống phát hiện được hỗ trợ bởi Chromium tự phát triển, thiết kế dành cho trình thu thập dữ liệu webtác nhân AI. 👉Dùng thử ngay
Quay lại blog

Tại sao Trình thu thập thông tin Elixir của bạn bị chặn, và cách mà Proxy Residential + Trình duyệt đám mây khắc phục điều đó

James Thompson
James Thompson

Scraping and Proxy Management Expert

03-Jun-2026

Những điểm chính:

  • Elixir được xây dựng cho việc thu thập đồng thời. Runtime BEAM thực hiện hàng ngàn quy trình nhẹ trên một nút, vì vậy một quá trình thu thập phân tán qua hàng trăm URL chạy như một Task.async_stream đơn giản thay vì một nhóm luồng mà bạn phải chăm sóc.
  • Req và HTTPoison thu thập, Floki phân tích. Req là khách hàng HTTP hiện đại, bao gồm các thư viện cần thiết; HTTPoison là tùy chọn lâu dài được hỗ trợ bởi Hackney; Floki chuyển đổi HTML thô thành một cây mà bạn truy vấn bằng các bộ chọn CSS. Cùng nhau, chúng bao phủ bất kỳ trang nào gửi mã nhúng đã được kết xuất.
  • Crawly là khuôn khổ thu thập đầy đủ. Nó lên lịch yêu cầu giữa các worker, xử lý phân trang thông qua các yêu cầu theo sau, áp dụng các middleware cho việc xoay vòng user-agent và tùy chọn yêu cầu, và đẩy các mục đã phân tích xuống một pipeline — theo phong cách Scrapy, nhưng trên BEAM.
  • Proxy dân cư Scrapeless điều hướng các lần thu thập. Một máy chủ proxy duy nhất, cổng và tiêu đề Basic-auth kết nối trực tiếp với connect_options của Req, :proxy / :proxy_auth của HTTPoison, hoặc middleware RequestOptions của Crawly, cung cấp cho mỗi yêu cầu một IP dân cư và định vị vị trí rời.
  • Các mục tiêu nặng JS và chống bot leo thang đến Trình duyệt Thu thập Scrapeless. Elixir không điều khiển Giao thức DevTools Chrome sạch sẽ như Node hoặc Python, vì vậy trình duyệt đám mây được truy cập theo hai cách: yêu cầu HTTP qua proxy dân cư Scrapeless cho phần lớn mã đã được kết xuất và một cuộc gọi nhỏ đến trình duyệt đám mây cho phần nhỏ được kết xuất ở phía khách hàng.
  • Miễn phí để bắt đầu. Các tài khoản Scrapeless mới bao gồm thời gian chạy Trình duyệt Thu thập miễn phí — đăng ký tại app.scrapeless.com.

Giới thiệu: Tại sao Elixir, và nơi bắt đầu có sự ma sát

Elixir chạy trên BEAM, cùng một máy ảo đã giữ cho các công tắc viễn thông Erlang trực tuyến trong nhiều thập kỷ. Đặc điểm nổi bật của nó cho việc thu thập là khả năng đồng thời giá rẻ: khởi tạo mười ngàn quy trình là điều bình thường, mỗi quy trình được tách biệt, mỗi quy trình có thể giữ một yêu cầu HTTP đang bay mà không làm gián đoạn các quy trình khác. Một lần thu thập cần một khuôn khổ async và tinh chỉnh nhóm cẩn thận trong một ngôn ngữ khác là một Task.async_stream với một giới hạn max_concurrency trong Elixir.

Câu chuyện thư viện đã trưởng thành. ReqHTTPoison thu thập các trang, Floki phân tích chúng bằng các bộ chọn CSS, và Crawly bọc toàn bộ chu trình — lên lịch, loại bỏ trùng lặp, phân trang, và pipeline mục — vào một khuôn khổ theo phong cách Scrapy vẫn cảm giác như Elixir chính thống. Đối với các danh mục tĩnh, bản đồ trang, và các trang đã được máy chủ kết xuất, ngăn xếp đó đã hoàn chỉnh tự nó.

Hai điều phá vỡ nó. Đầu tiên, danh tiếng IP: một địa chỉ trung tâm dữ liệu sạch sẽ bị đánh dấu ngay khi một mục tiêu chạy ngay cả một trình quản lý bot cơ bản, và không có bất kỳ tinh chỉnh tiêu đề nào sửa chữa được một IP rời bị chặn. Thứ hai, kết xuất phía khách hàng: một ứng dụng một trang trả về HTTP 200 với một <div id="app"> rỗng, và Floki phân tích chính xác những gì đã đến — không có gì. Trang nhìn đầy đủ trong trình duyệt và trống rỗng đối với trình thu thập.

Hướng dẫn này xây dựng ngăn xếp Elixir theo tầng. Tầng HTTP sử dụng Req, HTTPoison, và Crawly được điều hướng qua các proxy dân cư Scrapeless tại hơn 195 quốc gia. Tầng JS đã được kết xuất leo thang đến Trình duyệt Thu thập Scrapeless, được tiếp cận từ Elixir mà không cần yêu cầu BEAM nói CDP trực tiếp. Đối với lớp proxy dân cư mà các lần thu thập này đi qua, xem Proxy SSL là gì?.


Bạn có thể xây dựng gì

Mô hình hai tầng — các thư viện Elixir ở phía trước, Scrapeless ở phía sau sự leo thang — bao phủ hầu hết các công việc mà đánh bại một trình thu thập HTTP đơn giản:

  • Thu thập danh mục đồng thời. Bản đồ trang, kho lưu trữ bài viết, danh sách sản phẩm — Task.async_stream phân bố qua tập hợp URL với số lượng worker giới hạn và phân tích mỗi trang bằng Floki.
  • Giám sát theo lịch với Crawly. Định nghĩa một con nhện một lần, cho nó đi qua các danh sách theo lịch, và đẩy các mục đã phân tích xuống một pipeline xác thực và lưu trữ.
  • Tổng quan địa lý cụ thể. Gán quốc gia proxy Scrapeless để giá cả, khả năng có sẵn và tường đồng ý trả về những gì một người dùng địa phương thấy, không phải là bất cứ điều gì mà IP của máy chủ của bạn giải quyết.
  • Trích xuất kiên cố sau các trình quản lý bot. Đường dẫn thu thập qua egress dân cư để một IP người dùng bình thường, không phải một dải trung tâm dữ liệu, thực hiện yêu cầu.
  • RAG và LLM tiêu thụ. Kết xuất các trang nhà xuất bản và tài liệu thành văn bản sạch, sau đó cho nội dung đã trích xuất vào một pipeline nhúng.
  • Các trang SPA và cuộn vô hạn. Leo thang phần nhỏ được kết xuất phía khách hàng lên Trình duyệt Thu thập Scrapeless, cái mà chạy JavaScript phía đám mây trước khi bạn phân tích kết quả.

Tại sao kết hợp Elixir với Scrapeless

Elixir cung cấp cho bạn khả năng đồng thời, phân tích cú pháp và một framework thu thập dữ liệu; Scrapeless Scraping Browser cung cấp luồng xuất và hệ thống kết xuất mà một khách hàng HTTP phía máy chủ không thể cung cấp. Hai công cụ này hoạt động cùng nhau vì sự chuyển giao là một proxy HTTP tiêu chuẩn ở một bên và một điểm cuối trình duyệt đám mây đã được tài liệu hóa ở phía bên kia.

  • Proxy dân cư tại hơn 195 quốc gia. Được hiển thị dưới dạng một máy chủ proxy duy nhất, cổng và thông tin xác thực Basic-auth — có thể dễ dàng tích hợp vào Req, HTTPoison hoặc middleware RequestOptions của Crawly.
  • Pin vị trí địa lý theo yêu cầu. Một mã quốc gia trong tên người dùng proxy kiểm soát vị trí địa lý xuất mà không cần thêm một giai đoạn bắt tay, do đó mã giống nhau có thể lấy lượt xem từ Mỹ, Vương quốc Anh, Đức hoặc Nhật Bản chỉ bằng cách thay đổi một phần.
  • Trình duyệt đám mây chống phát hiện. Đối với các trang web được kết xuất phía client, Scrapeless Scraping Browser chạy trình duyệt Chromium tự phát triển với khả năng kết xuất JavaScript hoàn toàn trên đám mây và ngẫu nhiên hóa dấu vân tay theo phiên, để các SPA và các bảng tải chậm được làm đầy trước khi trích xuất.
  • Một API key cho cả hai cấp. Proxy dân cư và Scraping Browser được tính phí từ cùng một tài khoản Scrapeless, vì vậy cấp HTTP và cấp đã được kết xuất chia sẻ một thông tin xác thực.
  • Tùy chọn phiên dính. Giữ nguyên địa chỉ IP dân cư trong suốt một quá trình nhiều bước khi một luồng cần sự liên tục, hoặc xoay vòng theo yêu cầu cho các trường hợp khác.

Thời gian chạy miễn phí để bắt đầu và mở rộng theo mức sử dụng — xem giá Scrapeless để biết các cấp, và nhận API key của bạn ở gói miễn phí tại app.scrapeless.com.


Điều kiện tiên quyết

  • Elixir 1.16+ và Erlang/OTP 26+ — kiểm tra bằng elixir --version.
  • Một tài khoản Scrapeless và API key — đăng ký gói miễn phí tại app.scrapeless.com, sau đó lấy key từ Cài đặt → Quản lý API Key.
  • Thông tin xác thực proxy dân cư — hiển thị trong bảng điều khiển dưới Proxy → Residential tại app.scrapeless.com.
  • Hiểu biết cơ bản về mix, CSS selectors và terminal.

Cài đặt: thiết lập dự án mix và các phụ thuộc

Tạo một dự án mới và thêm các thư viện thu thập dữ liệu. mix new sẽ tạo cấu trúc; bốn phụ thuộc này bao gồm việc lấy dữ liệu (req, httpoison), phân tích cú pháp (floki), và toàn bộ framework thu thập dữ liệu (crawly):

bash Copy
mix new elixir_scraper --sup
cd elixir_scraper

Thêm các phụ thuộc vào mix.exs:

elixir Copy
# mix.exs
defp deps do
  [
    {:req, "~> 0.5"},        # khách hàng HTTP hiện đại (Finch/Mint ở phía dưới)
    {:httpoison, "~> 2.2"},  # khách hàng HTTP sử dụng hackney, tùy chọn lâu dài
    {:floki, "~> 0.36"},     # phân tích cú pháp HTML với truy vấn CSS-selector
    {:crawly, "~> 0.17"}     # framework thu thập dữ liệu toàn diện, theo phong cách Scrapy
  ]
end

Sau đó tải chúng xuống:

bash Copy
mix deps.get

Bạn không cần cả bốn trong một dự án thực tế — req cộng với floki là cặp tối thiểu để lấy và phân tích. Hướng dẫn thể hiện từng cái để bạn có thể chọn khách hàng phù hợp với ngăn xếp của bạn.


Cấu hình: lưu trữ thông tin xác thực Scrapeless của bạn

Xuất API key và thông tin xác thực proxy dân cư của bạn dưới dạng biến môi trường để chúng không nằm trong quản lý mã nguồn. Trong bảng điều khiển dưới Proxy → Residential, nhấp vào Tạo và bảng điều khiển sẽ in một chuỗi kết nối phân cách bằng dấu hai chấm theo dạng <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"          # in ở đầu tên người dùng
export SCRAPELESS_PROXY_PASS="your_channel_password"
export SCRAPELESS_PROXY_GATEWAY="gw-us.scrapeless.io"   # xem các cổng khu vực bên dưới

Cổng khu vực: gw-us.scrapeless.io (Châu Mỹ), gw-eu.scrapeless.io (Châu Âu), gw-ap.scrapeless.io (Châu Á-Thái Bình Dương). Chọn cổng gần nhất với thời gian chạy của bạn để giữ độ trễ giai đoạn bắt tay thấp; quốc gia xuất vẫn được kiểm soát bởi đoạn tên người dùng country_<CC> bất kể bạn kết nối qua cổng nào. Cổng là 8789 cho tất cả.

Tên người dùng proxy dân cư được xây dựng từ bốn tham số:

  • <CHANNEL_ID> — định danh kênh của bạn (in ở đầu tên người dùng trên bảng điều khiển).
  • country_<CC> — mã quốc gia dưới dạng mã ISO hai chữ cái: country_US, country_GB, country_DE, country_JP, v.v. (sử dụng mã hiển thị trong bộ chọn vị trí trên bảng điều khiển).
  • r_<duration> — khoảng thời gian xoay vòng phiên dính (ví dụ: r_10m giữ nguyên cùng một IP trong 10 phút trước khi xoay vòng).
  • s_<SESSION_ID> — định danh phiên sticky-session; sử dụng lại cùng một s_<id> để giữ một IP xuyên suốt các yêu cầu trong khoảng thời gian xoay vòng.

Bỏ qua các khối r_s_ để có một IP dân cư mới cho mỗi yêu cầu; giữ chúng khi một hành trình phân trang cần cùng một IP xuyên suốt.


Cơ bản: lấy dữ liệu với Req qua một proxy dân cư, phân tích với Floki

Req chuyển hướng thông qua một proxy HTTP với khóa :connect_options, mà nó chuyển tiếp đến Finch và Mint bên dưới. Xác thực proxy được đưa vào :proxy_headers dưới dạng một tiêu đề Basic-auth duy nhất — Mint hợp nhất nó vào yêu cầu CONNECT. Tên người dùng mang mã quốc gia, vì vậy dòng proxy tự chọn địa lý thoát:

elixir Copy
defmodule ElixirScraper.ReqClient do
  @gateway System.get_env("SCRAPELESS_PROXY_GATEWAY") || "gw-us.scrapeless.io"
  @port 8789

  # Xây dựng tên người dùng proxy với mã quốc gia được nhúng vào.
  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 "Lấy một URL qua cổng thoát dân cư Scrapeless ở `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

Gọi nó và đưa nội dung thẳng đến Floki. Floki.parse_document/1 chuyển đổi chuỗi HTML thành một cây; Floki.find/2 truy vấn nó bằng các bộ chọn CSS; Floki.text/1Floki.attribute/2 lấy giá trị ra:

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: "trang đầu tiên")

Ba điều này được khẳng định sớm:

  • Proxy được cấu hình theo từng yêu cầu, không phải toàn cầu. Điều này giữ cho một client tự do kéo các quốc gia khác nhau bằng cách truyền một tham số country khác.
  • Tiêu đề Basic-auth là dòng mang tải. Nếu không có proxy_headers, đường hầm CONNECT đến cổng thoát dân cư bị từ chối vì thiếu thông tin xác thực.
  • Floki truy vấn cây đã phân tích, không phải chuỗi thô. Luôn luôn parse_document/1 trước, sau đó là find/2 — các bộ chọn chạy đối với cây.

Nâng cao 1: biến thể HTTPoison

HTTPoison ra đời trước Req và vẫn phổ biến trong các mã nguồn hiện tại. Nó dựa trên hackney, cho phép cấu hình proxy qua hai tùy chọn yêu cầu: :proxy dưới dạng một tuple {host, port}:proxy_auth dưới dạng một tuple {user, password}. Không cần mã hóa Base64 thủ công — hackney tự động xây dựng tiêu đề:

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

Phần phân tích thì giống nhau — HTTPoison trả về một chuỗi nội dung, và Floki sẽ xử lý phần còn lại. Chọn Req cho mã mới (nó hỗ trợ giải mã JSON, chuyển hướng và quản lý kết nối sẵn có) và HTTPoison khi bạn đang mở rộng một dự án đã được xây dựng trên đó.


Nâng cao 2: một spider Crawly với phân trang và cổng thoát proxy

Đối với bất cứ thứ gì ngoài một nhóm các URL nhỏ, Crawly thay thế vòng lặp thủ công. Một spider khai báo các URL bắt đầu của nó và một callback parse_item/1; Crawly lập lịch các yêu cầu qua các worker, theo dõi các yêu cầu mới mà callback trả về (đó là cách phân trang hoạt động), và đẩy các mục đã phân tích xuống một ống dẫn.

Kết nối proxy thông qua middleware RequestOptions. Nó chuyển danh sách từ khóa của nó thẳng đến trình tìm nạp HTTPoison bên dưới, vì vậy cùng một tùy chọn :proxy:proxy_auth từ biến thể HTTPoison áp dụng cho mọi yêu cầu mà spider thực hiện:

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, 
vi Copy
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"}
  ]

Con bọ tự thực hiện ba callback. parse_item/1 thực hiện hai nhiệm vụ cùng một lúc: nó trích xuất các mục trên trang hiện tại, và nó xây dựng các yêu cầu theo dõi cho trang tiếp theo — danh sách thứ hai đó là thứ điều khiển phân trang:

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

    # Trích xuất một mục cho mỗi thẻ sản phẩm trên trang này.
    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)

    # Xây dựng yêu cầu trang tiếp theo: danh sách này là cách Crawly phân trang.
    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

Chạy nó từ iex -S mix:

elixir Copy
Crawly.Engine.start_spider(BooksSpider)

Crawly đi qua từng trang bằng cách theo dõi liên kết li.next a mà callback trả về, viết mỗi mục đã được xác thực, không trùng lặp vào ./output/BooksSpider.jl, và dừng lại tại closespider_itemcount. Mỗi yêu cầu đi ra qua proxy dân cư Scrapeless vì middleware RequestOptions đã chèn các tùy chọn :proxy:proxy_auth vào yêu cầu trước khi trình thu thập chạy.

Lấy khóa API của bạn trên kế hoạch miễn phí: app.scrapeless.com


Tránh bị chặn: ra ngoài dân cư, ghim địa lý và áp lực trở lại

Một trình thu thập bị chặn vì những lý do có thể dự đoán, và hầu hết trong số đó có thể xử lý từ cấu hình bạn đã có:

  • Danh tiếng IP datacenter. Dải IP của một máy chủ là tín hiệu đầu tiên mà một nhà quản lý bot kiểm tra. Định tuyến qua các proxy dân cư Scrapeless khiến yêu cầu trông giống như một kết nối gia đình thông thường, đây là yếu tố lớn nhất chống lại các chặn danh tiếng IP.
  • Địa lý ra ngoài. Các trang kiểm soát nội dung theo khu vực — giá cả, hàng tồn kho, tường đồng ý. Ghim quốc gia với đoạn tên người dùng country_<CC> để kết quả khớp với địa phương bạn dự định đọc.
  • Độ đồng thời trông giống như một cuộc tấn công. Giới hạn sự song song ở ≤3 yêu cầu cho mỗi máy chủ. Với Task.async_stream, đó là max_concurrency: 3; với Crawly, đó là concurrent_requests_per_domain: 3. Qua đó, một bể yêu cầu chặt chẽ thì không thể phân biệt với một trận lụt.
  • Một user-agent mặc định. ReqHTTPoison gửi UA mặc định từ thư viện mà rất dễ lọc. Đặt một user-agent trình duyệt thực tế (như các đoạn mã trên đã làm) hoặc luân phiên một danh sách qua middleware UserAgent của Crawly.
  • Tốc độ. Đối với các vòng lặp không phải Crawly, cách xa các yêu cầu với một Process.sleep/1 nhỏ giữa các lô thay vì bắn toàn bộ một lần. Crawly điều phối tốc độ yêu cầu cho bạn thông qua bộ lập lịch của nó.

Không có gì trong số này cứu một trang nội dung đến qua JavaScript sau khi sơn lần đầu — đó là phần sau.


Mục tiêu nhiều JS và chống bot: đi qua Scrapeless Scraping Browser

Req, HTTPoison, và Crawly đều trả về bất kỳ byte nào mà nguồn gửi. Đối với một ứng dụng React, Vue, hoặc Next.js, những byte đó là một vỏ rỗng cộng với một thẻ script — nội dung được vẽ ở phía khách hàng, và Floki phân tích một cây rỗng. Một khách hàng HTTP bên máy chủ không thể chạy JavaScript đó; một trình duyệt đám mây thì có thể.

Có hai cách để tiếp cận Scrapeless Scraping Browser từ Elixir, và chúng tương ứng với hai nửa của một khối lượng công việc thực sự.

(a) Yêu cầu HTTP qua các proxy dân cư Scrapeless — phần lớn nội dung được render

Copy
Hầu hết các trang trên hầu hết các trang web gửi HTML được render từ phía máy chủ. Đối với những trang này, tầng proxy dân cư ở trên là câu trả lời hoàn toàn: các khách hàng `Req` và `HTTPoison` đã thoát qua một IP dân cư, điều này vượt qua các cổng danh tiếng IP và các hạn chế địa lý mà không cần trình duyệt nào cả. Giữ lại tầng này trên phần lớn các trang trả về nội dung trực tiếp — đây là con đường rẻ nhất, và độ song song của Elixir giúp nó nhanh chóng.

### (b) Gọi đến trình duyệt đám mây — thiểu số được render từ phía client

Elixir không điều khiển giao thức Chrome DevTools một cách gọn gàng như Node hoặc Python, vì vậy đối với thiểu số được render bằng JavaScript, động thái idiomatic là giữ Elixir như một trình điều phối và gọi đến Scrapeless Scraping Browser thông qua một tiện ích hỗ trợ render nhỏ. Elixir khởi động tiện ích đó như một tiến trình bên ngoài với `System.cmd/3`, tiện ích kết nối đến điểm cuối WebSocket được tài liệu của trình duyệt đám mây, chạy trang và in HTML đã render lại cho Elixir — mà phân tích nó với Floki giống như trước đây.

Điểm cuối trình duyệt đám mây là một URL WebSocket duy nhất với khóa API và tham số phiên của bạn như các giá trị chuỗi truy vấn. Một trình render Python tối thiểu (được lưu dưới dạng `render.py`) kết nối với nó bằng Playwright:

```python
# render.py — được gọi bởi Elixir thông qua System.cmd/3 cho thiểu số được render bằng 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()
        # Làm nóng trang chính trước, sau đó điều hướng đến trang mục tiêu.
        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"))

Quá trình render diễn ra bên phía đám mây trong trình duyệt chống phát hiện của Scrapeless; cài đặt Playwright cục bộ chỉ là khách hàng của giao thức. Làm nóng trang chính trước khi đến trang mục tiêu tạo ra cookie và trạng thái điều hướng, điều này tạo ra một rendering sạch hơn trên các trang mà khóa người truy cập lần đầu.

Từ Elixir, việc gọi và phân tích vẫn trong một hàm. System.cmd/3 chặn tiến trình gọi cho đến khi tiện ích hỗ trợ trả về — vẫn ổn bên trong một Task, vì BEAM giữ cho mọi tiến trình khác vẫn chạy:

elixir Copy
defmodule ElixirScraper.CloudBrowser do
  @doc """
  Render một `url` nặng JS thông qua Scrapeless Scraping Browser và
  trả về HTML đã vẽ cho Floki phân tích.
  """
  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

Việc trước/sau là điểm chính. Một Req.get đơn giản trên https://quotes.toscrape.com/js/ trả về 0 phần tử trích dẫn, vì HTTP không thể thực thi JavaScript trên trang. Cùng một URL thông qua ElixirScraper.CloudBrowser.render/2 trả về DOM đã được render hoàn chỉnh với tất cả 10 trích dẫn, vì trình duyệt đám mây đã chạy JavaScript trước. Đó là hành vi của nền tảng, không phải là một mẹo điều chỉnh.

Đối với một pipeline theo tầng, lấy trước với Req, đếm số phần tử bạn mong đợi, và chỉ tăng cường các trang trả về rỗng đến CloudBrowser.render/2. Task.async_stream của Elixir chạy tầng HTTP rộng và tầng trình duyệt hẹp, vì phiên trình duyệt đám mây hiếm hơn các yêu cầu HTTP — giữ tầng trình duyệt ở max_concurrency: 3.


Khắc phục sự cố

Triệu chứng Nguyên nhân có thể Cách khắc phục
Floki trả về [] cho một bộ chọn tồn tại trong trình duyệt Trang render nội dung phía client; HTTP trả về một shell ứng dụng Tăng cường URL lên CloudBrowser.render/2; phân tích HTML đã render
Proxy CONNECT bị từ chối / 407 từ gateway Thông tin xác thực Basic-auth thiếu hoặc sai Xác nhận proxy_headers (Req) hoặc :proxy_auth (HTTPoison) mang theo tên người dùng và mật khẩu kênh
Floki.parse_document trả về {:error, ...} Nội dung không phải là HTML (API JSON, trang điều hướng, hoặc rỗng) Kiểm tra resp.status; đối với các điểm cuối JSON, giải mã nội dung thay vì phân tích nó như HTML
Nội dung giống nhau bất kể quốc gia_<CC> Trang không thay đổi theo khu vực, hoặc phân khúc quốc gia chưa được cập nhật Xác minh phân khúc tên người dùng đã thay đổi; một số trang không bị địa lý khóa
Truy cập bị từ chối hoặc thách thức giữa chừng thay vì nội dung Lối ra trung tâm dữ liệu hoặc cổng lần đầu truy cập Định tuyến qua lối ra dân cư và làm nóng trang chủ của trang web trong cùng một phiên trước trang mục tiêu
Crawly dừng lại sau một trang parse_item/1 không trả về yêu cầu tiếp theo Xác nhận bộ chọn trang tiếp theo khớp và Crawly.Utils.request_from_url/1 bọc từng URL tuyệt đối
Lỗi System.cmd với :enoent Tập lệnh python không có trong PATH Sử dụng đường dẫn đầy đủ đến trình thông dịch, hoặc gọi thông qua một shell có thể giải quyết nó

Một lưu ý về việc trôi bộ chọn: khi một trang web mục tiêu sắp xếp lại mã của nó, các cuộc gọi Floki.find/2 của bạn sẽ âm thầm trả về danh sách rỗng thay vì báo động. Kiểm tra lại và thắt chặt các bộ chọn so với DOM mới mỗi khi một scraper hoạt động trước đó bắt đầu trả về kết quả rỗng — coi danh sách rỗng như một tín hiệu để kiểm tra, không phải là một kết quả bình thường.


Kết luận: mở rộng quy trình lấy dữ liệu Elixir của bạn

Mô hình Elixir giảm xuống còn bốn bước. Lấy dữ liệu với Req hoặc HTTPoison (hoặc để Crawly lên lịch các lần lấy dữ liệu) qua các proxy dân cư Scrapeless; phân tích với các bộ chọn CSS của Floki; phân trang bằng cách trả về các yêu cầu tiếp theo từ parse_item/1; và nâng cấp thiểu số được render bằng JavaScript lên Scrapeless Scraping Browser, được truy cập từ Elixir như một cuộc gọi render bên ngoài thay vì yêu cầu BEAM giao tiếp CDP trực tiếp.

Từ đây, hình dạng giống nhau sẽ ghép thành các hệ thống lớn hơn. Để tìm hiểu sâu về lớp proxy dân cư, xem Proxy SSL là gì?. Trước khi bạn gửi: gán quốc gia_<CC> cho các trang giới hạn theo địa lý, giữ độ đồng thời ở ≤3 mỗi máy chủ, đặt một user-agent thực tế, coi các bộ chọn không có sẵn như có thể là rỗng, và giữ cho tầng HTTP rộng và tầng trình duyệt đám mây hẹp.


Sẵn sàng xây dựng quy trình dữ liệu AI của bạn?

Tham gia cộng đồng của chúng tôi để nhận một gói miễn phí và kết nối với các nhà phát triển đang xây dựng quy trình lấy dữ liệu Elixir: Discord · Telegram.

Đăng ký tại app.scrapeless.com để nhận runtime Scraping Browser miễn phí và điều chỉnh các mẫu ở trên cho các trang và vùng mà quy trình của bạn cần. Tài liệu tham khảo đầy đủ tại docs.scrapeless.com.


Câu hỏi thường gặp

Q: Lấy dữ liệu web với Elixir có hợp pháp không?

Ngôn ngữ không liên quan đến tính hợp pháp. Việc lấy dữ liệu công khai thường được phép ở nhiều khu vực pháp lý, nhưng pháp luật không đồng nhất: xem lại Điều khoản Dịch vụ của từng trang web, tránh thu thập dữ liệu cá nhân hoặc dữ liệu bản quyền mà bạn không có quyền, và hãy nhớ rằng các quy tắc thay đổi theo khu vực pháp lý. Khi không chắc chắn, hãy tìm lời khuyên pháp lý cho trường hợp sử dụng cụ thể của bạn. Scrapeless chỉ truy cập dữ liệu công khai.

Q: Tôi có cần proxy cho việc lấy dữ liệu Elixir không?

Đối với bất kỳ điều gì ở quy mô lớn, có. Địa chỉ IP trung tâm dữ liệu của một máy chủ là một trong những điều đầu tiên mà một quản lý bot đánh dấu, và lối ra dân cư giảm mạnh những khối đó. Một proxy cũng được yêu cầu bất cứ khi nào một trang khóa nội dung theo khu vực. Scrapeless cung cấp proxy dân cư ở trên 195 quốc gia — đặt phân khúc tên người dùng quốc gia_<CC> và định tuyến Req, HTTPoison, hoặc Crawly qua cổng, để bạn không phải tự tìm nguồn và xoay vòng các IP.

Q: Req hay HTTPoison — cái nào tôi nên sử dụng?

Đối với mã mới, Req: nó cung cấp giải mã JSON, theo dõi chuyển hướng và kết nối pooling qua Finch, với ít boilerplate hơn. Chọn HTTPoison khi bạn đang mở rộng một dự án đã được xây dựng trên đó, hoặc khi bạn muốn các tùy chọn :proxy / :proxy_auth của hackney trực tiếp. Cả hai đều phân tích giống hệt nhau với Floki, vì vậy sự lựa chọn phụ thuộc vào sự tiện lợi của khách hàng, không phải việc lấy dữ liệu.

Q: Khi nào tôi cần Scrapeless Scraping Browser thay vì HTTP thông thường?

Khi HTML mà khách hàng của bạn trả về là một ứng dụng JavaScript không có nội dung. Dấu hiệu: một bộ chọn mà bạn có thể thấy trong một trình duyệt thực tế trả về danh sách rỗng từ Floki. Trang đó được render ở phía khách hàng, vì vậy một khách hàng HTTP ở phía máy chủ không bao giờ thấy dữ liệu. Định tuyến những URL đó qua trình duyệt đám mây, cái sẽ chạy JavaScript trước khi bạn phân tích — và giữ HTTP thông thường trên các trang đã trả về nội dung.

Q: Tại sao gọi đến một trợ giúp render thay vì điều khiển trình duyệt từ Elixir trực tiếp?

Elixir không có trình điều khiển giao thức Chrome DevTools loại một như Node và Python, vì vậy mẫu sạch nhất giữ Elixir như là người điều phối và ủy quyền việc hiển thị cho một công cụ trợ giúp nhỏ bên ngoài được gọi bằng System.cmd/3. Elixir vẫn kiểm soát vòng lặp thu thập thông tin, giới hạn đồng thời, phân trang và phân tích Floki; chỉ việc thực thi JavaScript được chuyển sang trình duyệt đám mây. Điều này giữ cho mã Elixir mang tính biểu đạt và tích hợp chỉ với một cuộc gọi bên ngoài duy nhất.

Hỏi: Tôi nên chạy bao nhiêu yêu cầu đồng thời?

Giữ ở mức ≤3 mỗi máy chủ. Với Task.async_stream, đặt max_concurrency: 3; với Crawly, đặt concurrent_requests_per_domain: 3. Các danh mục công khai cho phép một chút hơn, nhưng các nguồn được bảo vệ chống bot muốn ít hơn, nhưng 3 là mặc định an toàn giúp giữ cho nhóm đang xử lý không trông giống như một cuộc tấn công. Giữ cho tầng trình duyệt đám mây thậm chí còn hẹp hơn, vì các phiên được hiển thị hiếm hơn so với các yêu cầu HTTP.

Tại Scrapless, chúng tôi chỉ truy cập dữ liệu có sẵn công khai trong khi tuân thủ nghiêm ngặt các luật, quy định và chính sách bảo mật trang web hiện hành. Nội dung trong blog này chỉ nhằm mục đích trình diễn và không liên quan đến bất kỳ hoạt động bất hợp pháp hoặc vi phạm nào. Chúng tôi không đảm bảo và từ chối mọi trách nhiệm đối với việc sử dụng thông tin từ blog này hoặc các liên kết của bên thứ ba. Trước khi tham gia vào bất kỳ hoạt động cạo nào, hãy tham khảo ý kiến ​​cố vấn pháp lý của bạn và xem xét các điều khoản dịch vụ của trang web mục tiêu hoặc có được các quyền cần thiết.

Bài viết phổ biến nhất

Danh mục