From bd6466a5f2d6080e0c08d2cb58ae1c281b8bc17c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Musab=20G=C3=BCltekin?= Date: Sat, 29 Jun 2019 14:18:31 +0300 Subject: [PATCH] http package renamed to client to reduce cunfusion --- README.md | 12 +++---- http/http.go => client/client.go | 2 +- {http => client}/request.go | 2 +- {http => client}/response.go | 2 +- geziyor.go | 54 ++++++++++++++++---------------- geziyor_test.go | 36 ++++++++++----------- middleware.go | 34 ++++++++++---------- options.go | 4 +-- 8 files changed, 73 insertions(+), 73 deletions(-) rename http/http.go => client/client.go (99%) rename {http => client}/request.go (97%) rename {http => client}/response.go (98%) diff --git a/README.md b/README.md index 45ec3e1..4518466 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Simple usage ```go geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://api.ipify.org"}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { fmt.Println(string(r.Body)) }, }).Start() @@ -44,7 +44,7 @@ func main() { }).Start() } -func quotesParse(g *geziyor.Geziyor, r *http.Response) { +func quotesParse(g *geziyor.Geziyor, r *client.Response) { r.HTMLDoc.Find("div.quote").Each(func(i int, s *goquery.Selection) { g.Exports <- map[string]interface{}{ "text": s.Find("span.text").Text(), @@ -78,7 +78,7 @@ After reading response, ```ParseFunc func(g *Geziyor, r *Response)``` called. ```go geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://api.ipify.org"}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { fmt.Println(string(r.Body)) }, }).Start() @@ -95,7 +95,7 @@ geziyor.NewGeziyor(&geziyor.Options{ g.GetRendered("https://httpbin.org/anything", g.Opt.ParseFunc) g.Head("https://httpbin.org/anything", g.Opt.ParseFunc) }, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { fmt.Println(string(r.Body)) }, }).Start() @@ -130,7 +130,7 @@ If response isn't HTML, ```response.HTMLDoc``` would be ```nil```. ```go geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://quotes.toscrape.com/"}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { r.HTMLDoc.Find("div.quote").Each(func(_ int, s *goquery.Selection) { log.Println(s.Find("span.text").Text(), s.Find("small.author").Text()) }) @@ -146,7 +146,7 @@ You can export data automatically using exporters. Just send data to ```Geziyor. ```go geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://quotes.toscrape.com/"}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { r.HTMLDoc.Find("div.quote").Each(func(_ int, s *goquery.Selection) { g.Exports <- map[string]interface{}{ "text": s.Find("span.text").Text(), diff --git a/http/http.go b/client/client.go similarity index 99% rename from http/http.go rename to client/client.go index 24427dd..7ca5e62 100644 --- a/http/http.go +++ b/client/client.go @@ -1,4 +1,4 @@ -package http +package client import ( "errors" diff --git a/http/request.go b/client/request.go similarity index 97% rename from http/request.go rename to client/request.go index 038835d..6b3872d 100644 --- a/http/request.go +++ b/client/request.go @@ -1,4 +1,4 @@ -package http +package client import ( "io" diff --git a/http/response.go b/client/response.go similarity index 98% rename from http/response.go rename to client/response.go index e1c3089..aa09706 100644 --- a/http/response.go +++ b/client/response.go @@ -1,4 +1,4 @@ -package http +package client import ( "github.com/PuerkitoBio/goquery" diff --git a/geziyor.go b/geziyor.go index ff9f767..41b9062 100644 --- a/geziyor.go +++ b/geziyor.go @@ -7,7 +7,7 @@ import ( "github.com/chromedp/cdproto/network" "github.com/chromedp/chromedp" "github.com/fpfeng/httpcache" - "github.com/geziyor/geziyor/http" + "github.com/geziyor/geziyor/client" "github.com/geziyor/geziyor/metrics" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -15,7 +15,7 @@ import ( "io" "io/ioutil" "log" - stdhttp "net/http" + "net/http" "net/http/cookiejar" "net/url" "sync" @@ -36,7 +36,7 @@ type Exporter interface { // Geziyor is our main scraper type type Geziyor struct { Opt *Options - Client *http.Client + Client *client.Client Exports chan interface{} metrics *metrics.Metrics @@ -55,7 +55,7 @@ type Geziyor struct { // If options provided, options func NewGeziyor(opt *Options) *Geziyor { geziyor := &Geziyor{ - Client: http.NewClient(), + Client: client.NewClient(), Opt: opt, Exports: make(chan interface{}), requestMiddlewares: []RequestMiddleware{ @@ -114,10 +114,10 @@ func (g *Geziyor) Start() { // Metrics if g.Opt.MetricsType == metrics.Prometheus { - metricsServer := &stdhttp.Server{Addr: ":2112"} + metricsServer := &http.Server{Addr: ":2112"} defer metricsServer.Close() go func() { - stdhttp.Handle("/metrics", promhttp.Handler()) + http.Handle("/metrics", promhttp.Handler()) metricsServer.ListenAndServe() }() } @@ -149,39 +149,39 @@ func (g *Geziyor) Start() { } // Get issues a GET to the specified URL. -func (g *Geziyor) Get(url string, callback func(g *Geziyor, r *http.Response)) { - req, err := stdhttp.NewRequest("GET", url, nil) +func (g *Geziyor) Get(url string, callback func(g *Geziyor, r *client.Response)) { + req, err := http.NewRequest("GET", url, nil) if err != nil { log.Printf("Request creating error %v\n", err) return } - g.Do(&http.Request{Request: req}, callback) + g.Do(&client.Request{Request: req}, callback) } // GetRendered issues GET request using headless browser // Opens up a new Chrome instance, makes request, waits for 1 second to render HTML DOM and closed. // Rendered requests only supported for GET requests. -func (g *Geziyor) GetRendered(url string, callback func(g *Geziyor, r *http.Response)) { - req, err := stdhttp.NewRequest("GET", url, nil) +func (g *Geziyor) GetRendered(url string, callback func(g *Geziyor, r *client.Response)) { + req, err := http.NewRequest("GET", url, nil) if err != nil { log.Printf("Request creating error %v\n", err) return } - g.Do(&http.Request{Request: req, Rendered: true}, callback) + g.Do(&client.Request{Request: req, Rendered: true}, callback) } // Head issues a HEAD to the specified URL -func (g *Geziyor) Head(url string, callback func(g *Geziyor, r *http.Response)) { - req, err := stdhttp.NewRequest("HEAD", url, nil) +func (g *Geziyor) Head(url string, callback func(g *Geziyor, r *client.Response)) { + req, err := http.NewRequest("HEAD", url, nil) if err != nil { log.Printf("Request creating error %v\n", err) return } - g.Do(&http.Request{Request: req}, callback) + g.Do(&client.Request{Request: req}, callback) } // Do sends an HTTP request -func (g *Geziyor) Do(req *http.Request, callback func(g *Geziyor, r *http.Response)) { +func (g *Geziyor) Do(req *client.Request, callback func(g *Geziyor, r *client.Response)) { if req.Synchronized { g.do(req, callback) } else { @@ -191,7 +191,7 @@ func (g *Geziyor) Do(req *http.Request, callback func(g *Geziyor, r *http.Respon } // Do sends an HTTP request -func (g *Geziyor) do(req *http.Request, callback func(g *Geziyor, r *http.Response)) { +func (g *Geziyor) do(req *client.Request, callback func(g *Geziyor, r *client.Response)) { g.acquireSem(req) defer g.releaseSem(req) if !req.Synchronized { @@ -207,7 +207,7 @@ func (g *Geziyor) do(req *http.Request, callback func(g *Geziyor, r *http.Respon } // Do request normal or Chrome and read response - var response *http.Response + var response *client.Response var err error if !req.Rendered { response, err = g.doRequestClient(req) @@ -233,7 +233,7 @@ func (g *Geziyor) do(req *http.Request, callback func(g *Geziyor, r *http.Respon } } -func (g *Geziyor) doRequestClient(req *http.Request) (*http.Response, error) { +func (g *Geziyor) doRequestClient(req *client.Request) (*client.Response, error) { // Do request resp, err := g.Client.Do(req.Request) @@ -260,7 +260,7 @@ func (g *Geziyor) doRequestClient(req *http.Request) (*http.Response, error) { return nil, errors.Wrap(err, "Reading body error") } - response := http.Response{ + response := client.Response{ Response: resp, Body: body, Meta: req.Meta, @@ -270,7 +270,7 @@ func (g *Geziyor) doRequestClient(req *http.Request) (*http.Response, error) { return &response, nil } -func (g *Geziyor) doRequestChrome(req *http.Request) (*http.Response, error) { +func (g *Geziyor) doRequestChrome(req *client.Request) (*client.Response, error) { var body string var reqID network.RequestID var res *network.Response @@ -280,7 +280,7 @@ func (g *Geziyor) doRequestChrome(req *http.Request) (*http.Response, error) { if err := chromedp.Run(ctx, network.Enable(), - network.SetExtraHTTPHeaders(network.Headers(http.ConvertHeaderToMap(req.Header))), + network.SetExtraHTTPHeaders(network.Headers(client.ConvertHeaderToMap(req.Header))), chromedp.ActionFunc(func(ctx context.Context) error { chromedp.ListenTarget(ctx, func(ev interface{}) { switch ev.(type) { @@ -317,11 +317,11 @@ func (g *Geziyor) doRequestChrome(req *http.Request) (*http.Response, error) { // Set new URL in case of redirection req.URL, _ = url.Parse(res.URL) - response := http.Response{ - Response: &stdhttp.Response{ + response := client.Response{ + Response: &http.Response{ Request: req.Request, StatusCode: int(res.Status), - Header: http.ConvertMapToHeader(res.Headers), + Header: client.ConvertMapToHeader(res.Headers), }, Body: []byte(body), Meta: req.Meta, @@ -331,7 +331,7 @@ func (g *Geziyor) doRequestChrome(req *http.Request) (*http.Response, error) { return &response, nil } -func (g *Geziyor) acquireSem(req *http.Request) { +func (g *Geziyor) acquireSem(req *client.Request) { if g.Opt.ConcurrentRequests != 0 { g.semGlobal <- struct{}{} } @@ -349,7 +349,7 @@ func (g *Geziyor) acquireSem(req *http.Request) { } } -func (g *Geziyor) releaseSem(req *http.Request) { +func (g *Geziyor) releaseSem(req *client.Request) { if g.Opt.ConcurrentRequests != 0 { <-g.semGlobal } diff --git a/geziyor_test.go b/geziyor_test.go index cea75e0..1d8a166 100644 --- a/geziyor_test.go +++ b/geziyor_test.go @@ -6,11 +6,11 @@ import ( "github.com/fortytw2/leaktest" "github.com/fpfeng/httpcache" "github.com/geziyor/geziyor" + "github.com/geziyor/geziyor/client" "github.com/geziyor/geziyor/exporter" "github.com/geziyor/geziyor/extractor" - "github.com/geziyor/geziyor/http" "github.com/geziyor/geziyor/metrics" - httpstd "net/http" + "net/http" "net/http/httptest" "testing" "unicode/utf8" @@ -20,7 +20,7 @@ func TestSimple(t *testing.T) { defer leaktest.Check(t)() geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://api.ipify.org"}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { fmt.Println(string(r.Body)) }, }).Start() @@ -31,7 +31,7 @@ func TestSimpleCache(t *testing.T) { geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://api.ipify.org"}, Cache: httpcache.NewMemoryCache(), - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { fmt.Println(string(r.Body)) g.Exports <- string(r.Body) g.Get("http://api.ipify.org", nil) @@ -48,7 +48,7 @@ func TestQuotes(t *testing.T) { }).Start() } -func quotesParse(g *geziyor.Geziyor, r *http.Response) { +func quotesParse(g *geziyor.Geziyor, r *client.Response) { r.HTMLDoc.Find("div.quote").Each(func(i int, s *goquery.Selection) { // Export Data g.Exports <- map[string]interface{}{ @@ -73,7 +73,7 @@ func TestAllLinks(t *testing.T) { geziyor.NewGeziyor(&geziyor.Options{ AllowedDomains: []string{"books.toscrape.com"}, StartURLs: []string{"http://books.toscrape.com/"}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { g.Exports <- []string{r.Request.URL.String()} r.HTMLDoc.Find("a").Each(func(i int, s *goquery.Selection) { if href, ok := s.Attr("href"); ok { @@ -91,7 +91,7 @@ func TestStartRequestsFunc(t *testing.T) { StartRequestsFunc: func(g *geziyor.Geziyor) { g.Get("http://quotes.toscrape.com/", g.Opt.ParseFunc) }, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { r.HTMLDoc.Find("a").Each(func(_ int, s *goquery.Selection) { g.Exports <- s.AttrOr("href", "") }) @@ -105,7 +105,7 @@ func TestGetRendered(t *testing.T) { StartRequestsFunc: func(g *geziyor.Geziyor) { g.GetRendered("https://httpbin.org/anything", g.Opt.ParseFunc) }, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { fmt.Println(string(r.Body)) fmt.Println(r.Header) }, @@ -118,7 +118,7 @@ func TestHEADRequest(t *testing.T) { StartRequestsFunc: func(g *geziyor.Geziyor) { g.Head("https://httpbin.org/anything", g.Opt.ParseFunc) }, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { fmt.Println(string(r.Body)) }, }).Start() @@ -127,7 +127,7 @@ func TestHEADRequest(t *testing.T) { func TestCookies(t *testing.T) { geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://quotes.toscrape.com/login"}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { if len(g.Client.Cookies(r.Request.URL.String())) == 0 { t.Fatal("Cookies is Empty") } @@ -136,7 +136,7 @@ func TestCookies(t *testing.T) { geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{"http://quotes.toscrape.com/login"}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { if len(g.Client.Cookies(r.Request.URL.String())) != 0 { t.Fatal("Cookies exist") } @@ -148,7 +148,7 @@ func TestCookies(t *testing.T) { func TestBasicAuth(t *testing.T) { geziyor.NewGeziyor(&geziyor.Options{ StartRequestsFunc: func(g *geziyor.Geziyor) { - req, _ := http.NewRequest("GET", "https://httpbin.org/anything", nil) + req, _ := client.NewRequest("GET", "https://httpbin.org/anything", nil) req.SetBasicAuth("username", "password") g.Do(req, nil) }, @@ -170,14 +170,14 @@ func TestExtractor(t *testing.T) { } func TestCharsetDetection(t *testing.T) { - ts := httptest.NewServer(httpstd.HandlerFunc(func(w httpstd.ResponseWriter, r *httpstd.Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "\xf0ültekin") })) defer ts.Close() geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{ts.URL}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { if !utf8.Valid(r.Body) { t.Fatal() } @@ -187,7 +187,7 @@ func TestCharsetDetection(t *testing.T) { geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{ts.URL}, - ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *client.Response) { if utf8.Valid(r.Body) { t.Fatal() } @@ -200,10 +200,10 @@ func TestCharsetDetection(t *testing.T) { func BenchmarkGeziyor_Do(b *testing.B) { // Create Server - ts := httptest.NewServer(httpstd.HandlerFunc(func(w httpstd.ResponseWriter, r *httpstd.Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, client") })) - ts.Client().Transport = http.NewClient().Transport + ts.Client().Transport = client.NewClient().Transport defer ts.Close() // As we don't benchmark creating a server, reset timer. @@ -212,7 +212,7 @@ func BenchmarkGeziyor_Do(b *testing.B) { geziyor.NewGeziyor(&geziyor.Options{ StartRequestsFunc: func(g *geziyor.Geziyor) { // Create Synchronized request to benchmark requests accurately. - req, _ := http.NewRequest("GET", ts.URL, nil) + req, _ := client.NewRequest("GET", ts.URL, nil) req.Synchronized = true // We only bench here ! diff --git a/middleware.go b/middleware.go index b4641ec..4c8832d 100644 --- a/middleware.go +++ b/middleware.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" "github.com/PuerkitoBio/goquery" - "github.com/geziyor/geziyor/http" + "github.com/geziyor/geziyor/client" "github.com/geziyor/geziyor/internal" "log" "math/rand" @@ -16,10 +16,10 @@ import ( // RequestMiddleware called before requests made. // Set request.Cancelled = true to cancel request -type RequestMiddleware func(g *Geziyor, r *http.Request) +type RequestMiddleware func(g *Geziyor, r *client.Request) // ResponseMiddleware called after request response receive -type ResponseMiddleware func(g *Geziyor, r *http.Response) +type ResponseMiddleware func(g *Geziyor, r *client.Response) func init() { log.SetOutput(os.Stdout) @@ -28,7 +28,7 @@ func init() { // recoverMiddleware recovers scraping being crashed. // Logs error and stack trace -func recoverMiddleware(g *Geziyor, r *http.Request) { +func recoverMiddleware(g *Geziyor, r *client.Request) { if r := recover(); r != nil { log.Println(r, string(debug.Stack())) g.metrics.PanicCounter.Add(1) @@ -36,7 +36,7 @@ func recoverMiddleware(g *Geziyor, r *http.Request) { } // allowedDomainsMiddleware checks for request host if it exists in AllowedDomains -func allowedDomainsMiddleware(g *Geziyor, r *http.Request) { +func allowedDomainsMiddleware(g *Geziyor, r *client.Request) { if len(g.Opt.AllowedDomains) != 0 && !internal.Contains(g.Opt.AllowedDomains, r.Host) { //log.Printf("Domain not allowed: %s\n", req.Host) r.Cancel() @@ -45,7 +45,7 @@ func allowedDomainsMiddleware(g *Geziyor, r *http.Request) { } // duplicateRequestsMiddleware checks for already visited URLs -func duplicateRequestsMiddleware(g *Geziyor, r *http.Request) { +func duplicateRequestsMiddleware(g *Geziyor, r *client.Request) { if !g.Opt.URLRevisitEnabled { key := r.Request.URL.String() + r.Request.Method if _, visited := g.visitedURLs.LoadOrStore(key, struct{}{}); visited { @@ -56,15 +56,15 @@ func duplicateRequestsMiddleware(g *Geziyor, r *http.Request) { } // defaultHeadersMiddleware sets default request headers -func defaultHeadersMiddleware(g *Geziyor, r *http.Request) { - r.Header = http.SetDefaultHeader(r.Header, "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") - r.Header = http.SetDefaultHeader(r.Header, "Accept-Charset", "utf-8") - r.Header = http.SetDefaultHeader(r.Header, "Accept-Language", "en") - r.Header = http.SetDefaultHeader(r.Header, "User-Agent", g.Opt.UserAgent) +func defaultHeadersMiddleware(g *Geziyor, r *client.Request) { + r.Header = client.SetDefaultHeader(r.Header, "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + r.Header = client.SetDefaultHeader(r.Header, "Accept-Charset", "utf-8") + r.Header = client.SetDefaultHeader(r.Header, "Accept-Language", "en") + r.Header = client.SetDefaultHeader(r.Header, "User-Agent", g.Opt.UserAgent) } // delayMiddleware delays requests -func delayMiddleware(g *Geziyor, r *http.Request) { +func delayMiddleware(g *Geziyor, r *client.Request) { if g.Opt.RequestDelayRandomize { min := float64(g.Opt.RequestDelay) * 0.5 max := float64(g.Opt.RequestDelay) * 1.5 @@ -75,29 +75,29 @@ func delayMiddleware(g *Geziyor, r *http.Request) { } // logMiddleware logs requests -func logMiddleware(g *Geziyor, r *http.Request) { +func logMiddleware(g *Geziyor, r *client.Request) { log.Println("Fetching: ", r.URL.String()) } // metricsRequestMiddleware sets stats -func metricsRequestMiddleware(g *Geziyor, r *http.Request) { +func metricsRequestMiddleware(g *Geziyor, r *client.Request) { g.metrics.RequestCounter.With("method", r.Method).Add(1) } // parseHTMLMiddleware parses response if response is HTML -func parseHTMLMiddleware(g *Geziyor, r *http.Response) { +func parseHTMLMiddleware(g *Geziyor, r *client.Response) { if !g.Opt.ParseHTMLDisabled && r.IsHTML() { r.HTMLDoc, _ = goquery.NewDocumentFromReader(bytes.NewReader(r.Body)) } } // metricsResponseMiddleware sets stats -func metricsResponseMiddleware(g *Geziyor, r *http.Response) { +func metricsResponseMiddleware(g *Geziyor, r *client.Response) { g.metrics.ResponseCounter.With("method", r.Request.Method).Add(1) } // extractorsMiddleware extracts data from loaders conf and exports it to exporters -func extractorsMiddleware(g *Geziyor, r *http.Response) { +func extractorsMiddleware(g *Geziyor, r *client.Response) { // Check if we have extractors and exporters if len(g.Opt.Extractors) != 0 && len(g.Opt.Exporters) != 0 { diff --git a/options.go b/options.go index ded7440..432bc6f 100644 --- a/options.go +++ b/options.go @@ -2,7 +2,7 @@ package geziyor import ( "github.com/fpfeng/httpcache" - "github.com/geziyor/geziyor/http" + "github.com/geziyor/geziyor/client" "github.com/geziyor/geziyor/metrics" "time" ) @@ -20,7 +20,7 @@ type Options struct { StartRequestsFunc func(g *Geziyor) // ParseFunc is callback of StartURLs response. - ParseFunc func(g *Geziyor, r *http.Response) + ParseFunc func(g *Geziyor, r *client.Response) // Extractors extracts items from pages Extractors []Extractor