Archived
1
0
Fork 0
This repository has been archived on 2024-12-18. You can view files and clone it, but cannot push or open issues or pull requests.
restockbot/parser_amazon.go
Julien Riou ab5abcd171
Select or create shop before parsing
A shop map was created to group URLs by shops and process them in order. Now
that we have Amazon and each URL can be parsed independently, there is no need
to group them anymore. Moreover, shops were passed as an argument to the
handleProducts function. Shop name can be deduced by the parser itself. The
parser has a reference to the database. The parser now select or create the shop
before parsing products.

Signed-off-by: Julien Riou <julien@riou.xyz>
2021-04-01 17:50:50 +02:00

158 lines
4.6 KiB
Go

package main
import (
"fmt"
"regexp"
"strings"
log "github.com/sirupsen/logrus"
paapi5 "github.com/spiegel-im-spiegel/pa-api"
"github.com/spiegel-im-spiegel/pa-api/entity"
"github.com/spiegel-im-spiegel/pa-api/query"
)
// NewAmazonServer creates an Amazon Server function based on the Marketplace.
// The paapi5 marketplaceEnum is not exported, so this type cannot be used in simple map.
func NewAmazonServer(marketplace string) *paapi5.Server {
switch marketplace {
case "www.amazon.fr":
return paapi5.New(paapi5.WithMarketplace(paapi5.LocaleFrance))
case "www.amazon.com":
return paapi5.New(paapi5.WithMarketplace(paapi5.LocaleUnitedStates))
default:
return paapi5.New() // default Marketplace
}
}
// Map of messages to detect availability
var availabilityMessages = []string{"En stock."}
// AmazonParser structure to handle Amazon parsing logic
type AmazonParser struct {
client paapi5.Client
searches []string
includeRegex *regexp.Regexp
excludeRegex *regexp.Regexp
amazonFulfilled bool
amazonMerchant bool
affiliateLinks bool
}
// NewAmazonParser to create a new AmazonParser instance
func NewAmazonParser(marketplace string, partnerTag string, accessKey string, secretKey string, searches []string, includeRegex string, excludeRegex string, amazonFulfilled bool, amazonMerchant bool, affiliateLinks bool) (*AmazonParser, error) {
var err error
var includeRegexCompiled, excludeRegexCompiled *regexp.Regexp
log.Debugf("compiling include name regex")
if includeRegex != "" {
includeRegexCompiled, err = regexp.Compile(includeRegex)
if err != nil {
return nil, err
}
}
log.Debugf("compiling exclude name regex")
if excludeRegex != "" {
excludeRegexCompiled, err = regexp.Compile(excludeRegex)
if err != nil {
return nil, err
}
}
return &AmazonParser{
client: NewAmazonServer(marketplace).CreateClient(partnerTag, accessKey, secretKey),
searches: searches,
includeRegex: includeRegexCompiled,
excludeRegex: excludeRegexCompiled,
amazonFulfilled: amazonFulfilled,
amazonMerchant: amazonMerchant,
affiliateLinks: affiliateLinks,
}, nil
}
// Parse Amazon API to return list of products
// Implements Parser interface
func (p *AmazonParser) Parse() ([]*Product, error) {
var products []*Product
for _, search := range p.searches {
log.Debugf("searching for '%s' on %s", search, p.client.Marketplace())
// create search request on API
q := query.NewSearchItems(
p.client.Marketplace(),
p.client.PartnerTag(),
p.client.PartnerType(),
).Search(query.Keywords, search).EnableItemInfo().EnableOffers()
body, err := p.client.Request(q)
if err != nil {
return nil, err
}
// decode response
res, err := entity.DecodeResponse(body)
if err != nil {
return nil, err
}
// decode products
for _, item := range res.SearchResult.Items {
product := &Product{}
if !p.affiliateLinks {
product.URL = fmt.Sprintf("https://%s/dp/%s", p.client.Marketplace(), item.ASIN)
} else {
product.URL = item.DetailPageURL // includes partner tag
}
product.Name = item.ItemInfo.Title.DisplayValue
if item.Offers != nil && *item.Offers.Listings != nil {
for _, offer := range *item.Offers.Listings {
// detect if product is packaged by Amazon
if p.amazonFulfilled && !offer.DeliveryInfo.IsAmazonFulfilled {
log.Debugf("excluding offer by '%s' for product '%s' because not fulfilled by Amazon", offer.MerchantInfo.Name, product.Name)
continue
}
// detect if product is sold by Amazon
if p.amazonMerchant && !strings.HasPrefix(offer.MerchantInfo.Name, "Amazon") {
log.Debugf("excluding offer by '%s' for product '%s' because not sold by Amazon", offer.MerchantInfo.Name, product.Name)
continue
}
// detect price
product.Price = offer.Price.Amount
product.PriceCurrency = offer.Price.Currency
// detect availability
if ContainsString(availabilityMessages, offer.Availability.Message) {
product.Available = true
break
}
}
}
products = append(products, product)
}
}
// apply filters
products = filterInclusive(p.includeRegex, products)
products = filterExclusive(p.excludeRegex, products)
return products, nil
}
// String to print AmazonParser
// Implements the Parser interface
func (p *AmazonParser) String() string {
return fmt.Sprintf("AmazonParser<%s@%s>", p.client.PartnerTag(), p.client.Marketplace())
}
// ShopName returns shop name from Amazon Marketplace
func (p *AmazonParser) ShopName() (string, error) {
return strings.ReplaceAll(p.client.Marketplace(), "www.", ""), nil
}