3 Commits
v0.3 ... 0.3.1

Author SHA1 Message Date
Pachakutiq
205f722ddc fix: Fix search crash (slice bounds out of range [4:1]) 2024-12-01 23:35:11 +08:00
Rustem Kamalov
aeb0616249 Fix google images selector + baidu images ads tag 2024-05-12 02:57:39 +03:00
Rustem Kamalov
fc49deba67 Fix yandex image search 2024-05-12 01:41:53 +03:00
5 changed files with 107 additions and 120 deletions

View File

@@ -24,19 +24,11 @@ type imageDataJson struct {
Height int Height int
Width int Width int
IsCopyright int IsCopyright int
AdType string `json:"adType"`
URL []struct { URL []struct {
SourcePage string `json:"FromURL"` SourcePage string `json:"FromURL"`
Original string `json:"ObjURL"` Original string `json:"ObjURL"`
} `json:"replaceUrl"` } `json:"replaceUrl"`
// Versions []struct {
// Height int
// Width int
// ImgSourcePage string `json:"fromURL"`
// URL string `json:"objURL"`
// Type string
// } `json:"setList"`
} }
} }
@@ -63,18 +55,12 @@ func (baid *Baidu) GetRateLimiter() *rate.Limiter {
func (baid *Baidu) isCaptcha(page *rod.Page) bool { func (baid *Baidu) isCaptcha(page *rod.Page) bool {
_, err := page.Timeout(baid.GetSelectorTimeout()).Search("div.passMod_dialog-body") _, err := page.Timeout(baid.GetSelectorTimeout()).Search("div.passMod_dialog-body")
if err != nil { return err == nil
return false
}
return true
} }
func (baid *Baidu) isTimeout(page *rod.Page) bool { func (baid *Baidu) isTimeout(page *rod.Page) bool {
_, err := page.Timeout(baid.GetSelectorTimeout()).Search("button.timeout-button") _, err := page.Timeout(baid.GetSelectorTimeout()).Search("button.timeout-button")
if err != nil { return err == nil
return false
}
return true
} }
func (baid *Baidu) Search(query core.Query) ([]core.SearchResult, error) { func (baid *Baidu) Search(query core.Query) ([]core.SearchResult, error) {
@@ -226,7 +212,15 @@ func (baid *Baidu) SearchImage(query core.Query) ([]core.SearchResult, error) {
Rank: (searchPage * 30) + (i + 1), Rank: (searchPage * 30) + (i + 1),
URL: img.URL[0].Original, URL: img.URL[0].Original,
Title: img.Title, Title: img.Title,
Description: fmt.Sprintf("%v,%v,%vx%x,copyright:%v", img.PictureDate, img.Type, img.Height, img.Width, img.IsCopyright)} Description: fmt.Sprintf("%v,%v,%vx%x,copyright:%v", img.PictureDate, img.Type, img.Height, img.Width, img.IsCopyright),
Ad: func() bool {
if img.AdType != "0" {
return true
} else {
return false
}
}(),
}
searchResults = append(searchResults, res) searchResults = append(searchResults, res)
} }

View File

