HTTPoison与cookie

时间:2016-01-18 17:20:09

标签: cookies elixir

我想抓一个网站。我想要抓的网站没有API。

我想要做的是(在Python中):

import requests

with requests.Session() as conn:
    url = "http://demo.ilias.de/login.php"
    auth = {
        "username": "benjamin",
        "password": "iliasdemo"
    }
    conn.post(url, data=auth)
    response = conn.get(url)
    do_work(response)

当尝试使用HTTPoison做同样的事情时,网站会回答“请在浏览器中启用会话cookie!”。 Elixir代码:

HTTPoison.post "http://demo.ilias.de/login.php", 
  "{\"username\":\"benjamin\", \"password\":\"iliasdemo\"}"

我想问题出在cookie上。

UPD#1。似乎并非所有Cookie都已保存,因为:hackney.cookies(headers)headers来自%HTTPoison.Response{headers: headers})并未输出其中一些Cookie(例如authchallenge)我在浏览器和在上面的Python代码的响应中。可能是hackney实际上没有发布任何内容的情况吗?

1 个答案:

答案 0 :(得分:1)

我遇到了类似的问题:

我向服务器api发出GET请求,它在同一位置响应301重定向,然后是#34; Set-Cookie"带有sessionId的标头。如果您按照重定向而不发送回cookie,则会使用相同的重定向和新的SessionId cookie进行响应。如果你从来没有把他们的饼干送回去,这个动机会继续这样。另一方面,如果您将他们的cookie发回给他们,他们会回复200状态代码和您询问的数据。

问题是hackney,因此HTTPoison无法遵循这种情况。 它实际上有一个:follow_redirect选项,当设置它时,它遵循重定向,但在抓取cookie并在重定向之间发回它们时不尽如人意。

我尝试过的所有浏览器(firefox,chrome,IE)都可以通过这种情况。 Python和wget也完成了这项工作。

无论如何,为了简短起见,我为我的案例写了一个解决方法,可能会给其他有类似问题的人提供一些想法:

defmodule GVHTTP do
  defmacro __using__(_) do
    quote do
      use HTTPoison.Base

      def cookies_from_resp_headers(recv_headers) when is_list(recv_headers) do
        List.foldl(recv_headers, [], fn
          {"Set-Cookie", c}, acc -> [c|acc]
          _, acc -> acc
        end)
        |> Enum.map(fn(raw_cookie) ->
            :hackney_cookie.parse_cookie(raw_cookie)
            |> (fn
                  [{cookie_name, cookie_value} | cookie_opts] ->
                    { cookie_name, cookie_value,
                      cookie_opts
                    }
                  _error ->
                    nil
                end).()
        end)
        |> Enum.filter(fn
          nil -> false
          _ -> true
        end)
      end

      def to_request_cookie(cookies) do
        cookies
        |> Enum.map(fn({ cookie_name, cookie_value, _cookie_opts}) ->
            cookie_name <> "=" <> cookie_value
          end)
        |> Enum.join("; ")
        |> (&("" == &1 && [] || [&1])).() # "" => [], "foo1=abc" => ["foo1=abc"]
      end

      def get(url, headers \\ [], options \\ []) do
        case options[:follow_redirect] do
          true ->
            hackney_options = case options[:max_redirect] do
              0 -> options # allow HTTPoison to handle the case of max_redirect overflow error
              _ -> Keyword.drop(options, [:follow_redirect, :max_redirect])
            end
            case request(:get, url, "", headers, hackney_options) do
              {:ok, %HTTPoison.Response{status_code: code, headers: recv_headers}} when code in [301, 302, 307] ->
                {_, location} = List.keyfind(recv_headers, "Location", 0)
                req_cookie =
                  cookies_from_resp_headers(recv_headers)
                  |> to_request_cookie()

                new_options =
                  options
                    |> Keyword.put(:max_redirect, (options[:max_redirect] || 5) - 1)
                    |> Keyword.put(:hackney, [cookie:
                        [options[:hackney][:cookie]|req_cookie]
                        |> List.delete(nil)
                        |> Enum.join("; ")
                      ]) # add any new cookies along with the previous ones to the request
                get(location, headers, new_options)
              resp ->
                resp
            end
          _ ->
            request(:get, url, "", headers, options)
        end
      end

    end # quote end
  end  # __using__ end
end