Showing
20 changed files
with
401 additions
and
70 deletions
bunjang/Dockerfile
0 → 100644
bunjang/controller/controller.go
0 → 100644
| 1 | +package controller | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "bunjang/service" | ||
| 5 | + "net/http" | ||
| 6 | + | ||
| 7 | + "github.com/labstack/echo/v4" | ||
| 8 | +) | ||
| 9 | + | ||
| 10 | +func Search(c echo.Context) error { | ||
| 11 | + keyword := c.Param("keyword") | ||
| 12 | + items, err := service.GetItemByKeyword(keyword) | ||
| 13 | + if err != nil { | ||
| 14 | + return err | ||
| 15 | + } | ||
| 16 | + return c.JSON(http.StatusOK, items) | ||
| 17 | +} |
bunjang/deploy.sh
0 → 100755
bunjang/docker-compose.yml
0 → 100644
bunjang/go.mod
0 → 100644
| 1 | +module bunjang | ||
| 2 | + | ||
| 3 | +go 1.17 | ||
| 4 | + | ||
| 5 | +require ( | ||
| 6 | + github.com/PuerkitoBio/goquery v1.8.0 // indirect | ||
| 7 | + github.com/andybalholm/cascadia v1.3.1 // indirect | ||
| 8 | + github.com/go-rod/rod v0.106.8 // indirect | ||
| 9 | + github.com/labstack/echo/v4 v4.7.2 // indirect | ||
| 10 | + github.com/labstack/gommon v0.3.1 // indirect | ||
| 11 | + github.com/mattn/go-colorable v0.1.11 // indirect | ||
| 12 | + github.com/mattn/go-isatty v0.0.14 // indirect | ||
| 13 | + github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
| 14 | + github.com/valyala/fasttemplate v1.2.1 // indirect | ||
| 15 | + github.com/ysmood/goob v0.4.0 // indirect | ||
| 16 | + github.com/ysmood/gson v0.7.1 // indirect | ||
| 17 | + github.com/ysmood/leakless v0.7.0 // indirect | ||
| 18 | + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect | ||
| 19 | + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect | ||
| 20 | + golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect | ||
| 21 | + golang.org/x/text v0.3.7 // indirect | ||
| 22 | +) |
bunjang/go.sum
0 → 100644
| 1 | +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= | ||
| 2 | +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= | ||
| 3 | +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= | ||
| 4 | +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= | ||
| 5 | +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| 6 | +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| 7 | +github.com/go-rod/rod v0.106.8 h1:pVMVz0jMtLVyx8FhJEEA6l+EY9Iw/nJTDYT/he4+UJc= | ||
| 8 | +github.com/go-rod/rod v0.106.8/go.mod h1:xkZOchuKqTOkMOBkrzb7uJpbKZRab1haPCWDvuZkS2U= | ||
| 9 | +github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI= | ||
| 10 | +github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= | ||
| 11 | +github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= | ||
| 12 | +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= | ||
| 13 | +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= | ||
| 14 | +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= | ||
| 15 | +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= | ||
| 16 | +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= | ||
| 17 | +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
| 18 | +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
| 19 | +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
| 20 | +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||
| 21 | +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||
| 22 | +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= | ||
| 23 | +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= | ||
| 24 | +github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= | ||
| 25 | +github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= | ||
| 26 | +github.com/ysmood/got v0.29.1/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY= | ||
| 27 | +github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= | ||
| 28 | +github.com/ysmood/gson v0.7.1 h1:zKL2MTGtynxdBdlZjyGsvEOZ7dkxaY5TH6QhAbTgz0Q= | ||
| 29 | +github.com/ysmood/gson v0.7.1/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= | ||
| 30 | +github.com/ysmood/leakless v0.7.0 h1:XCGdaPExyoreoQd+H5qgxM3ReNbSPFsEXpSKwbXbwQw= | ||
| 31 | +github.com/ysmood/leakless v0.7.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= | ||
| 32 | +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= | ||
| 33 | +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||
| 34 | +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||
| 35 | +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= | ||
| 36 | +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||
| 37 | +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
| 38 | +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
| 39 | +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
| 40 | +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
| 41 | +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= | ||
| 42 | +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
| 43 | +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||
| 44 | +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
| 45 | +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= | ||
| 46 | +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||
| 47 | +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
| 48 | +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
| 49 | +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
| 50 | +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
bunjang/main.go
0 → 100644
bunjang/model/api_response.go
0 → 100644
| 1 | +package model | ||
| 2 | + | ||
| 3 | +type ApiResponse struct { | ||
| 4 | + Result string `json:"result"` | ||
| 5 | + NoResult bool `json:"no_result"` | ||
| 6 | + Items []ApiResponseItem `json:"list"` | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +type ApiResponseItem struct { | ||
| 10 | + Name string `json:"name"` | ||
| 11 | + Pid string `json:"pid"` | ||
| 12 | + Price string `json:"price"` | ||
| 13 | + ProductImage string `json:"product_image"` | ||
| 14 | +} |
bunjang/model/item.go
0 → 100644
bunjang/router/router.go
0 → 100644
| 1 | +package router | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "bunjang/controller" | ||
| 5 | + | ||
| 6 | + "github.com/labstack/echo/v4" | ||
| 7 | +) | ||
| 8 | + | ||
| 9 | +const ( | ||
| 10 | + API = "/api/v2" | ||
| 11 | + APIBunJang = API + "/bunjang" | ||
| 12 | + APIKeyword = APIBunJang + "/:keyword" | ||
| 13 | +) | ||
| 14 | + | ||
| 15 | +func Init(e *echo.Echo) { | ||
| 16 | + e.GET(APIKeyword, controller.Search) | ||
| 17 | +} |
bunjang/service/item.go
0 → 100644
| 1 | +package service | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "bunjang/model" | ||
| 5 | + "encoding/json" | ||
| 6 | + "fmt" | ||
| 7 | + "io" | ||
| 8 | + "io/ioutil" | ||
| 9 | + "log" | ||
| 10 | + "net/http" | ||
| 11 | + "net/url" | ||
| 12 | + "strconv" | ||
| 13 | + "strings" | ||
| 14 | + "sync" | ||
| 15 | +) | ||
| 16 | + | ||
| 17 | +func GetItemByKeyword(keyword string) ([]model.Item, error) { | ||
| 18 | + var items []model.Item | ||
| 19 | + wg := sync.WaitGroup{} | ||
| 20 | + | ||
| 21 | + responseItems, err := getApiResponseItems(keyword) | ||
| 22 | + if err != nil { | ||
| 23 | + return nil, err | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + for _, responseItem := range responseItems { | ||
| 27 | + wg.Add(1) | ||
| 28 | + | ||
| 29 | + go func(responseItem model.ApiResponseItem) { | ||
| 30 | + defer wg.Done() | ||
| 31 | + extraInfo, err := getItemExtraInfo(responseItem.Pid) | ||
| 32 | + if err != nil { | ||
| 33 | + log.Fatal(err) | ||
| 34 | + } | ||
| 35 | + item := model.Item{ | ||
| 36 | + Platform: "번개장터", | ||
| 37 | + Name: responseItem.Name, | ||
| 38 | + Price: priceStringToInt(responseItem.Price), | ||
| 39 | + ThumbnailUrl: responseItem.ProductImage, | ||
| 40 | + ItemUrl: "https://m.bunjang.co.kr/products/" + responseItem.Pid, | ||
| 41 | + ExtraInfo: extraInfo, | ||
| 42 | + } | ||
| 43 | + items = append(items, item) | ||
| 44 | + }(responseItem) | ||
| 45 | + } | ||
| 46 | + wg.Wait() | ||
| 47 | + | ||
| 48 | + return items, nil | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +func getApiResponseItems(keyword string) ([]model.ApiResponseItem, error) { | ||
| 52 | + encText := url.QueryEscape(keyword) | ||
| 53 | + apiUrl := fmt.Sprintf("https://api.bunjang.co.kr/api/1/find_v2.json?q=%s&order=score&n=6", encText) | ||
| 54 | + | ||
| 55 | + response, err := getResponse(apiUrl) | ||
| 56 | + if err != nil { | ||
| 57 | + return nil, err | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + var apiResponse model.ApiResponse | ||
| 61 | + err = json.Unmarshal(response, &apiResponse) | ||
| 62 | + if err != nil { | ||
| 63 | + return nil, err | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + return apiResponse.Items, nil | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +func getItemExtraInfo(pid string) (string, error) { | ||
| 70 | + apiUrl := fmt.Sprintf("https://api.bunjang.co.kr/api/1/product/%s/detail_info.json", pid) | ||
| 71 | + | ||
| 72 | + response, err := getResponse(apiUrl) | ||
| 73 | + if err != nil { | ||
| 74 | + return "", err | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + var itemInfo map[string]interface{} | ||
| 78 | + err = json.Unmarshal(response, &itemInfo) | ||
| 79 | + if err != nil { | ||
| 80 | + return "", err | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + extraInfo := itemInfo["item_info"].(map[string]interface{})["description_for_detail"].(string) | ||
| 84 | + | ||
| 85 | + return extraInfo, nil | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +func getResponse(url string) ([]byte, error) { | ||
| 89 | + req, err := http.NewRequest("GET", url, nil) | ||
| 90 | + if err != nil { | ||
| 91 | + return nil, err | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + client := &http.Client{} | ||
| 95 | + resp, err := client.Do(req) | ||
| 96 | + if err != nil { | ||
| 97 | + return nil, err | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + defer func(Body io.ReadCloser) { | ||
| 101 | + err := Body.Close() | ||
| 102 | + if err != nil { | ||
| 103 | + log.Fatal(err) | ||
| 104 | + } | ||
| 105 | + }(resp.Body) | ||
| 106 | + | ||
| 107 | + response, _ := ioutil.ReadAll(resp.Body) | ||
| 108 | + | ||
| 109 | + return response, nil | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +func priceStringToInt(priceString string) int { | ||
| 113 | + strings.TrimSpace(priceString) | ||
| 114 | + | ||
| 115 | + if priceString == "" { | ||
| 116 | + return 0 | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + priceString = strings.ReplaceAll(priceString, "원", "") | ||
| 120 | + priceString = strings.ReplaceAll(priceString, ",", "") | ||
| 121 | + | ||
| 122 | + price, err := strconv.Atoi(priceString) | ||
| 123 | + if err != nil { | ||
| 124 | + log.Fatal(err) | ||
| 125 | + } | ||
| 126 | + return price | ||
| 127 | +} |
bunjang/undeploy.sh
0 → 100755
deploy.sh
0 → 100755
| 1 | +#!/usr/bin/env bash | ||
| 2 | + | ||
| 3 | +docker build -t daangn-api-server ./daangn/ | ||
| 4 | +docker build -t joongna-api-server ./joongna/ | ||
| 5 | +docker build -t bunjang-api-server ./bunjang/ | ||
| 6 | +docker build -t mamuri-db ./database/ | ||
| 7 | + | ||
| 8 | +docker-compose up -d | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
docker-compose.yml
0 → 100644
| 1 | +version: '3' | ||
| 2 | + | ||
| 3 | +services: | ||
| 4 | + daangn_api: | ||
| 5 | + image: daangn-api-server | ||
| 6 | + restart: always | ||
| 7 | + container_name: daangn-api-server-container | ||
| 8 | + ports: | ||
| 9 | + - '18080:8080' | ||
| 10 | + | ||
| 11 | + joongna_api: | ||
| 12 | + image: joongna-api-server | ||
| 13 | + restart: always | ||
| 14 | + container_name: joongna-api-server-container | ||
| 15 | + ports: | ||
| 16 | + - '18081:8080' | ||
| 17 | + | ||
| 18 | + bunjang_api: | ||
| 19 | + image: bunjang-api-server | ||
| 20 | + restart: always | ||
| 21 | + container_name: bunjang-api-server-container | ||
| 22 | + ports: | ||
| 23 | + - '18082:8080' | ||
| 24 | + | ||
| 25 | + db: | ||
| 26 | + image: mamuri-db | ||
| 27 | + restart: always | ||
| 28 | + container_name: mamuri-db-container | ||
| 29 | + ports: | ||
| 30 | + - '13060:3306' | ||
| 31 | + env_file: | ||
| 32 | + - "./database/mysql_init/.env" | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -11,7 +11,5 @@ WORKDIR /src | ... | @@ -11,7 +11,5 @@ WORKDIR /src |
| 11 | COPY --from=builder /src/joongna_api_server /src/joongna_api_server | 11 | COPY --from=builder /src/joongna_api_server /src/joongna_api_server |
| 12 | COPY --from=builder /src/config/.env /src/config/.env | 12 | COPY --from=builder /src/config/.env /src/config/.env |
| 13 | 13 | ||
| 14 | -RUN apk add chromium | ||
| 15 | - | ||
| 16 | EXPOSE 8080 | 14 | EXPOSE 8080 |
| 17 | CMD ["./joongna_api_server"] | 15 | CMD ["./joongna_api_server"] | ... | ... |
| ... | @@ -12,6 +12,11 @@ type Config struct { | ... | @@ -12,6 +12,11 @@ type Config struct { |
| 12 | CLIENTID string `env:"SECRET.CLIENTID"` | 12 | CLIENTID string `env:"SECRET.CLIENTID"` |
| 13 | CLIENTSECRET string `env:"SECRET.CLIENTSECRET"` | 13 | CLIENTSECRET string `env:"SECRET.CLIENTSECRET"` |
| 14 | } | 14 | } |
| 15 | + | ||
| 16 | + Header struct { | ||
| 17 | + Cookie string `env:"HEADER.COOKIE"` | ||
| 18 | + UserAgent string `env:"HEADER.USERAGENT"` | ||
| 19 | + } | ||
| 15 | } | 20 | } |
| 16 | 21 | ||
| 17 | var Cfg *Config | 22 | var Cfg *Config | ... | ... |
| 1 | package model | 1 | package model |
| 2 | 2 | ||
| 3 | type ApiResponse struct { | 3 | type ApiResponse struct { |
| 4 | - LastBuildDate string `json:"lastBuildDate"` | 4 | + CafeId int `json:"cafeId"` |
| 5 | - Total uint `json:"total"` | 5 | + ArticelCount int `json:"articleCount"` |
| 6 | - Start uint `json:"start"` | 6 | + Query string `json:"query"` |
| 7 | - Display uint `json:"display"` | 7 | + Items []ApiResponseItem `json:"articleList"` |
| 8 | - Items []ApiResponseItem `json:"items"` | ||
| 9 | } | 8 | } |
| 10 | 9 | ||
| 11 | type ApiResponseItem struct { | 10 | type ApiResponseItem struct { |
| 12 | - Title string `json:"title"` | 11 | + ArticleId int `json:"articleId"` |
| 13 | - Link string `json:"link"` | 12 | + Title string `json:"subject"` |
| 14 | - Description string `json:"description"` | 13 | + ExtraInfo string `json:"summary"` |
| 15 | - CafeName string `json:"cafename"` | 14 | + ThumbnailUrl string `json:"thumbnailImageUrl"` |
| 15 | + ProductSale ApiResponseItemSale `json:"productSale"` | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +type ApiResponseItemSale struct { | ||
| 19 | + SaleStatus string `json:"saleStatue"` | ||
| 20 | + Cost string `json:"cost"` | ||
| 16 | } | 21 | } | ... | ... |
| 1 | package service | 1 | package service |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | - "bytes" | ||
| 5 | "encoding/json" | 4 | "encoding/json" |
| 5 | + "fmt" | ||
| 6 | "io" | 6 | "io" |
| 7 | "io/ioutil" | 7 | "io/ioutil" |
| 8 | "joongna/config" | 8 | "joongna/config" |
| ... | @@ -13,36 +13,35 @@ import ( | ... | @@ -13,36 +13,35 @@ import ( |
| 13 | "strconv" | 13 | "strconv" |
| 14 | "strings" | 14 | "strings" |
| 15 | "sync" | 15 | "sync" |
| 16 | - "time" | ||
| 17 | - | ||
| 18 | - "github.com/PuerkitoBio/goquery" | ||
| 19 | - "github.com/go-rod/rod" | ||
| 20 | - "github.com/go-rod/rod/lib/launcher" | ||
| 21 | ) | 16 | ) |
| 22 | 17 | ||
| 23 | func GetItemByKeyword(keyword string) ([]model.Item, error) { | 18 | func GetItemByKeyword(keyword string) ([]model.Item, error) { |
| 24 | var items []model.Item | 19 | var items []model.Item |
| 25 | wg := sync.WaitGroup{} | 20 | wg := sync.WaitGroup{} |
| 26 | 21 | ||
| 27 | - itemsInfo, err := getItemsInfoByKeyword(keyword) | 22 | + responseItems, err := getItemsInfoByKeyword(keyword) |
| 28 | if err != nil { | 23 | if err != nil { |
| 29 | return nil, err | 24 | return nil, err |
| 30 | } | 25 | } |
| 31 | 26 | ||
| 32 | - for _, itemInfo := range itemsInfo { | 27 | + for _, responseItem := range responseItems { |
| 33 | - itemUrl := itemInfo.Link | ||
| 34 | - if itemInfo.CafeName != "중고나라" { | ||
| 35 | - continue | ||
| 36 | - } | ||
| 37 | wg.Add(1) | 28 | wg.Add(1) |
| 38 | - go func(itemUrl string) { | 29 | + |
| 30 | + go func(responseItem model.ApiResponseItem) { | ||
| 39 | defer wg.Done() | 31 | defer wg.Done() |
| 40 | - item, err := crawlingNaverCafe(itemUrl) | ||
| 41 | if err != nil { | 32 | if err != nil { |
| 42 | log.Fatal(err) | 33 | log.Fatal(err) |
| 43 | } | 34 | } |
| 44 | - items = append(items, *item) | 35 | + item := model.Item{ |
| 45 | - }(itemUrl) | 36 | + Platform: "중고나라", |
| 37 | + Name: responseItem.Title, | ||
| 38 | + Price: priceStringToInt(responseItem.ProductSale.Cost), | ||
| 39 | + ThumbnailUrl: responseItem.ThumbnailUrl, | ||
| 40 | + ItemUrl: fmt.Sprintf("https://m.cafe.naver.com/ca-fe/web/cafes/10050146/articles/%d", responseItem.ArticleId), | ||
| 41 | + ExtraInfo: responseItem.ExtraInfo, | ||
| 42 | + } | ||
| 43 | + items = append(items, item) | ||
| 44 | + }(responseItem) | ||
| 46 | } | 45 | } |
| 47 | wg.Wait() | 46 | wg.Wait() |
| 48 | 47 | ||
| ... | @@ -50,8 +49,8 @@ func GetItemByKeyword(keyword string) ([]model.Item, error) { | ... | @@ -50,8 +49,8 @@ func GetItemByKeyword(keyword string) ([]model.Item, error) { |
| 50 | } | 49 | } |
| 51 | 50 | ||
| 52 | func getItemsInfoByKeyword(keyword string) ([]model.ApiResponseItem, error) { | 51 | func getItemsInfoByKeyword(keyword string) ([]model.ApiResponseItem, error) { |
| 53 | - encText := url.QueryEscape("중고나라 " + keyword + " 판매중") | 52 | + encText := url.QueryEscape(keyword) |
| 54 | - apiUrl := "https://openapi.naver.com/v1/search/cafearticle.json?query=" + encText + "&sort=sim" | 53 | + apiUrl := fmt.Sprintf("https://apis.naver.com/cafe-web/cafe-mobile/CafeMobileWebArticleSearchListV3?cafeId=10050146&query=%s&searchBy=0&sortBy=sim&page=1&perPage=10&adUnit=MW_CAFE_BOARD", encText) |
| 55 | 54 | ||
| 56 | req, err := http.NewRequest("GET", apiUrl, nil) | 55 | req, err := http.NewRequest("GET", apiUrl, nil) |
| 57 | if err != nil { | 56 | if err != nil { |
| ... | @@ -59,6 +58,8 @@ func getItemsInfoByKeyword(keyword string) ([]model.ApiResponseItem, error) { | ... | @@ -59,6 +58,8 @@ func getItemsInfoByKeyword(keyword string) ([]model.ApiResponseItem, error) { |
| 59 | } | 58 | } |
| 60 | req.Header.Add("X-Naver-Client-Id", config.Cfg.Secret.CLIENTID) | 59 | req.Header.Add("X-Naver-Client-Id", config.Cfg.Secret.CLIENTID) |
| 61 | req.Header.Add("X-Naver-Client-Secret", config.Cfg.Secret.CLIENTSECRET) | 60 | req.Header.Add("X-Naver-Client-Secret", config.Cfg.Secret.CLIENTSECRET) |
| 61 | + req.Header.Add("Cookie", config.Cfg.Header.Cookie) | ||
| 62 | + req.Header.Add("User-agent", config.Cfg.Header.UserAgent) | ||
| 62 | 63 | ||
| 63 | client := &http.Client{} | 64 | client := &http.Client{} |
| 64 | resp, err := client.Do(req) | 65 | resp, err := client.Do(req) |
| ... | @@ -73,55 +74,26 @@ func getItemsInfoByKeyword(keyword string) ([]model.ApiResponseItem, error) { | ... | @@ -73,55 +74,26 @@ func getItemsInfoByKeyword(keyword string) ([]model.ApiResponseItem, error) { |
| 73 | }(resp.Body) | 74 | }(resp.Body) |
| 74 | 75 | ||
| 75 | response, _ := ioutil.ReadAll(resp.Body) | 76 | response, _ := ioutil.ReadAll(resp.Body) |
| 76 | - var apiResponse model.ApiResponse | 77 | + |
| 77 | - err = json.Unmarshal(response, &apiResponse) | 78 | + var apiResult map[string]interface{} |
| 79 | + err = json.Unmarshal(response, &apiResult) | ||
| 78 | if err != nil { | 80 | if err != nil { |
| 79 | - log.Fatal(err) | 81 | + return nil, err |
| 80 | } | 82 | } |
| 81 | - return apiResponse.Items, nil | ||
| 82 | -} | ||
| 83 | 83 | ||
| 84 | -func crawlingNaverCafe(cafeUrl string) (*model.Item, error) { | 84 | + result := apiResult["message"].(map[string]interface{})["result"] |
| 85 | - path, _ := launcher.LookPath() | 85 | + resultJson, err := json.Marshal(result) |
| 86 | - u := launcher.New().Bin(path).MustLaunch() | ||
| 87 | - | ||
| 88 | - browser := rod.New().ControlURL(u).MustConnect() | ||
| 89 | - defer func(browser *rod.Browser) { | ||
| 90 | - err := browser.Close() | ||
| 91 | - if err != nil { | ||
| 92 | - log.Fatal(err) | ||
| 93 | - } | ||
| 94 | - }(browser) | ||
| 95 | - | ||
| 96 | - frame := browser.MustPage(cafeUrl).MustElement("iframe#cafe_main") | ||
| 97 | - time.Sleep(time.Second * 2) | ||
| 98 | - source := frame.MustFrame().MustHTML() | ||
| 99 | - html, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(source))) | ||
| 100 | if err != nil { | 86 | if err != nil { |
| 101 | return nil, err | 87 | return nil, err |
| 102 | } | 88 | } |
| 103 | 89 | ||
| 104 | - title := html.Find("h3.title_text").Text() | 90 | + var apiResponse model.ApiResponse |
| 105 | - sold := html.Find("div.sold_area").Text() | 91 | + err = json.Unmarshal(resultJson, &apiResponse) |
| 106 | - price := priceStringToInt(html.Find(".ProductPrice").Text()) | 92 | + if err != nil { |
| 107 | - thumbnailUrl, _ := html.Find("div.product_thumb img").Attr("src") | 93 | + return nil, err |
| 108 | - extraInfo := html.Find(".se-module-text").Text() | ||
| 109 | - | ||
| 110 | - title = strings.TrimSpace(title) | ||
| 111 | - sold = strings.TrimSpace(sold) | ||
| 112 | - thumbnailUrl = strings.TrimSpace(thumbnailUrl) | ||
| 113 | - extraInfo = strings.TrimSpace(extraInfo) | ||
| 114 | - | ||
| 115 | - item := model.Item{ | ||
| 116 | - Platform: "중고나라", | ||
| 117 | - Name: title, | ||
| 118 | - Price: price, | ||
| 119 | - ThumbnailUrl: thumbnailUrl, | ||
| 120 | - ItemUrl: cafeUrl, | ||
| 121 | - ExtraInfo: extraInfo, | ||
| 122 | } | 94 | } |
| 123 | 95 | ||
| 124 | - return &item, nil | 96 | + return apiResponse.Items, nil |
| 125 | } | 97 | } |
| 126 | 98 | ||
| 127 | func priceStringToInt(priceString string) int { | 99 | func priceStringToInt(priceString string) int { | ... | ... |
-
Please register or login to post a comment