@@ -293,7 +293,7 @@ func (gogl *Google) Search(query core.Query) ([]core.SearchResult, error) {
// Get description // Get description
text := resEl.MustText() text := resEl.MustText()
textSliced := strings.Split(text, "\n") textSliced := strings.Split(text, "\n")
srchRes.Description = strings.Join(textSliced[4:], "\n") srchRes.Description = strings.Join(textSliced[:], "\n")
} else { } else {
//fmt.Println(i, attrs) //fmt.Println(i, attrs)
@@ -319,21 +319,12 @@ func (gogl *Google) SearchImage(query core.Query) ([]core.SearchResult, error) {
page := gogl.Navigate(url) page := gogl.Navigate(url)
defer gogl.close(page) defer gogl.close(page)
//// TODO: Case with cookie accept (appears with VPN)
// if page.MustInfo().URL != url {
// results, _ := page.Search("button[aria-label][jsaction]")
// if results != nil {
// //buttons, _ := results.All()
// //buttons[1].Click(proto.InputMouseButtonLeft, 1)
// }
// }
for len(searchResultsMap) < query.Limit { for len(searchResultsMap) < query.Limit {
page.WaitLoad() page.WaitLoad()
page.Mouse.Scroll(0, 1000000, 1) page.Mouse.Scroll(0, 1000000, 1)
page.WaitLoad() page.WaitLoad()
results, err := page.Timeout(gogl.Timeout).Search("div[data-hveid][data-ved][jsaction]") results, err := page.Timeout(gogl.Timeout).Search("div[data-hveid][data-ved][jsaction][jsdata]")
if err != nil { if err != nil {
logrus.Errorf("Cannot parse search results: %s", err) logrus.Errorf("Cannot parse search results: %s", err)
return *core.ConvertSearchResultsMap(searchResultsMap), core.ErrSearchTimeout return *core.ConvertSearchResultsMap(searchResultsMap), core.ErrSearchTimeout
@@ -365,18 +356,19 @@ func (gogl *Google) SearchImage(query core.Query) ([]core.SearchResult, error) {
continue continue
} }
dataID, err := r.Attribute("data-id") dataVed, err := r.Attribute("data-ved")
if err != nil { if err != nil {
logrus.Error("Cannot find `data-ved` attr")
continue continue
} }
// If already have image with this ID // If already have image with this ID
if _, ok := searchResultsMap[*dataID]; ok { if _, ok := searchResultsMap[*dataVed]; ok {
continue continue
} }
// Get URLs // Get URLs
link, err := r.Element("a[tabindex][role]") link, err := r.Element("a:not([ping])")
if err != nil { if err != nil {
continue continue
} }
@@ -411,7 +403,7 @@ func (gogl *Google) SearchImage(query core.Query) ([]core.SearchResult, error) {
Title: title, Title: title,
Description: fmt.Sprintf("Height:%v, Width:%v, Source Page: %v", imgSrc.Height, imgSrc.Width, imgSrc.PageURL), Description: fmt.Sprintf("Height:%v, Width:%v, Source Page: %v", imgSrc.Height, imgSrc.Width, imgSrc.PageURL),
} }
searchResultsMap[*dataID] = gR searchResultsMap[*dataVed] = gR
r.Remove() r.Remove()
} }

View File

@@ -2,28 +2,36 @@ package yandex
import ( import (
"encoding/json" "encoding/json"
"fmt"
"sort"
"time" "time"
"github.com/go-rod/rod" "github.com/go-rod/rod"
"github.com/go-rod/rod/lib/input"
"github.com/karust/openserp/core" "github.com/karust/openserp/core"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )
type YandexImageData struct { type ImageEntity struct {
SerpItem struct { ID string `json:"id"`
Freshness string Rank int `json:"pos"`
Snippet struct { Width int `json:"origWidth"`
Title string Height int `json:"origHeight"`
Text string Title string `json:"alt"`
URL string OrigURL string `json:"origUrl"`
Domain string ThumbURL string `json:"image"`
ShopScore int Freshness string `json:"freshnessCounter"`
} IsGIF bool `json:"gifLabel"`
ImgHref string `json:"img_href"` }
Pos int
} `json:"serp-item"` type ImageData struct {
InitalState struct {
SerpList struct {
Items struct {
Entities map[string]ImageEntity `json:"entities"`
} `json:"items"`
} `json:"serpList"`
} `json:"initialState"`
} }
type Yandex struct { type Yandex struct {
@@ -52,27 +60,13 @@ func (yand *Yandex) GetRateLimiter() *rate.Limiter {
func (yand *Yandex) isCaptcha(page *rod.Page) bool { func (yand *Yandex) isCaptcha(page *rod.Page) bool {
_, err := page.Timeout(yand.GetSelectorTimeout()).Search("form#checkbox-captcha-form") _, err := page.Timeout(yand.GetSelectorTimeout()).Search("form#checkbox-captcha-form")
if err != nil { return err == nil
return false
}
return true
} }
// Check if nothig is found // Check if nothig is found
func (yand *Yandex) isNoResults(page *rod.Page) bool { func (yand *Yandex) isNoResults(page *rod.Page) bool {
noResFound := false _, err := page.Timeout(yand.GetSelectorTimeout()).Search("div.Correction.SearchCorrection")
return err == nil
_, err := page.Timeout(yand.GetSelectorTimeout()).Search("div.EmptySearchResults-Title")
if err == nil {
noResFound = true
}
_, err = page.Timeout(yand.GetSelectorTimeout()).Search("div>div.RequestMeta-Message")
if err == nil {
noResFound = true
}
return noResFound
} }
func (yand *Yandex) parseResults(results rod.Elements, pageNum int) []core.SearchResult { func (yand *Yandex) parseResults(results rod.Elements, pageNum int) []core.SearchResult {
@@ -181,25 +175,27 @@ func (yand *Yandex) Search(query core.Query) ([]core.SearchResult, error) {
func (yand *Yandex) SearchImage(query core.Query) ([]core.SearchResult, error) { func (yand *Yandex) SearchImage(query core.Query) ([]core.SearchResult, error) {
logrus.Tracef("Start Yandex image search, query: %+v", query) logrus.Tracef("Start Yandex image search, query: %+v", query)
searchResultsMap := map[string]core.SearchResult{} searchResults := []core.SearchResult{}
url, err := BuildImageURL(query)
if err != nil {
return nil, err
}
page := yand.Navigate(url) searchPage := 0
if !yand.LeavePageOpen { for len(searchResults) < query.Limit {
defer page.Close() url, err := BuildImageURL(query, searchPage)
} if err != nil {
return nil, err
}
searchPage += 1
for len(searchResultsMap) < query.Limit { page := yand.Navigate(url)
page.WaitLoad()
page.Keyboard.Press(input.End)
page.WaitLoad()
time.Sleep(time.Duration(time.Second * 2))
// Get all search results in page if !yand.LeavePageOpen {
results, err := page.Timeout(yand.Timeout).Search("div.serp-item") defer page.Close()
}
//page.Keyboard.Press(input.End)
//page.WaitLoad()
//time.Sleep(time.Duration(time.Second * 2))
results, err := page.Timeout(yand.Timeout).Search("div[role='main'] div[data-state]")
if err != nil { if err != nil {
logrus.Errorf("Cannot find search results: %s", err) logrus.Errorf("Cannot find search results: %s", err)
} }
@@ -208,45 +204,33 @@ func (yand *Yandex) SearchImage(query core.Query) ([]core.SearchResult, error) {
if results == nil { if results == nil {
if yand.isCaptcha(page) { if yand.isCaptcha(page) {
logrus.Errorf("Yandex captcha occurred during: %s", url) logrus.Errorf("Yandex captcha occurred during: %s", url)
return *core.ConvertSearchResultsMap(searchResultsMap), core.ErrCaptcha return searchResults, core.ErrCaptcha
} else if yand.isNoResults(page) { } else if yand.isNoResults(page) {
logrus.Errorf("No results found") logrus.Errorf("No results found")
} }
return *core.ConvertSearchResultsMap(searchResultsMap), core.ErrSearchTimeout return searchResults, core.ErrSearchTimeout
} }
for i := 0; i < results.ResultCount; i++ { data, err := results.First.Attribute("data-state")
r, err := results.Get(i, 1) if err != nil {
if err != nil { return nil, err
logrus.Errorf("Cannot [%v] element from search result, [%v total]: %s", i, results.ResultCount, err) }
return *core.ConvertSearchResultsMap(searchResultsMap), err
var imgData ImageData
if err := json.Unmarshal([]byte(*data), &imgData); err != nil {
return nil, err
}
for id := range imgData.InitalState.SerpList.Items.Entities {
img := imgData.InitalState.SerpList.Items.Entities[id]
res := core.SearchResult{
Rank: img.Rank + 1,
URL: img.OrigURL,
Title: img.Title,
Description: fmt.Sprintf("%dx%d, freshness:%s, thumb_url:%s", img.Height, img.Width, img.Freshness, img.ThumbURL),
} }
dataAttr, err := r[0].Attribute("data-bem") searchResults = append(searchResults, res)
if err != nil {
continue
}
var data YandexImageData
err = json.Unmarshal([]byte(*dataAttr), &data)
if err != nil {
logrus.Errorf("Cannot unmarshal yandex image data: %v\nData: %v", err, *dataAttr)
continue
}
linkText := data.SerpItem.ImgHref
title := data.SerpItem.Snippet.Title
description := data.SerpItem.Snippet.Text
yR := core.SearchResult{
Rank: (i + 1),
URL: linkText,
Title: title,
Description: description,
}
searchResultsMap[linkText+title] = yR
} }
if !yand.LeavePageOpen { if !yand.LeavePageOpen {
@@ -254,5 +238,9 @@ func (yand *Yandex) SearchImage(query core.Query) ([]core.SearchResult, error) {
} }
} }
return *core.ConvertSearchResultsMap(searchResultsMap), nil sort.Slice(searchResults, func(i, j int) bool {
return searchResults[i].Rank < searchResults[j].Rank
})
return searchResults, nil
} }

View File

@@ -14,6 +14,19 @@ func init() {
browser, _ = core.NewBrowser(opts) browser, _ = core.NewBrowser(opts)
} }
// func TestParseImgData(t *testing.T) {
// jsonData, _ := os.ReadFile("./testImgData.json")
// var obj ImageData
// if err := json.Unmarshal(jsonData, &obj); err != nil {
// t.Fatal(err)
// }
// if (len(obj.InitalState.SerpList.Items.Entities)) != 30 {
// t.Fail()
// }
// }
func TestSearchYandex(t *testing.T) { func TestSearchYandex(t *testing.T) {
yand := New(*browser, core.SearchEngineOptions{}) yand := New(*browser, core.SearchEngineOptions{})
@@ -32,13 +45,13 @@ func TestSearchYandex(t *testing.T) {
func TestImageYandex(t *testing.T) { func TestImageYandex(t *testing.T) {
yand := New(*browser, core.SearchEngineOptions{}) yand := New(*browser, core.SearchEngineOptions{})
query := core.Query{Text: "furry tiger", Limit: 90} query := core.Query{Text: "furry tiger", Limit: 30}
results, err := yand.SearchImage(query) results, err := yand.SearchImage(query)
if err != nil { if err != nil {
t.Fatalf("Cannot [ImageYandex]: %s", err) t.Fatalf("Cannot [ImageYandex]: %s", err)
} }
if len(results) < 90 { if len(results) < 30 {
t.Fatalf("[ImageYandex] returned empty result") t.Fatalf("[ImageYandex] returned empty result")
} }
} }

View File

@@ -35,14 +35,14 @@ func BuildURL(q core.Query, page int) (string, error) {
} }
if len(params.Get("text")) == 0 { if len(params.Get("text")) == 0 {
return "", errors.New("Empty query built") return "", errors.New("empty query built")
} }
base.RawQuery = params.Encode() base.RawQuery = params.Encode()
return base.String(), nil return base.String(), nil
} }
func BuildImageURL(q core.Query) (string, error) { func BuildImageURL(q core.Query, page int) (string, error) {
// TODO: Add other parameters // TODO: Add other parameters
base, _ := url.Parse(baseURL) base, _ := url.Parse(baseURL)
base.Path += "images/search/" base.Path += "images/search/"
@@ -56,11 +56,11 @@ func BuildImageURL(q core.Query) (string, error) {
} }
params.Add("text", text) params.Add("text", text)
//params.Add("p", fmt.Sprint(page)) params.Add("p", fmt.Sprint(page))
} }
if len(params.Get("text")) == 0 { if len(params.Get("text")) == 0 {
return "", errors.New("Empty query built") return "", errors.New("empty query built")
} }
if q.Site != "" { if q.Site != "" {