diff --git a/README.md b/README.md index fc2d2a1..45ec3e1 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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { fmt.Println(string(r.Body)) }, }).Start() @@ -44,7 +44,7 @@ func main() { }).Start() } -func quotesParse(g *geziyor.Geziyor, r *geziyor.Response) { +func quotesParse(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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/geziyor.go b/geziyor.go index 65833b9..ff9f767 100644 --- a/geziyor.go +++ b/geziyor.go @@ -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 *Response)) { +func (g *Geziyor) Get(url string, callback func(g *Geziyor, r *http.Response)) { req, err := stdhttp.NewRequest("GET", url, nil) if err != nil { log.Printf("Request creating error %v\n", err) return } - g.Do(&Request{Request: req}, callback) + g.Do(&http.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 *Response)) { +func (g *Geziyor) GetRendered(url string, callback func(g *Geziyor, r *http.Response)) { req, err := stdhttp.NewRequest("GET", url, nil) if err != nil { log.Printf("Request creating error %v\n", err) return } - g.Do(&Request{Request: req, Rendered: true}, callback) + g.Do(&http.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 *Response)) { +func (g *Geziyor) Head(url string, callback func(g *Geziyor, r *http.Response)) { req, err := stdhttp.NewRequest("HEAD", url, nil) if err != nil { log.Printf("Request creating error %v\n", err) return } - g.Do(&Request{Request: req}, callback) + g.Do(&http.Request{Request: req}, callback) } // Do sends an HTTP request -func (g *Geziyor) Do(req *Request, callback func(g *Geziyor, r *Response)) { +func (g *Geziyor) Do(req *http.Request, callback func(g *Geziyor, r *http.Response)) { if req.Synchronized { g.do(req, callback) } else { @@ -191,7 +191,7 @@ func (g *Geziyor) Do(req *Request, callback func(g *Geziyor, r *Response)) { } // Do sends an HTTP request -func (g *Geziyor) do(req *Request, callback func(g *Geziyor, r *Response)) { +func (g *Geziyor) do(req *http.Request, callback func(g *Geziyor, r *http.Response)) { g.acquireSem(req) defer g.releaseSem(req) if !req.Synchronized { @@ -201,13 +201,13 @@ func (g *Geziyor) do(req *Request, callback func(g *Geziyor, r *Response)) { for _, middlewareFunc := range g.requestMiddlewares { middlewareFunc(g, req) - if req.cancelled { + if req.Cancelled { return } } // Do request normal or Chrome and read response - var response *Response + var response *http.Response var err error if !req.Rendered { response, err = g.doRequestClient(req) @@ -233,7 +233,7 @@ func (g *Geziyor) do(req *Request, callback func(g *Geziyor, r *Response)) { } } -func (g *Geziyor) doRequestClient(req *Request) (*Response, error) { +func (g *Geziyor) doRequestClient(req *http.Request) (*http.Response, error) { // Do request resp, err := g.Client.Do(req.Request) @@ -260,7 +260,7 @@ func (g *Geziyor) doRequestClient(req *Request) (*Response, error) { return nil, errors.Wrap(err, "Reading body error") } - response := Response{ + response := http.Response{ Response: resp, Body: body, Meta: req.Meta, @@ -270,7 +270,7 @@ func (g *Geziyor) doRequestClient(req *Request) (*Response, error) { return &response, nil } -func (g *Geziyor) doRequestChrome(req *Request) (*Response, error) { +func (g *Geziyor) doRequestChrome(req *http.Request) (*http.Response, error) { var body string var reqID network.RequestID var res *network.Response @@ -317,7 +317,7 @@ func (g *Geziyor) doRequestChrome(req *Request) (*Response, error) { // Set new URL in case of redirection req.URL, _ = url.Parse(res.URL) - response := Response{ + response := http.Response{ Response: &stdhttp.Response{ Request: req.Request, StatusCode: int(res.Status), @@ -331,7 +331,7 @@ func (g *Geziyor) doRequestChrome(req *Request) (*Response, error) { return &response, nil } -func (g *Geziyor) acquireSem(req *Request) { +func (g *Geziyor) acquireSem(req *http.Request) { if g.Opt.ConcurrentRequests != 0 { g.semGlobal <- struct{}{} } @@ -349,7 +349,7 @@ func (g *Geziyor) acquireSem(req *Request) { } } -func (g *Geziyor) releaseSem(req *Request) { +func (g *Geziyor) releaseSem(req *http.Request) { if g.Opt.ConcurrentRequests != 0 { <-g.semGlobal } diff --git a/geziyor_test.go b/geziyor_test.go index f1ec588..cea75e0 100644 --- a/geziyor_test.go +++ b/geziyor_test.go @@ -8,9 +8,9 @@ import ( "github.com/geziyor/geziyor" "github.com/geziyor/geziyor/exporter" "github.com/geziyor/geziyor/extractor" - http2 "github.com/geziyor/geziyor/http" + "github.com/geziyor/geziyor/http" "github.com/geziyor/geziyor/metrics" - "net/http" + httpstd "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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { +func quotesParse(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { fmt.Println(string(r.Body)) }, }).Start() @@ -127,8 +127,8 @@ 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 *geziyor.Response) { - if len(g.Client.Cookies("http://quotes.toscrape.com/login")) == 0 { + ParseFunc: func(g *geziyor.Geziyor, r *http.Response) { + if len(g.Client.Cookies(r.Request.URL.String())) == 0 { t.Fatal("Cookies is Empty") } }, @@ -136,8 +136,8 @@ 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 { + ParseFunc: func(g *geziyor.Geziyor, r *http.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, _ := geziyor.NewRequest("GET", "https://httpbin.org/anything", nil) + req, _ := http.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(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewServer(httpstd.HandlerFunc(func(w httpstd.ResponseWriter, r *httpstd.Request) { fmt.Fprint(w, "\xf0ültekin") })) defer ts.Close() geziyor.NewGeziyor(&geziyor.Options{ StartURLs: []string{ts.URL}, - ParseFunc: func(g *geziyor.Geziyor, r *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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 *geziyor.Response) { + ParseFunc: func(g *geziyor.Geziyor, r *http.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(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewServer(httpstd.HandlerFunc(func(w httpstd.ResponseWriter, r *httpstd.Request) { fmt.Fprint(w, "Hello, client") })) - ts.Client().Transport = http2.NewClient().Transport + ts.Client().Transport = http.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, _ := geziyor.NewRequest("GET", ts.URL, nil) + req, _ := http.NewRequest("GET", ts.URL, nil) req.Synchronized = true // We only bench here ! diff --git a/request.go b/http/request.go similarity index 91% rename from request.go rename to http/request.go index b3e873b..038835d 100644 --- a/request.go +++ b/http/request.go @@ -1,4 +1,4 @@ -package geziyor +package http import ( "io" @@ -11,13 +11,12 @@ type Request struct { Meta map[string]interface{} Synchronized bool Rendered bool - - cancelled bool + Cancelled bool } // Cancel request func (r *Request) Cancel() { - r.cancelled = true + r.Cancelled = true } // NewRequest returns a new Request given a method, URL, and optional body. diff --git a/response.go b/http/response.go similarity index 87% rename from response.go rename to http/response.go index a0b21ba..e1c3089 100644 --- a/response.go +++ b/http/response.go @@ -1,4 +1,4 @@ -package geziyor +package http import ( "github.com/PuerkitoBio/goquery" @@ -28,7 +28,8 @@ func (r *Response) JoinURL(relativeURL string) string { return joinedURL.String() } -func (r *Response) isHTML() bool { +// IsHTML checks if response content is HTML by looking to content-type header +func (r *Response) IsHTML() bool { contentType := r.Header.Get("Content-Type") for _, htmlContentType := range []string{"text/html", "application/xhtml+xml", "application/vnd.wap.xhtml+xml"} { if strings.Contains(contentType, htmlContentType) { diff --git a/middleware.go b/middleware.go index c5a25e9..b4641ec 100644 --- a/middleware.go +++ b/middleware.go @@ -16,10 +16,10 @@ import ( // RequestMiddleware called before requests made. // Set request.Cancelled = true to cancel request -type RequestMiddleware func(g *Geziyor, r *Request) +type RequestMiddleware func(g *Geziyor, r *http.Request) // ResponseMiddleware called after request response receive -type ResponseMiddleware func(g *Geziyor, r *Response) +type ResponseMiddleware func(g *Geziyor, r *http.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 *Request) { +func recoverMiddleware(g *Geziyor, r *http.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 *Request) { } // allowedDomainsMiddleware checks for request host if it exists in AllowedDomains -func allowedDomainsMiddleware(g *Geziyor, r *Request) { +func allowedDomainsMiddleware(g *Geziyor, r *http.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 *Request) { } // duplicateRequestsMiddleware checks for already visited URLs -func duplicateRequestsMiddleware(g *Geziyor, r *Request) { +func duplicateRequestsMiddleware(g *Geziyor, r *http.Request) { if !g.Opt.URLRevisitEnabled { key := r.Request.URL.String() + r.Request.Method if _, visited := g.visitedURLs.LoadOrStore(key, struct{}{}); visited { @@ -56,7 +56,7 @@ func duplicateRequestsMiddleware(g *Geziyor, r *Request) { } // defaultHeadersMiddleware sets default request headers -func defaultHeadersMiddleware(g *Geziyor, r *Request) { +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") @@ -64,7 +64,7 @@ func defaultHeadersMiddleware(g *Geziyor, r *Request) { } // delayMiddleware delays requests -func delayMiddleware(g *Geziyor, r *Request) { +func delayMiddleware(g *Geziyor, r *http.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 *Request) { } // logMiddleware logs requests -func logMiddleware(g *Geziyor, r *Request) { +func logMiddleware(g *Geziyor, r *http.Request) { log.Println("Fetching: ", r.URL.String()) } // metricsRequestMiddleware sets stats -func metricsRequestMiddleware(g *Geziyor, r *Request) { +func metricsRequestMiddleware(g *Geziyor, r *http.Request) { g.metrics.RequestCounter.With("method", r.Method).Add(1) } // parseHTMLMiddleware parses response if response is HTML -func parseHTMLMiddleware(g *Geziyor, r *Response) { - if !g.Opt.ParseHTMLDisabled && r.isHTML() { +func parseHTMLMiddleware(g *Geziyor, r *http.Response) { + if !g.Opt.ParseHTMLDisabled && r.IsHTML() { r.HTMLDoc, _ = goquery.NewDocumentFromReader(bytes.NewReader(r.Body)) } } // metricsResponseMiddleware sets stats -func metricsResponseMiddleware(g *Geziyor, r *Response) { +func metricsResponseMiddleware(g *Geziyor, r *http.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 *Response) { +func extractorsMiddleware(g *Geziyor, r *http.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 20c93d5..ded7440 100644 --- a/options.go +++ b/options.go @@ -2,6 +2,7 @@ package geziyor import ( "github.com/fpfeng/httpcache" + "github.com/geziyor/geziyor/http" "github.com/geziyor/geziyor/metrics" "time" ) @@ -19,7 +20,7 @@ type Options struct { StartRequestsFunc func(g *Geziyor) // ParseFunc is callback of StartURLs response. - ParseFunc func(g *Geziyor, r *Response) + ParseFunc func(g *Geziyor, r *http.Response) // Extractors extracts items from pages Extractors []Extractor