From a5ec28664d5ac314c381ed32ce90d0c0348f6832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Musab=20G=C3=BCltekin?= Date: Mon, 17 Jun 2019 13:31:19 +0300 Subject: [PATCH] Cookies support added. --- README.md | 9 +++++---- geziyor.go | 16 ++++++++-------- geziyor_test.go | 35 ++++++++++++++++++++++++++++------- internal/http.go | 46 ++++++++++++++++++++++++++++++++++++++++++++-- options.go | 3 +++ 5 files changed, 88 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index bd0c2e5..b805c74 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Geziyor is a blazing fast web crawling and web scraping framework. It can be use - Limit Concurrency (Global/Per Domain) - Request Delays (Constant/Randomized) - Automatic response decoding to UTF-8 +- Cookies - Middlewares See scraper [Options](https://godoc.org/github.com/geziyor/geziyor#Options) for all custom settings. @@ -23,7 +24,7 @@ Since the project is in **development phase**, **API may change in time**. Thus, Simple usage ```go -geziyor.NewGeziyor(geziyor.Options{ +geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://api.ipify.org"}, ParseFunc: func(g *geziyor.Geziyor, r *geziyor.Response) { fmt.Println(string(r.Body)) @@ -35,7 +36,7 @@ Advanced usage ```go func main() { - geziyor.NewGeziyor(geziyor.Options{ + geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://quotes.toscrape.com/"}, ParseFunc: quotesParse, Exporters: []geziyor.Exporter{exporter.JSONExporter{}}, @@ -75,7 +76,7 @@ Geziyor makes concurrent requests to those URLs. After reading response, ```ParseFunc func(g *Geziyor, r *Response)``` called. ```go -geziyor.NewGeziyor(geziyor.Options{ +geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://api.ipify.org"}, ParseFunc: func(g *geziyor.Geziyor, r *geziyor.Response) { fmt.Println(string(r.Body)) @@ -94,7 +95,7 @@ As it opens up a real browser, it takes a couple of seconds to make requests. ```go -geziyor.NewGeziyor(geziyor.Options{ +geziyor.NewGeziyor(&geziyor.Options{ StartRequestsFunc: func(g *geziyor.Geziyor) { g.GetRendered("https://httpbin.org/anything", g.Opt.ParseFunc) g.Head("https://httpbin.org/anything", g.Opt.ParseFunc) diff --git a/geziyor.go b/geziyor.go index c34526b..05dea22 100644 --- a/geziyor.go +++ b/geziyor.go @@ -25,10 +25,10 @@ type Exporter interface { // Geziyor is our main scraper type type Geziyor struct { - Opt Options + Opt *Options + Client *internal.Client Exports chan interface{} - client *http.Client wg sync.WaitGroup semGlobal chan struct{} semHosts struct { @@ -47,9 +47,9 @@ func init() { // NewGeziyor creates new Geziyor with default values. // If options provided, options -func NewGeziyor(opt Options) *Geziyor { +func NewGeziyor(opt *Options) *Geziyor { geziyor := &Geziyor{ - client: internal.NewClient(), + Client: internal.NewClient(opt.CookiesDisabled), Opt: opt, Exports: make(chan interface{}), requestMiddlewares: []RequestMiddleware{ @@ -69,11 +69,11 @@ func NewGeziyor(opt Options) *Geziyor { geziyor.Opt.MaxBodySize = 1024 * 1024 * 1024 // 1GB } if opt.Cache != nil { - geziyor.client.Transport = &httpcache.Transport{ - Transport: geziyor.client.Transport, Cache: opt.Cache, MarkCachedResponses: true} + geziyor.Client.Transport = &httpcache.Transport{ + Transport: geziyor.Client.Transport, Cache: opt.Cache, MarkCachedResponses: true} } if opt.Timeout != 0 { - geziyor.client.Timeout = opt.Timeout + geziyor.Client.Timeout = opt.Timeout } if opt.ConcurrentRequests != 0 { geziyor.semGlobal = make(chan struct{}, opt.ConcurrentRequests) @@ -212,7 +212,7 @@ func (g *Geziyor) doRequestClient(req *Request) (*Response, error) { log.Println("Fetching: ", req.URL.String()) // Do request - resp, err := g.client.Do(req.Request) + resp, err := g.Client.Do(req.Request) if resp != nil { defer resp.Body.Close() } diff --git a/geziyor_test.go b/geziyor_test.go index 7f34032..0d16456 100644 --- a/geziyor_test.go +++ b/geziyor_test.go @@ -13,7 +13,7 @@ import ( ) func TestSimple(t *testing.T) { - geziyor.NewGeziyor(geziyor.Options{ + geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://api.ipify.org"}, ParseFunc: func(g *geziyor.Geziyor, r *geziyor.Response) { fmt.Println(string(r.Body)) @@ -23,7 +23,7 @@ func TestSimple(t *testing.T) { func TestSimpleCache(t *testing.T) { defer leaktest.Check(t)() - geziyor.NewGeziyor(geziyor.Options{ + geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://api.ipify.org"}, Cache: httpcache.NewMemoryCache(), ParseFunc: func(g *geziyor.Geziyor, r *geziyor.Response) { @@ -36,7 +36,7 @@ func TestSimpleCache(t *testing.T) { func TestQuotes(t *testing.T) { defer leaktest.Check(t)() - geziyor.NewGeziyor(geziyor.Options{ + geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://quotes.toscrape.com/"}, ParseFunc: quotesParse, Exporters: []geziyor.Exporter{&exporter.JSONExporter{}}, @@ -65,7 +65,7 @@ func quotesParse(g *geziyor.Geziyor, r *geziyor.Response) { func TestAllLinks(t *testing.T) { defer leaktest.Check(t)() - geziyor.NewGeziyor(geziyor.Options{ + geziyor.NewGeziyor(&geziyor.Options{ AllowedDomains: []string{"books.toscrape.com"}, StartURLs: []string{"http://books.toscrape.com/"}, ParseFunc: func(g *geziyor.Geziyor, r *geziyor.Response) { @@ -90,7 +90,7 @@ func TestRandomDelay(t *testing.T) { } func TestStartRequestsFunc(t *testing.T) { - geziyor.NewGeziyor(geziyor.Options{ + geziyor.NewGeziyor(&geziyor.Options{ StartRequestsFunc: func(g *geziyor.Geziyor) { g.Get("http://quotes.toscrape.com/", g.Opt.ParseFunc) }, @@ -104,7 +104,7 @@ func TestStartRequestsFunc(t *testing.T) { } func TestGetRendered(t *testing.T) { - geziyor.NewGeziyor(geziyor.Options{ + geziyor.NewGeziyor(&geziyor.Options{ StartRequestsFunc: func(g *geziyor.Geziyor) { g.GetRendered("https://httpbin.org/anything", g.Opt.ParseFunc) }, @@ -116,7 +116,7 @@ func TestGetRendered(t *testing.T) { } func TestHEADRequest(t *testing.T) { - geziyor.NewGeziyor(geziyor.Options{ + geziyor.NewGeziyor(&geziyor.Options{ StartRequestsFunc: func(g *geziyor.Geziyor) { g.Head("https://httpbin.org/anything", g.Opt.ParseFunc) }, @@ -125,3 +125,24 @@ func TestHEADRequest(t *testing.T) { }, }).Start() } + +func TestCookies(t *testing.T) { + geziyor.NewGeziyor(&geziyor.Options{ + StartURLs: []string{"http://quotes.toscrape.com/login"}, + ParseFunc: func(g *geziyor.Geziyor, r *geziyor.Response) { + if len(g.Client.Cookies("http://quotes.toscrape.com/login")) == 0 { + t.Fatal("Cookies is Empty") + } + }, + }).Start() + + geziyor.NewGeziyor(&geziyor.Options{ + StartURLs: []string{"http://quotes.toscrape.com/login"}, + ParseFunc: func(g *geziyor.Geziyor, r *geziyor.Response) { + if len(g.Client.Cookies("http://quotes.toscrape.com/login")) != 0 { + t.Fatal("Cookies exist") + } + }, + CookiesDisabled: true, + }).Start() +} diff --git a/internal/http.go b/internal/http.go index 67bcc17..ee79bdd 100644 --- a/internal/http.go +++ b/internal/http.go @@ -1,14 +1,27 @@ package internal import ( + "errors" "net" "net/http" + "net/http/cookiejar" + "net/url" "time" ) +var ( + // ErrNoCookieJar is the error type for missing cookie jar + ErrNoCookieJar = errors.New("cookie jar is not available") +) + +// Client is a small wrapper around *http.Client to provide new methods. +type Client struct { + *http.Client +} + // NewClient creates http.Client with modified values for typical web scraper -func NewClient() *http.Client { - return &http.Client{ +func NewClient(cookiesDisabled bool) *Client { + client := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ @@ -24,6 +37,35 @@ func NewClient() *http.Client { }, Timeout: time.Second * 180, // Google's timeout } + if !cookiesDisabled { + client.Jar, _ = cookiejar.New(nil) + } + return &Client{Client: client} +} + +// SetCookies handles the receipt of the cookies in a reply for the given URL +func (c *Client) SetCookies(URL string, cookies []*http.Cookie) error { + if c.Jar == nil { + return ErrNoCookieJar + } + u, err := url.Parse(URL) + if err != nil { + return err + } + c.Jar.SetCookies(u, cookies) + return nil +} + +// Cookies returns the cookies to send in a request for the given URL. +func (c *Client) Cookies(URL string) []*http.Cookie { + if c.Jar == nil { + return nil + } + parsedURL, err := url.Parse(URL) + if err != nil { + return nil + } + return c.Jar.Cookies(parsedURL) } // SetDefaultHeader sets header if not exists before diff --git a/options.go b/options.go index efb3fc8..8546c1d 100644 --- a/options.go +++ b/options.go @@ -64,4 +64,7 @@ type Options struct { // Revisiting same URLs is disabled by default URLRevisitEnabled bool + + // If set true, cookies won't send. + CookiesDisabled bool }