노마드코더의 고랭 강의를 듣고 작성한 문서입니다.
0. Introduction
0) What are Building
scrapper 를 만들 것이다.
이번 코스에서 kr.indeed.com 의 데이터를 추출하는 웹 스크래퍼를 만들어 볼 것이다.
엑셀에서 볼 수 있도록 (CSV)
Go 에 있는 데이터 처리 도구를 사용할 것 이다.
Python 에 비하면 매우 빠르다.
1) Software and Installation
go 를 다운로드하지 않고 사용하려면 repl.it 을 사용하자.
또한, VSCode 에 Go Extension 을 설치해 배워보자.
1. Go 설치
모든 go 코드는 GOPATH 에 저장해야 한다.
맥의 경우 usr/local/go 이다.
노드와 같이 package.json 은 없다.
이제 src 폴더에 github.com 폴더를 만들어주고 안에 자신의 user 이름으로 된 폴더를 또 만들어주자.
이제 learngo 라는 폴더를 만들고 안에 main.go 파일을 생성 후 VSCode 로 열어보자.
1. Theory
0) Main Package
go 를 컴파일하고 싶다면 무조건 main.go 가 필요하다.
서버를 시작하고 웹을 스크랩할 것이다.
공유를 위한 라이브러리나 오픈소스는 main.go 가 필요없다.
이 코스의 경우 우리는 컴파일이 필요하다.
main.go 는 진입점이다.
컴파일러는 자동적으로 main package 와 func main 을 찾는다.
JavaScript 나 C 랑 비슷하며 Python 이랑 비슷하다.
package main
import "fmt"
func main() {
fmt.Println("Hello world!!")
}
1) Variables and Constants
Go 의 변수와 상수를 보자.
const name string = "nico"
name := "nico"
축약형은 함수 안에서만 가능하고 변수만 가능하다.
2) for, range, ...args
func superAdd(numbers ...int) int {
for index, number := range numbers {
fmt.Println(index, number)
}
return 1
}
range 는 for 문에서만 사용할 수 있다.
3) If with a Twist
if 안에 변수를 선언할 수 있다.
4) Switch
func canIDrink(age int) bool {
switch {
case age < 18:
return false
case age == 18:
return true
case age > 50:
return false
}
return false
}
case 에 조건문을 사용할 수도 있다.
if 문과 마찬가지로 변수를 선언할 수도 있다.
5) Pointers!
메모리 주소를 확인해보자.
func main() {
a := 2
b := 5
fmt.Println(&a, &b)
}
func main() {
a := 2
b := &a
*b = 10
fmt.Println(a, b)
}
6) Arrays and Slices
append 함수는 기존의 배열을 바꾸지 않고 새로운 배열을 반환한다.
names := []string{"nico", "lynn", "dal"}
names = append(names, "flynn")
fmt.Println(names)
7) Maps
func main() {
nico := map[string]string{
"name": "nico",
"age": "12",
}
fmt.Println(nico)
map 에 이미 있는 값인지 확인하는 함수도 있다.
8) Structs
func main() {
favFood := []string{"kimchi", "ramen"}
nico := person{"nico", 18, favFood}
fmt.Println(nico.name)
}
Go 에는 constructor() 가 없다.
Python 에서는 __init__ 이다.
2. Bank & Dictionary projects
0) Account + NewAccount
- accounts.go
// Account
type Account struct {
owner string
balance int
}
// NewAccount creates Account
func NewAccount(owner string) *Account {
account := Account{
owner: owner,
balance: 0,
}
return &account
}
1) Methods part One
func (theAccount Account) Deposit(amount int) {
}
이 메서드에서 theAccount 는 receiver 가 된다.
2) Methods part Two
// Deposit x amount on your account
func (a *Account) Deposit(amount int) {
a.balance += amount
}
balance 가 바뀌지 않는다면 receiver 에 * 를 붙인다.
이제 에러에 대해 살펴보자.
에러를 발생시키는 방법은 2가지가 있다.
// Withdraw x amount from your account
func (a *Account) Withdraw(amount int) error {
if a.balance < amount {
// return error.Error()
return errors.New("Can't withdraw you are poor")
}
a.balance -= amount
return nil
}
log.Fatalln(err) 는 에러를 던지고 종료시킨다.
- main.go
func main() {
account := accounts.NewAccount("nico")
account.Deposit(10)
fmt.Println(account.Balance())
err := account.Withdraw(20)
if err != nil {
log.Println(err)
}
fmt.Println(account.Balance())
}
Go 에는 exception 이 없으므로 에러를 던져줘야 한다.
에러는 errNoMoney 처럼 err 이 앞에 prefix 로 붙는 것이 좋다. (린팅)
3) Finishing Up
method 의 types 에 대해 알아보자.
자바의 toString 을 재정의하는 것 처럼
func (a Account) String() string {
return "whatever you want"
}
go 에서도 재정의할 수 있다.
account struct 를 출력하면
fmt.Println(account)
func (a Account) String() string {
return fmt.Sprint(a.Owner(), "'s account.\nHas: ", a.Balance())
}
4) Dictionary part One
Go 블로그: https://go.dev/blog/
Go map in action: https://go.dev/blog/maps
- main
type Dictionary map[string]string
func main() {
dictionary := myDict.Dictionary{"first": "First word"}
definition, err := dictionary.Search("first")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(definition)
}
}
- myDict
// Dictionary type
type Dictionary map[string]string
var errNotFound = errors.New("Not Found")
// Search for a word
func (d Dictionary) Search(word string) (string, error) {
value, exists := d[word]
if exists {
return value, nil
}
return "", errNotFound
}
5) Add Method
- myDict.go
// Add a word to the dictionary
func (d Dictionary) Add(word, def string) error {
_, err := d.Search(word)
switch err {
case errNotFound:
d[word] = def
case nil:
return errWordExists
}
return nil
}
- main.go
type Dictionary map[string]string
func main() {
dictionary := myDict.Dictionary{}
word := "hello"
definition := "Greeting"
err := dictionary.Add(word, definition)
if err != nil {
fmt.Println(err)
}
hello, err := dictionary.Search(word)
fmt.Println(hello)
err2 := dictionary.Add(word, definition)
if err2 != nil {
fmt.Println(err2)
}
}
6) Update Delete
스킵
3. URL checker & Go routines
0) hitURL
1) Slow URLChecker
2) Goroutines
func main() {
sexyCount("nico")
sexyCount("flynn")
}
func sexyCount(person string) {
for i := 0; i < 10; i++ {
fmt.Println(person, "is sexy", i)
time.Sleep(time.Second)
}
}
- 고루틴을 사용하기
func main() {
go sexyCount("nico")
sexyCount("flynn")
}
고루틴은 프로그램이 작동하는 동안만 유효한다.
func main() {
go sexyCount("nico")
go sexyCount("flynn")
time.Sleep(time.Second * 5)
}
3) Channels
채널은 고루틴이랑 메인함수 사이에 정보를 전달하기 위한 방법이다.
고루틴에서 다른 고루틴으로도 전달할 수 있다.
func main() {
c := make(chan bool)
people := [2]string{"nico", "flynn"}
for _, person := range people {
go isSexy(person, c)
}
result := <-c
fmt.Println(result)
// time.Sleep(time.Second * 10)
}
func isSexy(person string, c chan bool) {
time.Sleep(time.Second * 5)
c <- true
}
채널이 있으면 기다리지 않아도 메인 함수는 종료되지 않는다.
- 두개의 메시지 받기
func main() {
c := make(chan bool)
people := [2]string{"nico", "flynn"}
for _, person := range people {
go isSexy(person, c)
}
fmt.Println(<-c)
fmt.Println(<-c)
// time.Sleep(time.Second * 10)
}
func isSexy(person string, c chan bool) {
time.Sleep(time.Second * 5)
c <- true
}
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
고루틴이 2개인데 채널을 더 받으려고 한다면 데드락이 걸린다.
자세한 것은 다음강의에서 살펴보자 !
4) Channels Recap
func main() {
c := make(chan string)
people := [2]string{"nico", "flynn"}
for _, person := range people {
go isSexy(person, c)
}
fmt.Println("Waiting for messages")
fmt.Println("Received this message:", <-c)
fmt.Println("Received this message:", <-c)
}
func isSexy(person string, c chan string) {
time.Sleep(time.Second * 5)
c <- person + " is sexy"
}
메시지를 기다리는 것은 blocking operation 이다.
- for 문
func main() {
c := make(chan string)
people := [5]string{"nico", "flynn", "dal", "japanguy", "larry"}
for _, person := range people {
go isSexy(person, c)
}
for i := 0; i < len(people); i++ {
fmt.Println(<-c)
}
}
이 것이 훨씬 나은 방법이다.
5) One more Recap
6) URLChecker + Go Routines
7) FAST URLChecker
4. Job scrapper
0) getPages part One
스크래퍼 플젝트를 작성해보자.
goquery 를 사용할 것이다. JQuery 같은 것이다.
HTML 내부를 들여다볼 수 있다.
go get github.com/PuerkitoBio/goquery
goquery GitHub: https://github.com/PuerkitoBio/goquery
func getPages() int {
fmt.Println("[GET]Request :", baseURL)
req, rErr := http.NewRequest("GET", baseURL, nil)
checkErr(rErr)
purl, err := url.Parse(baseURL)
client := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(purl)}}
res, err := client.Do(req)
checkErr(err)
checkCode(res)
return 0
}
- 중간 샘플 코드
var baseURL string = "https://kr.indeed.com/jobs?q=python&limit=50"
func main() {
getPages()
}
func getPages() int {
req, rErr := http.NewRequest("GET", baseURL, nil)
checkErr(rErr)
// 프록시로 호출
purl, err := url.Parse(baseURL)
client := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(purl)}}
res, err := client.Do(req)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
fmt.Println(doc)
return 0
}
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalln("Request failed with Status:", res.StatusCode)
}
}
결국 진행하다가 안되서 사람인으로 진행했습니다.
- 샘플코드
var baseURL string = "https://www.saramin.co.kr/zf_user/search/recruit?&searchword=python"
func main() {
getPages()
}
func getPages() int {
res, err := http.Get(baseURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
doc.Find(".pagination").Each(func(i int, s *goquery.Selection) {
fmt.Println(s.Html())
})
return 0
}
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalln("Request failed with Status:", res.StatusCode)
}
}
- 페이지 갯수 확인
doc.Find(".pagination").Each(func(i int, s *goquery.Selection) {
fmt.Println(s.Find("a").Length())
})
1) getPages part Two
var baseURL string = "https://www.saramin.co.kr/zf_user/search/recruit?&searchword=python"
func main() {
totalPages := getPages()
for i := 0; i < totalPages; i++ {
getPage(i)
}
fmt.Println(totalPages)
}
func getPage(page int) {
pageUrl := baseURL + "&recruitPage=" + strconv.Itoa(page + 1)
fmt.Println("Requesting", pageUrl)
}
func getPages() int {
pages := 0
res, err := http.Get(baseURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
doc.Find(".pagination").Each(func(i int, s *goquery.Selection) {
pages = s.Find("a").Length()
})
return pages
}
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalln("Request failed with Status:", res.StatusCode)
}
}
2) extractJob part One
type extractedJob struct {
id string
location string
title string
salary string
summary string
}
var baseURL string = "https://www.saramin.co.kr/zf_user/search/recruit?keydownAccess=&searchType=search&searchword=python&panel_type=&search_optional_item=y&search_done=y&panel_count=y&preview=y&recruitSort=relation&recruitPageCount=40&inner_com_type=&company_cd=0%2C1%2C2%2C3%2C4%2C5%2C6%2C7%2C9%2C10&show_applied=&quick_apply=&except_read=&ai_head_hunting="
func main() {
totalPages := getPages()
for i := 0; i < 1; i++ {
getPage(i)
}
fmt.Println(totalPages)
}
func getPage(page int) {
pageURL := baseURL + "&recruitPage=" + strconv.Itoa(page+1)
fmt.Println("Requesting", pageURL)
res, err := http.Get(pageURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
searchCards := doc.Find(".item_recruit")
searchCards.Each(func(i int, card *goquery.Selection) {
id, _ := card.Attr("value")
fmt.Println(id)
title := card.Find(".job_tit").Text()
fmt.Println(title)
})
}
func getPages() int {
pages := 0
res, err := http.Get(baseURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
doc.Find(".pagination").Each(func(i int, s *goquery.Selection) {
pages = s.Find("a").Length()
})
return pages
}
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalln("Request failed with Status:", res.StatusCode)
}
}
- 지역 추출
searchCards.Each(func(i int, card *goquery.Selection) {
id, _ := card.Attr("value")
fmt.Println(id)
title := card.Find(".job_tit").Text()
fmt.Println(title)
location := card.Find(".job_condition").Text()
fmt.Println(location)
})
다음 강의에서 공백을 없애보자.
3) extractJob part Two
go package strings: https://pkg.go.dev/strings@go1.20.2
type extractedJob struct {
id string
location string
title string
summary string
}
var baseURL string = "https://www.saramin.co.kr/zf_user/search/recruit?keydownAccess=&searchType=search&searchword=python&panel_type=&search_optional_item=y&search_done=y&panel_count=y&preview=y&recruitSort=relation&recruitPageCount=40&inner_com_type=&company_cd=0%2C1%2C2%2C3%2C4%2C5%2C6%2C7%2C9%2C10&show_applied=&quick_apply=&except_read=&ai_head_hunting="
func main() {
var jobs []extractedJob
totalPages := getPages()
for i := 0; i < totalPages; i++ {
extractedJobs := getPage(i)
jobs = append(jobs, extractedJobs...)
}
fmt.Println(jobs)
fmt.Println(totalPages)
}
func getPage(page int) []extractedJob {
var jobs []extractedJob
pageURL := baseURL + "&recruitPage=" + strconv.Itoa(page+1)
fmt.Println("Requesting", pageURL)
res, err := http.Get(pageURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
searchCards := doc.Find(".item_recruit")
searchCards.Each(func(i int, card *goquery.Selection) {
job := extractJob(card)
jobs = append(jobs, job)
})
return jobs
}
func getPages() int {
pages := 0
res, err := http.Get(baseURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
doc.Find(".pagination").Each(func(i int, s *goquery.Selection) {
pages = s.Find("a").Length()
})
return pages
}
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalln("Request failed with Status:", res.StatusCode)
}
}
func extractJob(card *goquery.Selection) extractedJob {
id, _ := card.Attr("value")
title := cleanString(card.Find(".job_tit").Text())
location := cleanString(card.Find(".job_condition").Text())
summary := cleanString(card.Find(".job_sector").Text())
fmt.Println(id, title, location, summary)
return extractedJob{
id: id,
title: title,
location: location,
summary: summary,
}
}
func cleanString(str string) string {
return strings.Join(strings.Fields(strings.TrimSpace(str)), " ")
}
4) Writing Jobs
csv 관련한 패키지가 있다.
메뉴얼: https://pkg.go.dev/encoding/csv@go1.20.2
type extractedJob struct {
id string
location string
title string
summary string
}
var baseURL string = "https://www.saramin.co.kr/zf_user/search/recruit?keydownAccess=&searchType=search&searchword=python&panel_type=&search_optional_item=y&search_done=y&panel_count=y&preview=y&recruitSort=relation&recruitPageCount=40&inner_com_type=&company_cd=0%2C1%2C2%2C3%2C4%2C5%2C6%2C7%2C9%2C10&show_applied=&quick_apply=&except_read=&ai_head_hunting="
func main() {
var jobs []extractedJob
totalPages := getPages()
for i := 0; i < totalPages; i++ {
extractedJobs := getPage(i)
jobs = append(jobs, extractedJobs...)
}
writeJobs(jobs)
fmt.Println("Done, extracted", len(jobs))
}
func writeJobs(jobs []extractedJob) {
file, err := os.Create("jobs.csv")
checkErr(err)
w := csv.NewWriter(file)
defer w.Flush()
headers := []string{"ID", "Title", "Location", "Summary"}
wErr := w.Write(headers)
checkErr(wErr)
for _, job := range jobs {
jobSlice := []string{job.id, job.title, job.location, job.summary}
jwErr := w.Write(jobSlice)
checkErr(jwErr)
}
}
func getPage(page int) []extractedJob {
var jobs []extractedJob
pageURL := baseURL + "&recruitPage=" + strconv.Itoa(page+1)
fmt.Println("Requesting", pageURL)
res, err := http.Get(pageURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
searchCards := doc.Find(".item_recruit")
searchCards.Each(func(i int, card *goquery.Selection) {
job := extractJob(card)
jobs = append(jobs, job)
})
return jobs
}
func getPages() int {
pages := 0
res, err := http.Get(baseURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
doc.Find(".pagination").Each(func(i int, s *goquery.Selection) {
pages = s.Find("a").Length()
})
return pages
}
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalln("Request failed with Status:", res.StatusCode)
}
}
func extractJob(card *goquery.Selection) extractedJob {
id, _ := card.Attr("value")
title := cleanString(card.Find(".job_tit").Text())
location := cleanString(card.Find(".job_condition").Text())
summary := cleanString(card.Find(".job_sector").Text())
return extractedJob{
id: id,
title: title,
location: location,
summary: summary,
}
}
func cleanString(str string) string {
return strings.Join(strings.Fields(strings.TrimSpace(str)), " ")
}
이제 id 를 링크로 바꾸자
for _, job := range jobs {
jobSlice := []string{"https://www.saramin.co.kr/zf_user/jobs/relay/view?isMypage=no&recommend_ids=eJxFj8kVw0AIQ6vJHQFiOacQ999FZuyMOf4nISEnGll6FfDJrxulVbiwN3qwGRtjI8EGeqHcqAEzuUr7UWNl9SCq3E8yVcwsTzLhjnx7iYKnzW11ZRzVq1LbxuzO8vnKYrlHbUXL9BaNMapFxpgl7Fn0NwfT8Pa2aPqejx9jukA7&view_type=search&searchword=python&searchType=search&gz=1&t_ref_content=generic&t_ref=search&paid_fl=n&rec_idx=" + job.id, job.title, job.location, job.summary}
jwErr := w.Write(jobSlice)
checkErr(jwErr)
}
5) Channels Time
이제 고루틴을 활용해서 추출해보자.
main <-> getPage 와 getPage <-> extractJob 을 통해 주고받는
채널을 두 개 만들어보자.
- getPage 함수
func getPage(page int) []extractedJob {
var jobs []extractedJob
c := make(chan extractedJob)
pageURL := baseURL + "&recruitPage=" + strconv.Itoa(page+1)
fmt.Println("Requesting", pageURL)
res, err := http.Get(pageURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
searchCards := doc.Find(".item_recruit")
searchCards.Each(func(i int, card *goquery.Selection) {
go extractJob(card, c)
})
for i := 0; i < searchCards.Length(); i++ {
job := <-c
jobs = append(jobs, job)
}
return jobs
}
- extractJob 함수
func extractJob(card *goquery.Selection, c chan<- extractedJob) {
id, _ := card.Attr("value")
title := cleanString(card.Find(".job_tit").Text())
location := cleanString(card.Find(".job_condition").Text())
summary := cleanString(card.Find(".job_sector").Text())
c <- extractedJob{
id: id,
title: title,
location: location,
summary: summary,
}
}
6) More Channels Body
- main <-> getPages 고루틴 리팩토링
type extractedJob struct {
id string
location string
title string
summary string
}
var baseURL string = "https://www.saramin.co.kr/zf_user/search/recruit?keydownAccess=&searchType=search&searchword=python&panel_type=&search_optional_item=y&search_done=y&panel_count=y&preview=y&recruitSort=relation&recruitPageCount=40&inner_com_type=&company_cd=0%2C1%2C2%2C3%2C4%2C5%2C6%2C7%2C9%2C10&show_applied=&quick_apply=&except_read=&ai_head_hunting="
func main() {
var jobs []extractedJob
c := make(chan []extractedJob)
totalPages := getPages()
for i := 0; i < totalPages; i++ {
go getPage(i, c)
}
for i := 0; i < totalPages; i++ {
extractedJob := <-c
jobs = append(jobs, extractedJob...)
}
writeJobs(jobs)
fmt.Println("Done, extracted", len(jobs))
}
func getPage(page int, mainC chan<- []extractedJob) {
var jobs []extractedJob
c := make(chan extractedJob)
pageURL := baseURL + "&recruitPage=" + strconv.Itoa(page+1)
fmt.Println("Requesting", pageURL)
res, err := http.Get(pageURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
searchCards := doc.Find(".item_recruit")
searchCards.Each(func(i int, card *goquery.Selection) {
go extractJob(card, c)
})
for i := 0; i < searchCards.Length(); i++ {
job := <-c
jobs = append(jobs, job)
}
mainC <- jobs
}
func getPages() int {
pages := 0
res, err := http.Get(baseURL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
doc.Find(".pagination").Each(func(i int, s *goquery.Selection) {
pages = s.Find("a").Length()
})
return pages
}
func writeJobs(jobs []extractedJob) {
file, err := os.Create("jobs.csv")
checkErr(err)
w := csv.NewWriter(file)
defer w.Flush()
headers := []string{"ID", "Title", "Location", "Summary"}
wErr := w.Write(headers)
checkErr(wErr)
for _, job := range jobs {
jobSlice := []string{"https://www.saramin.co.kr/zf_user/jobs/relay/view?isMypage=no&recommend_ids=eJxFj8kVw0AIQ6vJHQFiOacQ999FZuyMOf4nISEnGll6FfDJrxulVbiwN3qwGRtjI8EGeqHcqAEzuUr7UWNl9SCq3E8yVcwsTzLhjnx7iYKnzW11ZRzVq1LbxuzO8vnKYrlHbUXL9BaNMapFxpgl7Fn0NwfT8Pa2aPqejx9jukA7&view_type=search&searchword=python&searchType=search&gz=1&t_ref_content=generic&t_ref=search&paid_fl=n&rec_idx=" + job.id, job.title, job.location, job.summary}
jwErr := w.Write(jobSlice)
checkErr(jwErr)
}
}
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalln("Request failed with Status:", res.StatusCode)
}
}
func extractJob(card *goquery.Selection, c chan<- extractedJob) {
id, _ := card.Attr("value")
title := cleanString(card.Find(".job_tit").Text())
location := cleanString(card.Find(".job_condition").Text())
summary := cleanString(card.Find(".job_sector").Text())
c <- extractedJob{
id: id,
title: title,
location: location,
summary: summary,
}
}
func cleanString(str string) string {
return strings.Join(strings.Fields(strings.TrimSpace(str)), " ")
}
엄청나게 빠른 속도로 jobs.csv 파일이 생성되는 것을 볼 수 있다.
5. Web server with echo
0) Setup Part One
go echo 로 서버를 만들 것이다.
go get github.com/labstack/echo
echo 를 받으려고 하니 1.17 이여야 한다고 해서 고 버전을 변경해서 설치했다.
- Go 버전 변경
brew link --force go@1.17
1) Setup Part Two
https://echo.labstack.com/guide/
샘플 코드를 복사해보자.
- home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go Jobs</title>
</head>
<body>
<h1>Go Jobs</h1>
<h3>Indeed.com scrapper</h3>
<form method="POST" action="/scrape">
<input type="text" placeholder="what jobb do u want" name="term">
<button>Search</button>
</form>
</body>
</html>
- main.go
func handleHome(c echo.Context) error {
return c.File("home.html")
}
func handleScrape(c echo.Context) error {
term := strings.ToLower(scrapper.CleanString(c.FormValue("term")))
fmt.Println(term)
return nil
}
func main() {
e := echo.New()
// e.GET()
e.GET("/", handleHome)
e.POST("/scrape", handleScrape)
e.Logger.Fatal(e.Start(":1323"))
}
2) File Download
func handleScrape(c echo.Context) error {
term := strings.ToLower(scrapper.CleanString(c.FormValue("term")))
scrapper.Scrape(term)
return c.Attachment("jobs.csv", "job.csv")
}
에코의 문서를 보면 알겠지만, 정말 배우기 쉽다.
템플릿 메뉴얼: https://echo.labstack.com/guide/templates/
'Backend > 노트' 카테고리의 다른 글
도커 환경에서 디버그하기 (0) | 2023.04.30 |
---|---|
따라하며 배우는 도커와 CI 환경 (1) | 2023.03.26 |
Gin (0) | 2022.10.15 |
Hands-On Full stack development with Go (1) | 2022.10.07 |
한 번에 끝내는 Node.js 웹 프로그래밍 초격차 패키지 Online - 2 (1) | 2022.09.20 |