mirror of
https://github.com/karust/openserp.git
synced 2026-08-12 20:03:29 +08:00
141 lines
3.7 KiB
Go
141 lines
3.7 KiB
Go
package ecosia
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
"github.com/karust/openserp/core"
|
|
)
|
|
|
|
func classifyEcosiaRawHTML(body []byte) error {
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.Contains(strings.ToLower(doc.Text()), "captcha") {
|
|
return core.ErrCaptcha
|
|
}
|
|
if doc.Find("[data-test-id='web-no-results']").Length() > 0 ||
|
|
(doc.Find(Selectors.Mainline).Length() > 0 &&
|
|
doc.Find(Selectors.Result).Length() == 0 &&
|
|
doc.Find(Selectors.Ad).Length() == 0) {
|
|
return core.ErrEmptyResult
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// imageResultParser parses an Ecosia image SERP HTML response into search
|
|
// results using goquery (no browser required).
|
|
func imageResultParser(response *http.Response) ([]core.SearchResult, error) {
|
|
doc, err := goquery.NewDocumentFromReader(response.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var (
|
|
results []core.SearchResult
|
|
rank = 1
|
|
)
|
|
doc.Find(Selectors.ImageResult).Each(func(_ int, s *goquery.Selection) {
|
|
href, ok := s.Find(Selectors.ImageLink).Attr("href")
|
|
if !ok || strings.TrimSpace(href) == "" {
|
|
return
|
|
}
|
|
title, _ := s.Find(Selectors.ImageLink).Find("img").Attr("alt")
|
|
title = strings.TrimSpace(title)
|
|
var (
|
|
source = strings.TrimSpace(s.Find(Selectors.ImageSource).Text())
|
|
dims = strings.TrimSpace(s.Find(Selectors.ImageDims).Text())
|
|
desc = source
|
|
)
|
|
if dims != "" {
|
|
if source != "" {
|
|
desc = source + " (" + dims + ")"
|
|
} else {
|
|
desc = dims
|
|
}
|
|
}
|
|
results = append(results, core.SearchResult{
|
|
Rank: rank,
|
|
URL: href,
|
|
Title: title,
|
|
Description: desc,
|
|
})
|
|
rank++
|
|
})
|
|
return results, nil
|
|
}
|
|
|
|
func Search(ctx context.Context, query core.Query) (results []core.SearchResult, err error) {
|
|
ctx = core.PrepareEngineContext(ctx, query, "ecosia", false)
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
err = core.RecoverEnginePanicWithContext(ctx, "ecosia", recovered, nil)
|
|
results = nil
|
|
}
|
|
}()
|
|
|
|
pageNum, startRank, err := startPage(query.Start)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ecosiaURL, err := BuildURL(query, pageNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
core.WithRequest(ctx).WithField("url", ecosiaURL).Debug(fmt.Sprintf("Ecosia URL built: %s", ecosiaURL))
|
|
|
|
res, err := core.RawSearchRequest(ctx, ecosiaURL, query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer core.DrainAndCloseResponse(res)
|
|
core.WithRequest(ctx).WithField("status_code", res.StatusCode).Debug(
|
|
fmt.Sprintf("Ecosia Raw response: code=%d", res.StatusCode),
|
|
)
|
|
|
|
body, err := core.ReadRawSearchBody(res)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
htmlStatus := classifyEcosiaRawHTML(body)
|
|
if htmlStatus != nil && !errors.Is(htmlStatus, core.ErrEmptyResult) {
|
|
return nil, htmlStatus
|
|
}
|
|
|
|
parsedResults, err := ParseHTML(bytes.NewReader(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(parsedResults) == 0 {
|
|
if errors.Is(htmlStatus, core.ErrEmptyResult) {
|
|
return []core.SearchResult{}, nil
|
|
}
|
|
return nil, fmt.Errorf("%w: ecosia raw search returned no parseable results", core.ErrParser)
|
|
}
|
|
|
|
// Ecosia paginates by page index, not result offset, so re-rank from
|
|
// the page boundary rather than query.Start (off-grid offsets round down).
|
|
// Skip ads so organic ranks stay sequential from startRank.
|
|
organicIdx := 0
|
|
for i := range parsedResults {
|
|
if parsedResults[i].Ad {
|
|
continue
|
|
}
|
|
parsedResults[i].Rank = startRank + organicIdx
|
|
organicIdx++
|
|
}
|
|
setSeparatedAdAbsoluteRanks(parsedResults, pageNum*10)
|
|
|
|
core.WithRequest(ctx).WithField("results_count", len(parsedResults)).Debug(
|
|
fmt.Sprintf("Ecosia Raw results : %v", parsedResults),
|
|
)
|
|
|
|
return core.DeduplicateResults(parsedResults), nil
|
|
}
|