Dev/Go
Cobra로 Command 입력
newtype
2022. 10. 11. 09:46
개요
- jeager의 소스 코드를 보다보니, cobra라는 package를 사용하고 있었고 검색해보니, 복잡한 파라미터를 사용하는 cli app 개발에 유용하며 kubeneties, jeager, promethtus등 go로 만들어진 많은 app 에서 사용하고 있다고 한다. cobra를 활용한 간단한 프로젝트를 해보려고한다.
데모 프로젝트
- go_google_images 단어를 입력받아, 이미지를 검색해서 다운로드하는 간단한 프로그램입니다. cobra를 사용해서 커맨드 파라미터를 입력받을 것입니다.
기능
- cobra로 param을 입력 받는다.
- 입력받은 검색어를 아래 URL로 google에 요청한다.
"https://www.google.co.in/search?q="+QUERY+"&source=lnms&tbm=isch"
- html을 파싱해서 검색 결과 이미지 목록을 추출한다.
- 이미지를 다운 받아 경로에 저정한다.
- 파일명은 "검색어_0000." + 이미지 형식으로 저장한다.
Usage
Usage:
google-image-downloader [flags]
google-image-downloader [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
get Download google images.
help Help about any command
version Display version number.
Flags:
-h, --help help for google-image-downloader
Code Review
- cmd라는 하위 디렉토리를 만들고, 추가한 command 마다 소스 파일을 구분해준다. 여기서는
get
,version
두개의 command를 만들었다. root
는get
,version
두개의 하위 command를 가진다.
func init() {
rootCmd.AddCommand(version)
rootCmd.AddCommand(get)
}
- main 에서
root
의Execute()
를 호출한다.
cmd.Execute()
- command 구조체에서 정의한 내용들을 필요에 따라 호출된다.
- Use: 한줄짜리 Usage 메시지
- Short: help에서 출력되는 짧은 메시지
- Long: help에서 출려되는 긴 메시지 'help <현재-command>'와 같이 출력된다.
- Example: 사용예 출력메시지
- Args: Run을 실행하기 전에 파라미터 유효성 검사를 하기 위한 함수. nil을 리턴해야 Run이 실행된다.
- Run: 현재-command가 입력되었을때 실행한다.
var get = &cobra.Command{
Use: appName + " get \"query-string\"",
Short: "Download google images.",
Long: "search google and download image files.",
Example: func() string {
return fmt.Sprintf(" %s get \"IU\"\n", appName) +
fmt.Sprintf(" %s get \"IU\" --target ./images\n", appName)
}(),
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 1 {
return errors.New("enter the Query-String")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
targetPath, err := cmd.Flags().GetString("target")
if err != nil {
log.Fatalln(err)
}
fmt.Printf("search %s download to %s\n", args[0], targetPath)
googleImageCrawler.Crawler(args[0], targetPath)
},
}
Crawling
- html 파싱은 goquery를 사용했다.
doc, err := goquery.NewDocumentFromReader(res.Body)
- 'Search Result'라는 text 이후에 나오는 img 태그를 찾고, src 또는 data-src 속성의 값을 saveUrlImage 함수로 넘긴다
doc.Find("h1").Each(func(i int, s *goquery.Selection) {
if s.Text() == "Search results" {
s = s.Next().Find("div")
s.Find("img").Each(func(i int, s *goquery.Selection) {
imgUrl := ""
if src, exists := s.Attr("src"); exists {
imgUrl = src
} else if src, exists := s.Attr("data-src"); exists {
imgUrl = src
}
// ......
})
}
})
- 이미지 저장시간이 오래 걸리 수 있으므로 go 루틴을 사용해서 병렬화하고
go func(url string, filename string) {
saveUrlImage(imgUrl, filename)
defer wait.Done()
}(imgUrl, filename)
- 전체 이미지가 모두 다운받은 후 프로그램을 종료해야하므로 sync.WaitGroup을 만들어 대기한다.
var wait sync.WaitGroup
// ...
wait.Add(1)
go func(url string, filename string) {
saveUrlImage(imgUrl, filename)
defer wait.Done()
}(imgUrl, filename)
})
}
})
wait.Wait()
Image Download
- html body에 데이터를 이미지 파일로 저장한다.
- html 해더의
Content-Type
으로 이미지 파일 확장자를 결정한다.
func getImageType(res *http.Response) string {
imageTypes := []string{"jpeg", "svg", "png", "gif", "bmp"}
contentType := res.Header.Get("Content-Type")
for _, imageType := range imageTypes {
if fmt.Sprintf("image/%s", imageType) == contentType {
return imageType
}
}
return ""
}
반응형