muquant_crawler

command module
v0.0.0-...-2927fed Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2021 License: MIT Imports: 16 Imported by: 0

README

muquant_crawler(돌연변이 퀀트) - My first GO project

GO 를 이용하여 취미로 시작하는 퀀트 프로젝트

  • Python pandas, BeautifulSoup를 이용하여 프로젝트 진행 중, 크롤링이 너무너무 오래 걸려 go lang으로 코딩해보기로 결심.
Step1) krx 종목코드 크롤링
  • request 이용하여 크롤링
  • 인코딩해결
  • gota 의 ReadHTML 사용
  • 필요한 정보만 파싱(종목코드, 회사명?) >> 2021-08-21 21h
  • test로 MariaDB에 넣어보기 >> 2021-08-21 22h 50m
  • mongoDB 에 넣어보기 (나중에)
Step2) 네이버 크롤링
  • 종목코드를 이용하여 전체 크롤링 >> 2021-08-23 01h
  • 크롤링한 정보를 DB에 삽입
  • 병렬처리 >> 2021-08-24 12h 40m (진행중)
  • 파이썬을 이용하여 크롤링하였으나, 너무 오래걸려서 학습겸 GO로 변경 잘될것으로 기대..

Python -> Go 대체

  • beautifulsoup : github.com/PuerkitoBio/goquery
  • pandas : github.com/go-gota/gota/dataframe, github.com/go-gota/gota/series

Memo

  • 디버깅시 unused 에러를 피하기 위해선, _ = df 와 같이 임시로 사용

  • 이중 slice 만들기 / 데이터 삽입은 append 이용

crawlStockInfo := make([][]string, 7)
  • request 헤더 변경
pageURL := fmt.Sprintf("%s&page=%d", url, pageNum)
req2, err := http.NewRequest("GET", pageURL, nil)
if err != nil {
	log.Fatal(err)
}
req2.Header.Add("User-agent", "Mozilla/5.0")

client2 := &http.Client{}
res2, _ := client2.Do(req2)
if res.StatusCode != 200 {
	log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
}
defer res2.Body.Close()
  • request 로 소스불러오기
resp, err := http.Get(URL_KRX)
if err != nil {
	panic(err)
}
defer resp.Body.Close()

data, err := ioutil.ReadAll(resp.Body)
if err != nil {
	panic(err)
}
  • jQuery 에서 원하는 정보 Find 후, 문자열 숫자로 변경
tempHref, _ := doc.Find(".pgRR a").Attr("href")
tempPgrr := strings.Split(tempHref, "=")
pgrr, _ := strconv.Atoi(tempPgrr[len(tempPgrr)-1])
  • jQuery 에서 Find, Each 사용
// tr을 찾은 후, tr의 자식노드(td)가 7인 경우만 로드를 시작.
// 자식노드(td)의 값을 하나씩 불러와서 화이트스페이스 제거 후, 값만 추출하여 slice에 삽입
doc2.Find("tr").Each(func(idx int, sel *goquery.Selection) {
	if sel.Find("td").Length() == 7 {
		sel.Find("td").Each(func(idx2 int, sel2 *goquery.Selection) {
			if idx2 == 2 {
				tt := strings.ReplaceAll(sel2.Text(), "\n", "")
				tt = strings.ReplaceAll(tt, "\t", "")
				crawlStockInfo[idx2] = append(crawlStockInfo[idx2], tt)
			} else {
				crawlStockInfo[idx2] = append(crawlStockInfo[idx2], sel2.Text())
			}
		})
	}
})
  • golang slice 사용 (make 로 미리 할당하나, append로 하나씩 넣어주나 시간복잡도 비슷한 것으로 추정)
    crawlStockInfo := make([][]string, 7)

  • 파일출력

file, err := os.Create("output.txt") // output.txt 파일 열기
if err != nil {
	panic(err)
}
defer file.Close() // main 함수가 끝나기 직전에 파일을 닫음
fmt.Fprint(file, "출력할 내용")
  • 시간비교
startTime := time.Now()
endTime := time.Now()
fmt.Println(endTime.Sub(startTime))
  • gota Rbind 이용하여 삽입
addRow := dataframe.New(
	series.New(crawlStockInfo[0], series.String, "date"),
	series.New(crawlStockInfo[1], series.String, "close"),
	series.New(crawlStockInfo[2], series.String, "diff"),
	series.New(crawlStockInfo[3], series.String, "open"),
	series.New(crawlStockInfo[4], series.String, "high"),
	series.New(crawlStockInfo[5], series.String, "low"),
	series.New(crawlStockInfo[6], series.String, "volume"),
)
df = df.RBind(addRow)
  • gota dataframe 만들기
df := dataframe.New(
	series.New(crawlStockInfo[0], series.String, "date"),
	series.New(crawlStockInfo[1], series.String, "close"),
	series.New(crawlStockInfo[2], series.Int, "diff"),
	series.New(crawlStockInfo[3], series.Int, "open"),
	series.New(crawlStockInfo[4], series.Int, "high"),
	series.New(crawlStockInfo[5], series.Int, "low"),
	series.New(crawlStockInfo[6], series.Int, "volume"),
)
  • gota 데이터변경(Series Map 사용)
// fill zero-padding
sel2 := sel1.Col("code")
zeroFill := func(e series.Element) series.Element {
	result := e.Copy()
	zeroResult := fmt.Sprintf("%06s", result.String())
	result.Set(zeroResult)
	return series.Element(result)
}
received := sel2.Map(zeroFill)
  • gota 데이터 이름 변경 및 선택
companyInfo = cs.Rename("company", "회사명").
	Rename("code", "종목코드").
	Select([]string{"code", "company"})
  • gota ReadHTML (option 넣어서 기본 타입을 String으로 설정) --> ReadHTML의 경우 th인식불가, td 안에 불필요한 데이터 있을 시 인식못함.
    cs := dataframe.ReadHTML(strings.NewReader(htmlUTF8), dataframe.DetectTypes(false), dataframe.DefaultType(series.String))[0]
  • mariaDB 접속 및 삽입
db, err := sql.Open("mysql", "root:password@tcp(ip:port)/DBNAME")
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	// Connect and check the server version
	//var version string
	sql := `CREATE TABLE IF NOT EXISTS company_info (
            	code VARCHAR(20),
                company VARCHAR(40),
                last_update DATE,
                PRIMARY KEY(CODE) )`
	db.Exec(sql)
  • 정규표현식으로 문자열 수정
re, _ := regexp.Compile(`(<[/]?)th`)
html := re.ReplaceAllString(string(data), "${1}td")
  • strings.ReplaceAll 사용
    strings.ReplaceAll("\n\nTEST\n\n", "\n", "")

  • 한글인코딩

// 한글인코딩이 깨져서, 한글을 UTF8로 변환? 헷갈리는 부분.
// 보통 NewEncoder()로 UTF8 -> euc-kr 로 변환해서 출력해야하는데...
// 이상하게 반대로 euc-kr -> UTF8 로 변환해서 출력해야 잘 됨.
htmlUTF8, _, err := transform.String(korean.EUCKR.NewDecoder(), html)
if err != nil {
	panic(err)
}
  • struct {} 의 경우 사이즈가 0이므로, 유용하게 사용할 수 있다.

  • 병렬처리

    • golang에서는 goroutine을 사용하여 병렬처리(동시성 시분할)
    • 병렬처리랑은 다르기 때문에, CPU 갯수를 늘려주어야 함
      runtime.GOMAXPROCS(runtime.NumCPU())
    • 또한 채널을 이용하여 수신받고, 송신하여 파이프 생성
runtime.GOMAXPROCS(runtime.NumCPU())
guard := make(chan struct{}, runtime.NumCPU())

seriesCompanyCode := companyInfo.Col("code")

for i := 0; i < seriesCompanyCode.Len(); i++ {
	code := seriesCompanyCode.Val(i).(string)
	guard <- struct{}{}		// 버퍼 해소시까지, 대기
	go func(s string) {
		ReadNaver(s)
		<-guard				// 수신을 함으로써 버퍼 1개 해소.
	}(code)
}

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL