Programming Language/Go
[Go언어] GoLang을 활용하여 웹페이지 크롤링
new_challenge
2020. 4. 15. 14:31
반응형
이번 포스팅은 Go언어를 사용하여 웹 페이지를 크롤링하는
튜토리얼 입니다.
아래 포스팅은 노마드코더의 golang강의를 기반으로
작성되었습니다.
크롤링 할 페이지 선택
>> 취업에 대한 정보가 나와있는 웹사이트
>> 검색어 쿼리를 던졌을 때 출력되는 결과를 크롤링
크롤링 코드 작성 (Go Lang)
사용 할 라이브러리 설치
- Go언어에서 크롤링을 하기 위해 필요한 라이브러리 설치
$ go get github.com/PuerkitoBio/goquery
- 아래는 Goquery 깃허브 링크
부가기능 별 함수 작성
에러 확인 function
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
http 요청 결과 상태 코드 확인 function
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalf("Status code err: %d %s", res.StatusCode, res.Status)
}
}
결과 string의 띄어쓰기 혹은 앞뒤 공백 제거 function
//CleanString function
func CleanString(str string) string {
return strings.Join(strings.Fields(strings.TrimSpace(str)), " ")
}
>> strings.TrimSpace(str) : 문자열 앞 뒤의 공백제거
>> strings.Fields(str) : 공백을 기준으로 문자열을 잘라 slice 형태로 저장
>> strings.Join([]str, sep) : sep 기준으로 문자열을 합쳐줌
< 테스트 용 코드 >
< 코드 실행 시 결과 >
메인 기능 별 함수 작성
크롤링 할 총 페이지 수 확인 func
func getPages(baseURL string) 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-list").Each(func(i int, s *goquery.Selection) {
pages = s.Find("li").Length()
})
return pages
}
특정 페이지의 결과 데이터 가져오기
func getCard(page int, baseURL string, c1 chan []Indeed) {
var jobs []Indeed
c := make(chan Indeed)
URL := baseURL + "&start=" + strconv.Itoa(page*10)
fmt.Println(URL)
res, err := http.Get(URL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
searchCards := doc.Find(".jobsearch-SerpJobCard")
searchCards.Each(func(i int, s *goquery.Selection) {
go extractJob(s, c)
})
for i := 0; i < searchCards.Length(); i++ {
job := <-c
jobs = append(jobs, job)
}
c1 <- jobs
}
>> strconv.Itoa(int) : int타입을 string으로 변환해주는 함수
각 결과 별 상세 데이터 추출 func
- 데이터를 담을 struct 선언
type Indeed struct {
id string
title string
location string
summary string
}
- 상세 데이터 추출
func extractJob(s *goquery.Selection, c chan<- Indeed) {
id, _ := s.Attr("data-jk")
title := CleanString(s.Find(".title>a").Text())
location := CleanString(s.Find(".accessible-contrast-color-location").Text())
summary := CleanString(s.Find(".summary").Text())
c <- Indeed{
id: id,
title: title,
location: location,
summary: summary}
}
결과 저장하는 func
func writeJobs(jobs []Indeed) {
file, err := os.Create("jobs.csv")
checkErr(err)
w := csv.NewWriter(file)
//Write data to the file
defer w.Flush()
header := []string{"ID", "TITLE", "LOCATION", "SUMMARY"}
wErr := w.Write(header)
checkErr(wErr)
for _, job := range jobs {
jobSlice := []string{"https://kr.indeed.com/viewjob?jk=" + job.id, job.title, job.location, job.summary}
jobErr := w.Write(jobSlice)
checkErr(jobErr)
}
}
크롤링 실행 시키는 메인 Scrapper 함수
//Scrapper function
func Scrapper(query string) {
var jobs []Indeed
var baseURL string = "https://kr.indeed.com/jobs?q=" + query // + "&l=%EC%84%9C%EC%9A%B8"
c1 := make(chan []Indeed)
TotalPage := getPages(baseURL)
fmt.Println("TotalPage...", TotalPage)
for i := 0; i < TotalPage; i++ {
go getCard(i, baseURL, c1)
}
for i := 0; i < TotalPage; i++ {
extractJobs := <-c1
//merge slices or arrays
jobs = append(jobs, extractJobs...)
}
writeJobs(jobs)
fmt.Println("Done")
}
API 서버 생성
Go 언어를 사용해서 API 서버 생성하기. 많은 라이브러리들이 존재 함
그 중에 echo를 활용 할 예정
ECHO 라이브러리 설치
$ go get -u github.com/labstack/echo/
화면을 보여줄 html 작성
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width= , initial-scale=1.0">
<title>Go Project</title>
</head>
<body>
<h1>Go Project</h1>
<h3>Indeed.com scrapper</h3>
<form method="POST" action="/scrape">
<input placeholder="what job do u want" name="query"/>
<button>Search</button>
</form>
</body>
</html>
- 쿼리로 보낼 검색어를 입력 할 수 있도록 form을 생성
- POST 매서드로 보내며 action="/scrape"로 요청을 보내도록 함
실제 서버가 작동 할 수 있도록 main.go
기본 home.html로 연결 func
//Handler function
func Handler(c echo.Context) error {
return c.File("home.html")
}
검색 쿼리에 대해 스크래핑 요청 func
//HandleFunc function
func HandleFunc(c echo.Context) error {
query := strings.ToLower(scrapper.CleanString(c.FormValue("query")))
fmt.Println(query)
scrapper.Scrapper(query)
return c.Attachment(fileName, query+".csv")
}
기본 main func
package main
import (
"fmt"
"strings"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.GET("/", Handler)
e.POST("/scrape", HandleFunc)
e.Logger.Fatal(e.Start(":1323"))
}
실제 구동 화면
main.go 실행
$ go run main.go
localhost:1323 접속
golang 검색 후 search 클릭
- 크롤링 함수가 golang 검색 후 나온 결과에 대한 크롤링 진행 후 아래와 같이 파일로 다운로드 함
Full Code : Scrapper.go
package scrapper
import (
"encoding/csv"
"fmt"
"log"
"net/http"
"os"
"strconv"
"strings"
"github.com/PuerkitoBio/goquery"
)
// goQuery 사용
// go get github.com/PuerkitoBio/goquery
//Indeed is a struct
type Indeed struct {
id string
title string
location string
summary string
}
//Scrapper function
func Scrapper(query string) {
var jobs []Indeed
var baseURL string = "https://kr.indeed.com/jobs?q=" + query // + "&l=%EC%84%9C%EC%9A%B8"
c1 := make(chan []Indeed)
TotalPage := getPages(baseURL)
fmt.Println("TotalPage...", TotalPage)
for i := 0; i < TotalPage; i++ {
go getCard(i, baseURL, c1)
}
for i := 0; i < TotalPage; i++ {
extractJobs := <-c1
//merge slices or arrays
jobs = append(jobs, extractJobs...)
}
writeJobs(jobs)
fmt.Println("Done")
}
func writeJobs(jobs []Indeed) {
file, err := os.Create("jobs.csv")
checkErr(err)
w := csv.NewWriter(file)
//Write data to the file
defer w.Flush()
header := []string{"ID", "TITLE", "LOCATION", "SUMMARY"}
wErr := w.Write(header)
checkErr(wErr)
for _, job := range jobs {
jobSlice := []string{"https://kr.indeed.com/viewjob?jk=" + job.id, job.title, job.location, job.summary}
jobErr := w.Write(jobSlice)
checkErr(jobErr)
}
}
func getCard(page int, baseURL string, c1 chan []Indeed) {
var jobs []Indeed
c := make(chan Indeed)
URL := baseURL + "&start=" + strconv.Itoa(page*10)
fmt.Println(URL)
res, err := http.Get(URL)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
searchCards := doc.Find(".jobsearch-SerpJobCard")
searchCards.Each(func(i int, s *goquery.Selection) {
go extractJob(s, c)
})
for i := 0; i < searchCards.Length(); i++ {
job := <-c
jobs = append(jobs, job)
}
c1 <- jobs
}
func extractJob(s *goquery.Selection, c chan<- Indeed) {
id, _ := s.Attr("data-jk")
title := CleanString(s.Find(".title>a").Text())
location := CleanString(s.Find(".accessible-contrast-color-location").Text())
summary := CleanString(s.Find(".summary").Text())
c <- Indeed{
id: id,
title: title,
location: location,
summary: summary}
}
func getPages(baseURL string) 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-list").Each(func(i int, s *goquery.Selection) {
pages = s.Find("li").Length()
})
return pages
}
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalf("Status code err: %d %s", res.StatusCode, res.Status)
}
}
//CleanString function
func CleanString(str string) string {
return strings.Join(strings.Fields(strings.TrimSpace(str)), " ")
}
Full Code : home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width= , initial-scale=1.0">
<title>Go Project</title>
</head>
<body>
<h1>Go Project</h1>
<h3>Indeed.com scrapper</h3>
<form method="POST" action="/scrape">
<input placeholder="what job do u want" name="query"/>
<button>Search</button>
</form>
</body>
</html>
Full Code : main.go
package main
import (
"fmt"
"strings"
"github.com/labstack/echo"
scrapper "github.com/[github_id]/Scrapper"
)
var fileName = "jobs.csv"
//Handler function
func Handler(c echo.Context) error {
return c.File("home.html")
}
//HandleFunc function
func HandleFunc(c echo.Context) error {
query := strings.ToLower(scrapper.CleanString(c.FormValue("query")))
fmt.Println(query)
scrapper.Scrapper(query)
return c.Attachment(fileName, query+".csv")
}
func main() {
e := echo.New()
e.GET("/", Handler)
e.POST("/scrape", HandleFunc)
e.Logger.Fatal(e.Start(":1323"))
}
반응형