mirror of
https://github.com/karust/openserp.git
synced 2026-08-05 16:53:54 +08:00
fix default search limits
This commit is contained in:
@@ -124,7 +124,7 @@ Common parameters:
|
|||||||
| `date` | Date range | `20250101..20251231` |
|
| `date` | Date range | `20250101..20251231` |
|
||||||
| `file` | File extension | `pdf`, `doc`, `xls` |
|
| `file` | File extension | `pdf`, `doc`, `xls` |
|
||||||
| `site` | Site-specific search | `github.com` |
|
| `site` | Site-specific search | `github.com` |
|
||||||
| `limit` | Number of organic results, max 100. Ads may be returned in addition. | `10`, `25`, `50` |
|
| `limit` | Number of organic results, max 100. When omitted or `<=10`, only the first SERP page is parsed. | `25`, `50` |
|
||||||
| `start` | Pagination offset | `0`, `10`, `20` |
|
| `start` | Pagination offset | `0`, `10`, `20` |
|
||||||
| `format` | Output format | `json`, `markdown`, `text`, `ndjson` |
|
| `format` | Output format | `json`, `markdown`, `text`, `ndjson` |
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ Engine-specific parameters:
|
|||||||
| Parameter | Supported engines | Notes |
|
| Parameter | Supported engines | Notes |
|
||||||
| ---------- | ----------------- | ---------------------------------------------------------------------- |
|
| ---------- | ----------------- | ---------------------------------------------------------------------- |
|
||||||
| `filter` | `google` | Duplicate filter: `true` hides similar results, `false` includes them. |
|
| `filter` | `google` | Duplicate filter: `true` hides similar results, `false` includes them. |
|
||||||
| `features` | browser `Search` | Populate `serp_features[]` from the live page . |
|
| `features` | browser `Search` | Populate `serp_features[]` from the live page. Defaults to `true`. |
|
||||||
|
|
||||||
## Search Response Example
|
## Search Response Example
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func TestBuildURL(t *testing.T) {
|
|||||||
if got := params.Get("gpc"); got != "stf=1388534400,1672531200|stftype=2" {
|
if got := params.Get("gpc"); got != "stf=1388534400,1672531200|stftype=2" {
|
||||||
t.Fatalf("unexpected gpc value: %q", got)
|
t.Fatalf("unexpected gpc value: %q", got)
|
||||||
}
|
}
|
||||||
if got := params.Get("rn"); got != "10" {
|
if got := params.Get("rn"); got != "" {
|
||||||
t.Fatalf("unexpected rn value: %q", got)
|
t.Fatalf("unexpected rn value: %q", got)
|
||||||
}
|
}
|
||||||
if got := params.Get("pn"); got != "" {
|
if got := params.Get("pn"); got != "" {
|
||||||
@@ -140,6 +140,23 @@ func TestBuildImageURL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "small limit omits result count param",
|
||||||
|
query: core.Query{
|
||||||
|
Text: "golang",
|
||||||
|
Limit: 10,
|
||||||
|
},
|
||||||
|
pageNum: 2,
|
||||||
|
check: func(t *testing.T, params url.Values, _ string) {
|
||||||
|
t.Helper()
|
||||||
|
if got := params.Get("rn"); got != "" {
|
||||||
|
t.Fatalf("rn should be omitted when Limit<=10, got %q", got)
|
||||||
|
}
|
||||||
|
if got := params.Get("pn"); got != "60" {
|
||||||
|
t.Fatalf("unexpected pn value: %q", got)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "empty fields return error",
|
name: "empty fields return error",
|
||||||
query: core.Query{},
|
query: core.Query{},
|
||||||
|
|||||||
@@ -278,13 +278,13 @@ func (baid *Baidu) SearchImage(ctx context.Context, query core.Query) ([]core.Se
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for len(searchResults) < query.Limit {
|
for core.ShouldFetchResultPage(len(searchResults), query.Limit, searchPage) {
|
||||||
done, err := fetchPage()
|
done, err := fetchPage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
searchPage++
|
searchPage++
|
||||||
if done {
|
if done || !core.ShouldFetchResultPage(len(searchResults), query.Limit, searchPage) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func BuildURL(q core.Query) (string, error) {
|
|||||||
logrus.Warn("File search not supported")
|
logrus.Warn("File search not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
if q.Limit != 0 {
|
if q.Limit > 10 {
|
||||||
params.Add("rn", strconv.Itoa(q.Limit))
|
params.Add("rn", strconv.Itoa(q.Limit))
|
||||||
}
|
}
|
||||||
if q.Start < 0 {
|
if q.Start < 0 {
|
||||||
@@ -104,8 +104,10 @@ func BuildImageURL(q core.Query, pageNum int) (string, error) {
|
|||||||
return "", errors.New("Empty query built")
|
return "", errors.New("Empty query built")
|
||||||
}
|
}
|
||||||
|
|
||||||
if q.Limit != 0 {
|
if q.Limit > 10 {
|
||||||
params.Add("rn", "30") // Results per page
|
params.Add("rn", "30") // Results per page
|
||||||
|
}
|
||||||
|
if pageNum > 0 {
|
||||||
params.Add("pn", strconv.Itoa(pageNum*30)) // Offset
|
params.Add("pn", strconv.Itoa(pageNum*30)) // Offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ func TestBuildURL(t *testing.T) {
|
|||||||
if got := params.Get("filters"); got != `ex1:"ez5_20454_20570"` {
|
if got := params.Get("filters"); got != `ex1:"ez5_20454_20570"` {
|
||||||
t.Fatalf("unexpected filters value: %q", got)
|
t.Fatalf("unexpected filters value: %q", got)
|
||||||
}
|
}
|
||||||
|
if got := params.Get("count"); got != "" {
|
||||||
|
t.Fatalf("count should be omitted when Limit<=10, got %q", got)
|
||||||
|
}
|
||||||
// LangCode unset → no locale params; let Bing pick defaults
|
// LangCode unset → no locale params; let Bing pick defaults
|
||||||
// from the request rather than biasing toward en-US.
|
// from the request rather than biasing toward en-US.
|
||||||
for _, key := range []string{"mkt", "setlang", "cc"} {
|
for _, key := range []string{"mkt", "setlang", "cc"} {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func BuildURL(q core.Query) (string, error) {
|
|||||||
if q.Start > 0 {
|
if q.Start > 0 {
|
||||||
// Bing uses 1-based first-result index for pagination.
|
// Bing uses 1-based first-result index for pagination.
|
||||||
params.Add("first", strconv.Itoa(q.Start+1))
|
params.Add("first", strconv.Itoa(q.Start+1))
|
||||||
} else if q.Limit > 0 {
|
} else if q.Limit > 10 {
|
||||||
params.Add("count", strconv.Itoa(q.Limit))
|
params.Add("count", strconv.Itoa(q.Limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "0.7.15"
|
version = "0.7.16"
|
||||||
defaultConfigFilename = "config"
|
defaultConfigFilename = "config"
|
||||||
envPrefix = "OPENSERP"
|
envPrefix = "OPENSERP"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -171,6 +171,19 @@ func OrganicLimitReached(results []SearchResult, limit int) bool {
|
|||||||
return limit > 0 && CountOrganicResults(results) >= limit
|
return limit > 0 && CountOrganicResults(results) >= limit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldFetchResultPage reports whether a paginated engine should fetch another
|
||||||
|
// SERP page. Small/default limits should use the first SERP page as-is instead
|
||||||
|
// of chasing a target count across multiple page loads.
|
||||||
|
func ShouldFetchResultPage(collected, limit, pagesFetched int) bool {
|
||||||
|
if pagesFetched <= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if limit > 0 && collected >= limit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return limit > defaultQueryLimit
|
||||||
|
}
|
||||||
|
|
||||||
// LimitOrganicResults keeps all ads and at most limit non-ad results.
|
// LimitOrganicResults keeps all ads and at most limit non-ad results.
|
||||||
func LimitOrganicResults(results []SearchResult, limit int) []SearchResult {
|
func LimitOrganicResults(results []SearchResult, limit int) []SearchResult {
|
||||||
if limit <= 0 {
|
if limit <= 0 {
|
||||||
@@ -356,7 +369,7 @@ func (searchQuery *Query) InitFromContext(reqCtx *fiber.Ctx) error {
|
|||||||
return errInvalidParam(fmt.Sprintf("filter: %v", err))
|
return errInvalidParam(fmt.Sprintf("filter: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
searchQuery.Features, err = strconv.ParseBool(reqCtx.Query("features", "0"))
|
searchQuery.Features, err = strconv.ParseBool(reqCtx.Query("features", "1"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errInvalidParam(fmt.Sprintf("features: %v", err))
|
return errInvalidParam(fmt.Sprintf("features: %v", err))
|
||||||
}
|
}
|
||||||
|
|||||||
58
core/common_test.go
Normal file
58
core/common_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestShouldFetchResultPage(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
collected int
|
||||||
|
limit int
|
||||||
|
pagesFetched int
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "always fetch first page",
|
||||||
|
limit: 10,
|
||||||
|
pagesFetched: 0,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unset limit stops after first page",
|
||||||
|
collected: 8,
|
||||||
|
limit: 0,
|
||||||
|
pagesFetched: 1,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default limit stops after first page even if short",
|
||||||
|
collected: 8,
|
||||||
|
limit: 10,
|
||||||
|
pagesFetched: 1,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "larger limit can fetch another short page",
|
||||||
|
collected: 8,
|
||||||
|
limit: 11,
|
||||||
|
pagesFetched: 1,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "larger limit stops when satisfied",
|
||||||
|
collected: 11,
|
||||||
|
limit: 11,
|
||||||
|
pagesFetched: 1,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := ShouldFetchResultPage(tt.collected, tt.limit, tt.pagesFetched)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("ShouldFetchResultPage(%d, %d, %d) = %t, want %t",
|
||||||
|
tt.collected, tt.limit, tt.pagesFetched, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -341,6 +341,40 @@ func TestInvalidQueryParametersReturnJSONError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFeaturesDefaultEnabledAndCanBeDisabled(t *testing.T) {
|
||||||
|
var got []bool
|
||||||
|
engine := &engineMock{
|
||||||
|
name: "google",
|
||||||
|
initialized: true,
|
||||||
|
searchFn: func(_ context.Context, q Query) ([]SearchResult, error) {
|
||||||
|
got = append(got, q.Features)
|
||||||
|
return []SearchResult{{Rank: 1, URL: "https://example.com/google", Title: "google"}}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
opts := DefaultServerOptions()
|
||||||
|
opts.Resilience.Retry.MaxRetries = 0
|
||||||
|
srv := NewServerWithOptions("127.0.0.1", 7240, opts, engine)
|
||||||
|
|
||||||
|
resp := request(t, srv, "/google/search?text=golang")
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("expected default features request to succeed, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
resp = request(t, srv, "/google/search?text=golang&features=false")
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("expected features=false request to succeed, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(got) != 2 {
|
||||||
|
t.Fatalf("expected 2 engine calls, got %d", len(got))
|
||||||
|
}
|
||||||
|
if !got[0] {
|
||||||
|
t.Fatal("expected features to default to true")
|
||||||
|
}
|
||||||
|
if got[1] {
|
||||||
|
t.Fatal("expected features=false to disable feature extraction")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMegaEnginesEndpointResponseFormat(t *testing.T) {
|
func TestMegaEnginesEndpointResponseFormat(t *testing.T) {
|
||||||
google := &engineMock{name: "google", initialized: true}
|
google := &engineMock{name: "google", initialized: true}
|
||||||
yandex := &engineMock{name: "yandex", initialized: false}
|
yandex := &engineMock{name: "yandex", initialized: false}
|
||||||
|
|||||||
@@ -637,12 +637,12 @@ components:
|
|||||||
name: limit
|
name: limit
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: Maximum organic results to return (1–100). Ads may be returned in addition.
|
description: Maximum organic results to return (1-100). Ads may be returned in addition. Omitted or small limits (<=10) parse only the first SERP page; larger limits may paginate when an engine supports it.
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
minimum: 1
|
minimum: 1
|
||||||
maximum: 100
|
maximum: 100
|
||||||
default: 25
|
default: 10
|
||||||
example: 10
|
example: 10
|
||||||
StartQuery:
|
StartQuery:
|
||||||
name: start
|
name: start
|
||||||
@@ -672,7 +672,7 @@ components:
|
|||||||
supported by the engine.
|
supported by the engine.
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: true
|
||||||
EnginesQuery:
|
EnginesQuery:
|
||||||
name: engines
|
name: engines
|
||||||
in: query
|
in: query
|
||||||
|
|||||||
@@ -219,13 +219,13 @@ func (ddg *DuckDuckGo) Search(ctx context.Context, query core.Query) (results []
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for query.Limit <= 0 || core.CountOrganicResults(allResults) < query.Limit {
|
for core.ShouldFetchResultPage(core.CountOrganicResults(allResults), query.Limit, searchPage) {
|
||||||
done, err := fetchPage()
|
done, err := fetchPage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
searchPage++
|
searchPage++
|
||||||
if done || (query.Limit > 0 && core.CountOrganicResults(allResults) >= query.Limit) {
|
if done || !core.ShouldFetchResultPage(core.CountOrganicResults(allResults), query.Limit, searchPage) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := core.SleepContext(ctx, ddg.pageSleep); err != nil {
|
if err := core.SleepContext(ctx, ddg.pageSleep); err != nil {
|
||||||
|
|||||||
@@ -256,3 +256,68 @@ func TestDuckDuckGoLanguageMapping(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldFetchDuckDuckGoPage(t *testing.T) {
|
||||||
|
results := func(organic, ads int) []core.SearchResult {
|
||||||
|
out := make([]core.SearchResult, 0, organic+ads)
|
||||||
|
for i := 0; i < ads; i++ {
|
||||||
|
out = append(out, core.SearchResult{URL: "https://ad.example/" + string(rune('a'+i)), Ad: true})
|
||||||
|
}
|
||||||
|
for i := 0; i < organic; i++ {
|
||||||
|
out = append(out, core.SearchResult{URL: "https://example.com/" + string(rune('a'+i))})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
results []core.SearchResult
|
||||||
|
limit int
|
||||||
|
pagesFetched int
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "first page is always fetched",
|
||||||
|
limit: 10,
|
||||||
|
pagesFetched: 0,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default limit does not chase a short first page",
|
||||||
|
results: results(8, 2),
|
||||||
|
limit: 10,
|
||||||
|
pagesFetched: 1,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit larger limit can paginate",
|
||||||
|
results: results(8, 0),
|
||||||
|
limit: 11,
|
||||||
|
pagesFetched: 1,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "satisfied larger limit stops",
|
||||||
|
results: results(11, 0),
|
||||||
|
limit: 11,
|
||||||
|
pagesFetched: 1,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unset internal query stops after first page",
|
||||||
|
results: results(8, 0),
|
||||||
|
limit: 0,
|
||||||
|
pagesFetched: 1,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := core.ShouldFetchResultPage(core.CountOrganicResults(tt.results), tt.limit, tt.pagesFetched)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("ShouldFetchResultPage() = %t, want %t", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -203,16 +203,13 @@ func (e *Ecosia) Search(ctx context.Context, query core.Query) (results []core.S
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for query.Limit <= 0 || core.CountOrganicResults(all) < query.Limit {
|
for core.ShouldFetchResultPage(core.CountOrganicResults(all), query.Limit, pageNum-firstPage) {
|
||||||
done, err := fetchPage()
|
done, err := fetchPage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pageNum++
|
pageNum++
|
||||||
if done {
|
if done || !core.ShouldFetchResultPage(core.CountOrganicResults(all), query.Limit, pageNum-firstPage) {
|
||||||
break
|
|
||||||
}
|
|
||||||
if query.Limit > 0 && core.CountOrganicResults(all) >= query.Limit {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := core.SleepContext(ctx, e.pageSleep); err != nil {
|
if err := core.SleepContext(ctx, e.pageSleep); err != nil {
|
||||||
@@ -347,16 +344,13 @@ func (e *Ecosia) SearchImage(ctx context.Context, query core.Query) (results []c
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for query.Limit <= 0 || len(out) < query.Limit {
|
for core.ShouldFetchResultPage(len(out), query.Limit, pageNum) {
|
||||||
done, err := fetchPage()
|
done, err := fetchPage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pageNum++
|
pageNum++
|
||||||
if done {
|
if done || !core.ShouldFetchResultPage(len(out), query.Limit, pageNum) {
|
||||||
break
|
|
||||||
}
|
|
||||||
if query.Limit > 0 && len(out) >= query.Limit {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := core.SleepContext(ctx, e.pageSleep); err != nil {
|
if err := core.SleepContext(ctx, e.pageSleep); err != nil {
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ func (gogl *Google) SearchImage(ctx context.Context, query core.Query) ([]core.S
|
|||||||
// cells can hang on right-click. Cap iterations as a last-resort guard.
|
// cells can hang on right-click. Cap iterations as a last-resort guard.
|
||||||
const maxImagePasses = 20
|
const maxImagePasses = 20
|
||||||
stagnant := 0
|
stagnant := 0
|
||||||
for pass := 0; pass < maxImagePasses && len(searchResultsMap) < query.Limit; pass++ {
|
for pass := 0; pass < maxImagePasses && core.ShouldFetchResultPage(len(searchResultsMap), query.Limit, pass); pass++ {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return *core.ConvertSearchResultsMap(searchResultsMap), err
|
return *core.ConvertSearchResultsMap(searchResultsMap), err
|
||||||
}
|
}
|
||||||
@@ -526,7 +526,7 @@ func (gogl *Google) SearchImage(ctx context.Context, query core.Query) ([]core.S
|
|||||||
if err := r.Remove(); err != nil {
|
if err := r.Remove(); err != nil {
|
||||||
gogl.logger.Debug("Failed to remove parsed image element: %s", err)
|
gogl.logger.Debug("Failed to remove parsed image element: %s", err)
|
||||||
}
|
}
|
||||||
if len(searchResultsMap) >= query.Limit {
|
if query.Limit > 0 && len(searchResultsMap) >= query.Limit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,12 +72,16 @@ func TestBuildSearchURL(t *testing.T) {
|
|||||||
Text: "megadeth tickets",
|
Text: "megadeth tickets",
|
||||||
Filter: true,
|
Filter: true,
|
||||||
LangCode: "DE",
|
LangCode: "DE",
|
||||||
|
Limit: 10,
|
||||||
},
|
},
|
||||||
check: func(t *testing.T, params url.Values, host string) {
|
check: func(t *testing.T, params url.Values, host string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if host != "www.google.de" {
|
if host != "www.google.de" {
|
||||||
t.Fatalf("unexpected host: %s", host)
|
t.Fatalf("unexpected host: %s", host)
|
||||||
}
|
}
|
||||||
|
if got := params.Get("num"); got != "" {
|
||||||
|
t.Fatalf("num should be omitted when Limit<=10, got %q", got)
|
||||||
|
}
|
||||||
if got := params.Get("hl"); got != "de" {
|
if got := params.Get("hl"); got != "de" {
|
||||||
t.Fatalf("unexpected hl value: %q", got)
|
t.Fatalf("unexpected hl value: %q", got)
|
||||||
}
|
}
|
||||||
@@ -316,12 +320,16 @@ func TestBuildImageSearchURL(t *testing.T) {
|
|||||||
Text: "mountains",
|
Text: "mountains",
|
||||||
LangCode: "en",
|
LangCode: "en",
|
||||||
Region: "GB",
|
Region: "GB",
|
||||||
|
Limit: 10,
|
||||||
},
|
},
|
||||||
check: func(t *testing.T, params url.Values, host string) {
|
check: func(t *testing.T, params url.Values, host string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if host != "www.google.co.uk" {
|
if host != "www.google.co.uk" {
|
||||||
t.Fatalf("unexpected host: %s", host)
|
t.Fatalf("unexpected host: %s", host)
|
||||||
}
|
}
|
||||||
|
if got := params.Get("num"); got != "" {
|
||||||
|
t.Fatalf("num should be omitted when Limit<=10, got %q", got)
|
||||||
|
}
|
||||||
if got := params.Get("gl"); got != "gb" {
|
if got := params.Get("gl"); got != "gb" {
|
||||||
t.Fatalf("unexpected gl value: %q", got)
|
t.Fatalf("unexpected gl value: %q", got)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ func BuildURL(q core.Query) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Limit number of results
|
// Limit number of results
|
||||||
if q.Limit != 0 {
|
if q.Limit > 10 {
|
||||||
params.Add("num", strconv.Itoa(q.Limit))
|
params.Add("num", strconv.Itoa(q.Limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ func BuildImageURL(q core.Query) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Limit number of results
|
// Limit number of results
|
||||||
if q.Limit != 0 {
|
if q.Limit > 10 {
|
||||||
params.Add("num", strconv.Itoa(q.Limit))
|
params.Add("num", strconv.Itoa(q.Limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ func (yand *Yandex) Search(ctx context.Context, query core.Query) (results []cor
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for !core.OrganicLimitReached(allResults, query.Limit) {
|
for core.ShouldFetchResultPage(core.CountOrganicResults(allResults), query.Limit, searchPage-startPage) {
|
||||||
done, err := fetchPage()
|
done, err := fetchPage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Yandex commonly challenges rapid pagination, so a later page can
|
// Yandex commonly challenges rapid pagination, so a later page can
|
||||||
@@ -257,7 +257,7 @@ func (yand *Yandex) Search(ctx context.Context, query core.Query) (results []cor
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
searchPage++
|
searchPage++
|
||||||
if done || core.OrganicLimitReached(allResults, query.Limit) {
|
if done || !core.ShouldFetchResultPage(core.CountOrganicResults(allResults), query.Limit, searchPage-startPage) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := core.SleepContext(ctx, yand.pageSleep); err != nil {
|
if err := core.SleepContext(ctx, yand.pageSleep); err != nil {
|
||||||
@@ -281,7 +281,6 @@ func (yand *Yandex) SearchImage(ctx context.Context, query core.Query) ([]core.S
|
|||||||
yand.logger.Debug("Starting image search, query: %+v", query)
|
yand.logger.Debug("Starting image search, query: %+v", query)
|
||||||
|
|
||||||
searchResults := []core.SearchResult{}
|
searchResults := []core.SearchResult{}
|
||||||
allowPagination := query.Limit > 30
|
|
||||||
|
|
||||||
searchPage := 0
|
searchPage := 0
|
||||||
// fetchPage loads one image page and appends parsed results.
|
// fetchPage loads one image page and appends parsed results.
|
||||||
@@ -343,22 +342,19 @@ func (yand *Yandex) SearchImage(ctx context.Context, query core.Query) ([]core.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchResults = append(searchResults, res)
|
searchResults = append(searchResults, res)
|
||||||
}
|
if query.Limit > 0 && len(searchResults) >= query.Limit {
|
||||||
if len(searchResults) >= query.Limit {
|
return true, nil
|
||||||
return true, nil
|
}
|
||||||
}
|
|
||||||
if searchPage == 1 && !allowPagination {
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for len(searchResults) < query.Limit {
|
for core.ShouldFetchResultPage(len(searchResults), query.Limit, searchPage) {
|
||||||
done, err := fetchPage()
|
done, err := fetchPage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return searchResults, err
|
return searchResults, err
|
||||||
}
|
}
|
||||||
if done {
|
if done || !core.ShouldFetchResultPage(len(searchResults), query.Limit, searchPage) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user