ryan-senn

ryan-senn

Help with picking the right tools to perform a lot of concurrent http requests

Hello,

I’m building a somewhat unique tool and I’m looking for guidance on how to best architect it.
Basically I need to crawl 100 web pages, collect all links (a tags) and follow them to find out the final destination of the link (follow redirects, some of them use short URLs etc.) to check for the destination domain. Each page can have ~200 links, so I need to potentially follow 20k links.
I am using Elixir 1.10.2 and Phoenix 1.5.1.

Here is what I had in mind, please let me know if there is a better/more efficient way to achieve this.

  1. Use a LiveView module as a GenServer, this way I can show progress live in the browser
  2. Use Task.async to fetch the 100 pages concurrently
  3. Catch the responses with a handle_info function. Use Floki to parse and find all a tags (links). Use Enum.each on the links and make the http request in another Task.async for each of them. I would use Tesla with Hackney, as Tesla has a middle ware to follow redirects. I would do a head request, as I don’t need the body.
  4. Catch the responses with another handle_info function and check for the domain. If it matches the domain, update the socket to show the link, otherwise just return the unmodified socket

Here is what I had in mind code-wise (not implemented, just laying out the basic idea).

defmodule Example.PageLive do
  use Example, :live_view

  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  def handle_event("submit", _, socket) do
    # the initial 100 pages
    pages = ["http://example.com"]

    # Task.async each of them
    Enum.each(pages, fn page ->
      Task.async(fn ->
        # load the links
        links =
          page
          |> Tesla.get()
          |> Floki.parse_and_find_links()

        # send the response to handle_info
        {:page_loaded, links}
      end)
    end)

    # could show status
    {:noreply, socket}
  end

  def handle_info({_pid, {:page_loaded, links}}, socket) do
    # Task.async each of them
    Enum.each(links, fn link ->
      Task.async(fn ->
        # find destination with Tesla.head and the follow_redirects middleware
        destination = Tesla.head(link)

        # send the response to handle_info
        {:link_loaded, destination}
      end)
    end)

    # could show status
    {:noreply, socket}
  end

  def handle_info({_pid, {:link_loaded, destination}}, socket) do
    if URI.parse(destination).host == "example.com" do
      # do stuff if destination is the domain we're looking for
      {:noreply, socket}
    else
      # ignore otherwise
      {:noreply, socket}
    end
  end
end

Is this a sensible approach? Do you think that Tesla and Hackney are good choices? I don’t have much experience with GenServers and stuff like that. What sort of server would I need to be able to run all of this specs wise? Also I read a little bit about hackney pools, do I need to do anything? Also will using a LiveView for my GenServer cause any performance issues?

And last question: Is Elixir a good choice for doing something like this?

Thanks heaps in advance!

Most Liked

LostKobrakai

LostKobrakai

Your general approach seems fine. I’d switch out Task.async with something better at dealing with potential of overwhelming the system/network/the found endpoints… like GenStage. Also I’d be cautious of starting an exponentially growing numbers of Tasks concurrently without limit. The BEAM is great at handling the processes, but timeouts might spiral out of control. I’d rather look at one (or a set of) queues to schedule work onto.

If you find performance problems then I’d start looking into alternative http clients.

quatermain

quatermain

This is maybe ready-made solution for you Crawly or you can inspire from their solution.

Where Next?

Popular in Questions Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54120 245
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement