Ver3.0 ๐ธ๏ธ Go์ธ์ด ํฌ๋กค๋ง๊ณผ Colly๋ฅผ ํ์ฉํ ์น ์คํฌ๋ํ ๋ฐ์ดํฐ ์์ง ์๋ฒฝ ๊ฐ์ด๋

๐ธ๏ธ Go์ธ์ด ํฌ๋กค๋ง๊ณผ Colly๋ฅผ ํ์ฉํ ์น ์คํฌ๋ํ ๋ฐ์ดํฐ ์์ง ์๋ฒฝ ๊ฐ์ด๋
์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ๋ฐฐ์ฐ๋ Go ์น ํฌ๋กค๋ง์ ๋ชจ๋ ๊ฒ
์๋ ! ๐ ์ค๋์ Go์ธ์ด๋ก ์น ํฌ๋กค๋ง์ ์ด๋ป๊ฒ ํ๋์ง, ๊ทธ๋ฆฌ๊ณ Colly๋ผ๋ ์์ฒญ ๊ฐ๋ ฅํ ํ๋ ์์ํฌ๋ฅผ ์ด๋ป๊ฒ ํ์ฉํ๋์ง ํจ๊ป ์์๋ณผ ๊ฑฐ์ผ. ํน์ "์น์์ ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ์์งํ๊ณ ์ถ์๋ฐ ์ด๋ป๊ฒ ์์ํด์ผ ํ ๊น?" ๊ณ ๋ฏผํ ์ ์์ด? ๊ทธ๋ ๋ค๋ฉด ์ ๋๋ก ์ฐพ์์์ด! ๐
์์ฆ ๋ฐ์ดํฐ๋ ์ ๋ง ์ค์ํ์์? ๊ฐ๊ฒฉ ๋น๊ต, ๋ด์ค ๋ชจ๋ํฐ๋ง, ์์ฅ ์กฐ์ฌ ๋ฑ ๋ค์ํ ๋ถ์ผ์์ ์น ์คํฌ๋ํ์ด ํ์ฉ๋๊ณ ์์ด. ๊ทธ๋ฆฌ๊ณ Go์ธ์ด๋ ์ด๋ฐ ์์ ์ ์ ๋ง ์ต์ ํ๋ ์ธ์ด์ผ. ๋น ๋ฅด๊ณ , ๋์์ฑ ์ฒ๋ฆฌ๊ฐ ๋ฐ์ด๋๊ณ , ๋ฉ๋ชจ๋ฆฌ ํจ์จ๋ ์ข๊ฑฐ๋ ! ๐
์ด ๊ธ์์๋ ๊ธฐ์ด๋ถํฐ ์ค์ ํ์ฉ๊น์ง, ๋ง์น ์์์ ์น๊ตฌ๊ฐ ์ค๋ช ํด์ฃผ๋ฏ์ด ์ฝ๊ณ ์ฌ๋ฏธ์๊ฒ ์๋ ค์ค๊ฒ. ํ๋ก๊ทธ๋๋ฐ์ ๋ฐฐ์ฐ๊ณ ์ถ๊ฑฐ๋ ์๋ก์ด ๊ธฐ์ ์ ์ตํ๊ณ ์ถ๋ค๋ฉด ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ ๋ฌธ๊ฐ๋ค์ ๋์์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ!
๐ฏ ์น ํฌ๋กค๋ง์ด๋ ๋ฌด์์ผ๊น?
๋จผ์ ๊ธฐ๋ณธ๋ถํฐ ์ง๊ณ ๋์ด๊ฐ์! ์น ํฌ๋กค๋ง(Web Crawling)์ ์ธํฐ๋ท์ ์๋ ์น ํ์ด์ง๋ค์ ์๋์ผ๋ก ํ์ํ๊ณ ์ ๋ณด๋ฅผ ์์งํ๋ ๊ธฐ์ ์ด์ผ. ๋ง์น ๊ฑฐ๋ฏธ(Spider)๊ฐ ๊ฑฐ๋ฏธ์ค์ ํ๊ณ ๋ค๋๋ฏ์ด ์น ํ์ด์ง๋ค์ ๋์๋ค๋๋ค๊ณ ํด์ 'ํฌ๋กค๋ง'์ด๋ผ๋ ์ด๋ฆ์ด ๋ถ์์ด. ๐ท๏ธ
๐ ์น ํฌ๋กค๋ง vs ์น ์คํฌ๋ํ
๋ง์ ์ฌ๋๋ค์ด ์ด ๋ ์ฉ์ด๋ฅผ ํผ์ฉํ๋๋ฐ, ์ฌ์ค ์ฝ๊ฐ์ ์ฐจ์ด๊ฐ ์์ด:
โข ์น ํฌ๋กค๋ง: ์น ํ์ด์ง๋ฅผ ์๋์ผ๋ก ํ์ํ๊ณ ์ธ๋ฑ์ฑํ๋ ๊ณผ์ (๊ฒ์์์ง์ด ํ๋ ์ผ)
โข ์น ์คํฌ๋ํ: ํน์ ์น ํ์ด์ง์์ ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํ๋ ๊ณผ์
ํ์ง๋ง ์ค๋ฌด์์๋ ๋ ์ฉ์ด๋ฅผ ๊ฑฐ์ ๊ฐ์ ์๋ฏธ๋ก ์ฌ์ฉํ๋๊น ๋๋ฌด ์ ๊ฒฝ ์ฐ์ง ์์๋ ๋ผ! ๐
๐ค ์ Go์ธ์ด๋ก ํฌ๋กค๋ง์ ํ ๊น?
Python์ด ์น ํฌ๋กค๋ง์์ ๊ฐ์ฅ ์ ๋ช ํ๊ธด ํ์ง๋ง, Go์ธ์ด๋ ์์ฒญ๋ ์ฅ์ ๋ค์ด ์์ด:
1. ๋๋ผ์ด ์๋ โก Go๋ ์ปดํ์ผ ์ธ์ด๋ผ์ Python๋ณด๋ค ํจ์ฌ ๋น ๋ฅด๊ฒ ์คํ๋ผ. ๋๋์ ๋ฐ์ดํฐ๋ฅผ ์์งํ ๋ ์ด ์ฐจ์ด๊ฐ ์ ๋ง ํฌ๊ฒ ๋๊ปด์ ธ!
2. ๊ณ ๋ฃจํด(Goroutine)์ ํตํ ๋์์ฑ ์ฒ๋ฆฌ ๐
์์ฒ ๊ฐ์ ์น ํ์ด์ง๋ฅผ ๋์์ ํฌ๋กค๋งํ ์ ์์ด. ์ด๊ฒ Go์ ๊ฐ์ฅ ํฐ ๊ฐ์ ์ด์ผ. ๋ค๋ฅธ ์ธ์ด์์๋ ๋ณต์กํ ๋ฉํฐ์ค๋ ๋ฉ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ์ง๋ง, Go์์๋ `go` ํค์๋ ํ๋๋ฉด ๋!
3. ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ ๐พ
๊ฐ๋น์ง ์ปฌ๋ ์
์ด ํจ์จ์ ์ผ๋ก ์๋ํด์ ์ฅ์๊ฐ ์คํ๋๋ ํฌ๋กค๋ฌ์๋ ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฑฑ์ ์ด ์ ์ด.
4. ๋จ์ผ ๋ฐ์ด๋๋ฆฌ ๋ฐฐํฌ ๐ฆ
์ปดํ์ผํ๋ฉด ํ๋์ ์คํ ํ์ผ๋ก ๋ง๋ค์ด์ ธ์ ๋ฐฐํฌ๊ฐ ์ ๋ง ๊ฐ๋จํด. ์์กด์ฑ ์ง์ฅ์์ ํด๋ฐฉ!
๐ ๏ธ Colly ํ๋ ์์ํฌ ์๊ฐ
Colly๋ Go์ธ์ด๋ก ์์ฑ๋ ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ์น ์คํฌ๋ํ ํ๋ ์์ํฌ์ผ. GitHub์์ 2๋ง ๊ฐ ์ด์์ ์คํ๋ฅผ ๋ฐ์ ๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ง! โญ
๐จ Colly์ ์ฃผ์ ํน์ง
โข ๊น๋ํ API: ์ง๊ด์ ์ด๊ณ ์ฌ์ฉํ๊ธฐ ์ฌ์ด ์ธํฐํ์ด์ค
โข ๋น ๋ฅธ ์๋: ์ด๋น ์์ฒ ๊ฐ์ ์์ฒญ ์ฒ๋ฆฌ ๊ฐ๋ฅ
โข ์๋ ์ฟ ํค ๋ฐ ์ธ์ ๊ด๋ฆฌ: ๋ก๊ทธ์ธ์ด ํ์ํ ์ฌ์ดํธ๋ ์ฝ๊ฒ ํฌ๋กค๋ง
โข ๋ก๋ด ๋ฐฐ์ ํ์ค ์ง์: robots.txt๋ฅผ ์๋์ผ๋ก ํ์ธ
โข ๋ถ์ฐ ํฌ๋กค๋ง: ์ฌ๋ฌ ์๋ฒ์์ ๋์์ ํฌ๋กค๋ง ๊ฐ๋ฅ
โข ์บ์ฑ ์ง์: ์ค๋ณต ์์ฒญ ๋ฐฉ์ง
๐ฅ Colly ์ค์นํ๊ธฐ
์ค์น๋ ์ ๋ง ๊ฐ๋จํด! Go ๋ชจ๋์ ์ฌ์ฉํ๋ค๋ฉด ๋ค์ ๋ช ๋ น์ด ํ๋๋ฉด ๋์ด์ผ:
go get -u github.com/gocolly/colly/v2
๐ก Tip: Go 1.16 ์ด์ ๋ฒ์ ์ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด `go mod init` ๋ช ๋ น์ด๋ก ๋จผ์ ๋ชจ๋์ ์ด๊ธฐํํด์ผ ํด. ํ๋ก์ ํธ ํด๋์์ `go mod init myproject` ์ด๋ฐ ์์ผ๋ก ์คํํ๋ฉด ๋ผ!
๐ ์ฒซ ๋ฒ์งธ ํฌ๋กค๋ฌ ๋ง๋ค๊ธฐ
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํด๋ณผ๊น? ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํฌ๋กค๋ฌ๋ถํฐ ์์ํด๋ณด์! ๐
๐ ๊ธฐ๋ณธ ์์ : ์น ํ์ด์ง ์ ๋ชฉ ๊ฐ์ ธ์ค๊ธฐ
package main
import (
"fmt"
"github.com/gocolly/colly/v2"
)
func main() {
// Collector ์์ฑ
c := colly.NewCollector()
// HTML ์์๋ฅผ ์ฐพ์์ ๋ ์คํ๋ ์ฝ๋ฐฑ ํจ์
c.OnHTML("h1", func(e *colly.HTMLElement) {
fmt.Println("์ ๋ชฉ:", e.Text)
})
// ์์ฒญ ์ ์ ์คํ๋ ์ฝ๋ฐฑ
c.OnRequest(func(r *colly.Request) {
fmt.Println("๋ฐฉ๋ฌธ ์ค:", r.URL)
})
// ํฌ๋กค๋ง ์์
c.Visit("https://example.com")
}
์ด๋? ์๊ฐ๋ณด๋ค ๊ฐ๋จํ์ง? ์ด ์ฝ๋๊ฐ ํ๋ ์ผ์ ๋จ๊ณ๋ณ๋ก ์ค๋ช ํด์ค๊ฒ:
Step 1: `colly.NewCollector()`๋ก ์๋ก์ด ์ปฌ๋ ํฐ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด. ์ด๊ฒ ํฌ๋กค๋ง์ ํต์ฌ์ด์ผ!
Step 2: `OnHTML()` ๋ฉ์๋๋ก ํน์ HTML ์์๋ฅผ ์ฐพ์์ ๋ ์คํํ ํจ์๋ฅผ ๋ฑ๋กํด. ์ฌ๊ธฐ์๋ `h1` ํ๊ทธ๋ฅผ ์ฐพ๊ณ ์์ด.
Step 3: `OnRequest()`๋ก ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ์ ์คํํ ํจ์๋ฅผ ๋ฑ๋กํด. ๋ก๊น ์ด๋ ๋๋ฒ๊น ์ ์ ์ฉํด!
Step 4: `Visit()` ๋ฉ์๋๋ก ์ค์ ํฌ๋กค๋ง์ ์์ํด. URL์ ๋๊ฒจ์ฃผ๋ฉด ์์์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์!
๐ฏ CSS ์ ํ์ ํ์ฉํ๊ธฐ
Colly๋ CSS ์ ํ์๋ฅผ ์ฌ์ฉํด์ HTML ์์๋ฅผ ์ฐพ์. ์น ๊ฐ๋ฐ ๊ฒฝํ์ด ์๋ค๋ฉด ์ต์ํ ๊ฑฐ์ผ! ๋ช ๊ฐ์ง ์์ ๋ฅผ ๋ณผ๊น?
// ํด๋์ค๋ก ์ฐพ๊ธฐ
c.OnHTML(".article-title", func(e *colly.HTMLElement) {
fmt.Println(e.Text)
})
// ID๋ก ์ฐพ๊ธฐ
c.OnHTML("#main-content", func(e *colly.HTMLElement) {
fmt.Println(e.Text)
})
// ์์ฑ์ผ๋ก ์ฐพ๊ธฐ
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
fmt.Println("๋งํฌ:", link)
})
// ๋ณต์กํ ์ ํ์
c.OnHTML("div.container > article.post h2.title", func(e *colly.HTMLElement) {
fmt.Println(e.Text)
})
๐ก Pro Tip: ๋ธ๋ผ์ฐ์ ์ ๊ฐ๋ฐ์ ๋๊ตฌ(F12)๋ฅผ ์ด๊ณ ์ํ๋ ์์์์ ์ฐํด๋ฆญ โ "Copy selector"๋ฅผ ์ ํํ๋ฉด CSS ์ ํ์๋ฅผ ์๋์ผ๋ก ๋ณต์ฌํ ์ ์์ด!
๐ฅ ์ค์ ์์ : ๋ด์ค ์ฌ์ดํธ ํฌ๋กค๋ง
์ด์ ์ข ๋ ์ค์ฉ์ ์ธ ์์ ๋ฅผ ๋ง๋ค์ด๋ณด์! ๋ด์ค ์ฌ์ดํธ์์ ๊ธฐ์ฌ ์ ๋ชฉ๊ณผ ๋งํฌ๋ฅผ ์์งํ๋ ํฌ๋กค๋ฌ์ผ. ๐ฐ
package main
import (
"fmt"
"github.com/gocolly/colly/v2"
)
type Article struct {
Title string
Link string
Date string
}
func main() {
var articles []Article
c := colly.NewCollector(
// ํ์ฉํ ๋๋ฉ์ธ ์ง์
colly.AllowedDomains("example-news.com"),
// ๋์ ์์ฒญ ์ ์ ํ
colly.Async(true),
)
// ์์ฒญ ์๋ ์ ํ (์ด๋น 2๊ฐ)
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 2,
RandomDelay: 1 * time.Second,
})
// ๊ธฐ์ฌ ์ ๋ณด ์์ง
c.OnHTML("article.news-item", func(e *colly.HTMLElement) {
article := Article{
Title: e.ChildText("h2.title"),
Link: e.ChildAttr("a", "href"),
Date: e.ChildText("time.published"),
}
articles = append(articles, article)
})
// ๋ค์ ํ์ด์ง ๋งํฌ ์ฐพ๊ธฐ
c.OnHTML("a.next-page", func(e *colly.HTMLElement) {
nextPage := e.Attr("href")
c.Visit(e.Request.AbsoluteURL(nextPage))
})
// ์๋ฌ ์ฒ๋ฆฌ
c.OnError(func(r *colly.Response, err error) {
fmt.Println("์๋ฌ ๋ฐ์:", r.StatusCode, err)
})
// ํฌ๋กค๋ง ์๋ฃ ํ
c.OnScraped(func(r *colly.Response) {
fmt.Printf("์ด %d๊ฐ์ ๊ธฐ์ฌ๋ฅผ ์์งํ์ต๋๋ค!\n", len(articles))
})
// ์์!
c.Visit("https://example-news.com/latest")
c.Wait()
// ๊ฒฐ๊ณผ ์ถ๋ ฅ
for _, article := range articles {
fmt.Printf("์ ๋ชฉ: %s\n๋งํฌ: %s\n๋ ์ง: %s\n\n",
article.Title, article.Link, article.Date)
}
}
์! ์ฝ๋๊ฐ ์ข ๊ธธ์ด์ก์ง? ํ์ง๋ง ๊ฑฑ์ ๋ง. ํ๋์ฉ ๋ฏ์ด๋ณด๋ฉด ์ด๋ ต์ง ์์! ๐ค
๐ ์ฝ๋ ๋ถ์
1. ๊ตฌ์กฐ์ฒด ์ ์
`Article` ๊ตฌ์กฐ์ฒด๋ก ์์งํ ๋ฐ์ดํฐ์ ํํ๋ฅผ ์ ์ํ์ด. ์ ๋ชฉ, ๋งํฌ, ๋ ์ง๋ฅผ ์ ์ฅํ ๊ฑฐ์ผ.
2. Collector ์ต์
`AllowedDomains`๋ก ํน์ ๋๋ฉ์ธ๋ง ํฌ๋กค๋งํ๋๋ก ์ ํํ์ด. ์ด๋ ๊ฒ ํ๋ฉด ์ธ๋ถ ๋งํฌ๋ก ๋น ์ ธ๋๊ฐ๋ ๊ฑธ ๋ฐฉ์งํ ์ ์์ง!
3. ์๋ ์ ํ
`Limit()` ๋ฉ์๋๋ก ์์ฒญ ์๋๋ฅผ ์ ํํ์ด. ์๋ฒ์ ๋ถ๋ด์ ์ฃผ์ง ์๊ธฐ ์ํด ๊ผญ ํ์ํ ๋ถ๋ถ์ด์ผ! ์ด๋น 2๊ฐ์ ์์ฒญ๋ง ๋ณด๋ด๊ณ , ๋๋ค ๋๋ ์ด๋ ์ถ๊ฐํ์ด.
4. ํ์ด์ง๋ค์ด์
"๋ค์ ํ์ด์ง" ๋งํฌ๋ฅผ ์ฐพ์์ ์๋์ผ๋ก ๊ณ์ ํฌ๋กค๋งํ๋๋ก ํ์ด. ์ด๋ ๊ฒ ํ๋ฉด ์ฌ๋ฌ ํ์ด์ง๋ฅผ ์๋์ผ๋ก ์์งํ ์ ์์ง!
5. ์๋ฌ ์ฒ๋ฆฌ
`OnError`๋ก ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง ์ ์ํ์ด. ์ค์ ์์๋ ๋ก๊น
์ด๋ ์ฌ์๋ ๋ก์ง์ ์ถ๊ฐํ๋ฉด ์ข์.
โ๏ธ ๊ณ ๊ธ ๊ธฐ๋ฅ ํ์ฉํ๊ธฐ
๊ธฐ๋ณธ์ ๋ง์คํฐํ์ผ๋ ์ด์ ์ข ๋ ๊ณ ๊ธ ๊ธฐ๋ฅ๋ค์ ์์๋ณด์! ๐ช
๐ ๋ก๊ทธ์ธ์ด ํ์ํ ์ฌ์ดํธ ํฌ๋กค๋ง
๋ง์ ์ฌ์ดํธ๋ค์ด ๋ก๊ทธ์ธ์ ์๊ตฌํ์ง? Colly๋ก๋ ์ด๊ฒ๋ ์ฝ๊ฒ ์ฒ๋ฆฌํ ์ ์์ด!
package main
import (
"github.com/gocolly/colly/v2"
"net/http"
"net/url"
)
func main() {
c := colly.NewCollector()
// ์ฟ ํค ์ ์ฅ์ ์ค์
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("User-Agent", "Mozilla/5.0...")
})
// ๋ก๊ทธ์ธ ํผ ๋ฐ์ดํฐ ์ค๋น
loginData := url.Values{
"username": {"your_username"},
"password": {"your_password"},
}
// ๋ก๊ทธ์ธ ์์ฒญ
err := c.Post("https://example.com/login", loginData)
if err != nil {
panic(err)
}
// ๋ก๊ทธ์ธ ํ ๋ณดํธ๋ ํ์ด์ง ์ ๊ทผ
c.OnHTML("div.protected-content", func(e *colly.HTMLElement) {
fmt.Println(e.Text)
})
c.Visit("https://example.com/protected-page")
}
๐ผ๏ธ ์ด๋ฏธ์ง ๋ค์ด๋ก๋ํ๊ธฐ
์ด๋ฏธ์ง๋ฅผ ์์งํ๊ณ ์ ์ฅํ๋ ๊ฒ๋ ๊ฐ๋ฅํด!
package main
import (
"github.com/gocolly/colly/v2"
"io"
"net/http"
"os"
"path"
)
func main() {
c := colly.NewCollector()
c.OnHTML("img[src]", func(e *colly.HTMLElement) {
imgURL := e.Request.AbsoluteURL(e.Attr("src"))
// ์ด๋ฏธ์ง ๋ค์ด๋ก๋
resp, err := http.Get(imgURL)
if err != nil {
return
}
defer resp.Body.Close()
// ํ์ผ๋ช
์ถ์ถ
fileName := path.Base(imgURL)
// ํ์ผ ์์ฑ
file, err := os.Create("images/" + fileName)
if err != nil {
return
}
defer file.Close()
// ์ด๋ฏธ์ง ์ ์ฅ
io.Copy(file, resp.Body)
fmt.Println("๋ค์ด๋ก๋ ์๋ฃ:", fileName)
})
c.Visit("https://example.com/gallery")
}
โ ๏ธ ์ฃผ์: ์ด๋ฏธ์ง๋ฅผ ๋ค์ด๋ก๋ํ๊ธฐ ์ ์ ํด๋น ์ฌ์ดํธ์ ์ด์ฉ์ฝ๊ด๊ณผ ์ ์๊ถ์ ๋ฐ๋์ ํ์ธํด์ผ ํด! ๋ฌด๋จ์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ์์งํ๋ฉด ๋ฒ์ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์์ด.
๐ ํ๋ก์ ์ฌ์ฉํ๊ธฐ
IP ์ฐจ๋จ์ ํผํ๊ฑฐ๋ ์ง์ญ ์ ํ์ ์ฐํํ๊ธฐ ์ํด ํ๋ก์๋ฅผ ์ฌ์ฉํ ์ ์์ด:
package main
import (
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/proxy"
)
func main() {
c := colly.NewCollector()
// ํ๋ก์ ์ค์
rp, err := proxy.RoundRobinProxySwitcher(
"socks5://127.0.0.1:1080",
"http://proxy1.com:8080",
"http://proxy2.com:8080",
)
if err != nil {
panic(err)
}
c.SetProxyFunc(rp)
// ํฌ๋กค๋ง ์์
c.Visit("https://example.com")
}
๐ ๋ฐ์ดํฐ๋ฅผ JSON์ผ๋ก ์ ์ฅํ๊ธฐ
์์งํ ๋ฐ์ดํฐ๋ฅผ JSON ํ์ผ๋ก ์ ์ฅํ๋ฉด ๋์ค์ ๋ถ์ํ๊ธฐ ํธํด!
package main
import (
"encoding/json"
"github.com/gocolly/colly/v2"
"os"
)
type Product struct {
Name string `json:"name"`
Price float64 `json:"price"`
URL string `json:"url"`
}
func main() {
var products []Product
c := colly.NewCollector()
c.OnHTML("div.product", func(e *colly.HTMLElement) {
product := Product{
Name: e.ChildText("h3.product-name"),
Price: parsePrice(e.ChildText("span.price")),
URL: e.ChildAttr("a", "href"),
}
products = append(products, product)
})
c.Visit("https://example-shop.com/products")
// JSON ํ์ผ๋ก ์ ์ฅ
file, err := os.Create("products.json")
if err != nil {
panic(err)
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
encoder.Encode(products)
fmt.Println("๋ฐ์ดํฐ๊ฐ products.json์ ์ ์ฅ๋์์ต๋๋ค!")
}
func parsePrice(priceStr string) float64 {
// ๊ฐ๊ฒฉ ๋ฌธ์์ด์ float64๋ก ๋ณํํ๋ ๋ก์ง
// ์ค์ ๋ก๋ ๋ ๋ณต์กํ ํ์ฑ์ด ํ์ํ ์ ์์ด
return 0.0
}
๐ญ User-Agent์ ํค๋ ์ค์
๋ง์ ์น์ฌ์ดํธ๋ค์ด ๋ด์ ์ฐจ๋จํ๊ธฐ ์ํด User-Agent๋ฅผ ์ฒดํฌํด. ๊ทธ๋์ ์ค์ ๋ธ๋ผ์ฐ์ ์ฒ๋ผ ๋ณด์ด๋๋ก ์ค์ ํ๋ ๊ฒ ์ค์ํด! ๐ญ
package main
import (
"github.com/gocolly/colly/v2"
"math/rand"
"time"
)
func main() {
c := colly.NewCollector()
// User-Agent ๋ชฉ๋ก
userAgents := []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
}
// ๋๋ค User-Agent ์ค์
rand.Seed(time.Now().UnixNano())
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("User-Agent", userAgents[rand.Intn(len(userAgents))])
r.Headers.Set("Accept", "text/html,application/xhtml+xml")
r.Headers.Set("Accept-Language", "ko-KR,ko;q=0.9,en-US;q=0.8")
r.Headers.Set("Referer", "https://www.google.com")
})
c.Visit("https://example.com")
}
๐ก Tip: ์ค์ ๋ธ๋ผ์ฐ์ ์ User-Agent๋ฅผ ๋ณต์ฌํด์ ์ฌ์ฉํ๋ฉด ๋ ์์ฐ์ค๋ฌ์! ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ์ Network ํญ์์ ํ์ธํ ์ ์์ด.
๐ฆ ์๋ ์ ํ๊ณผ ์์ ๋ฐ๋ฅธ ํฌ๋กค๋ง
ํฌ๋กค๋ง์ ํ ๋๋ ๋์ ์๋ฒ์ ๋ถ๋ด์ ์ฃผ์ง ์๋๋ก ์กฐ์ฌํด์ผ ํด. ์ด๊ฑด ๊ธฐ์ ์ ์ธ ๋ฌธ์ ์ด๊ธฐ๋ ํ์ง๋ง ์ค๋ฆฌ์ ์ธ ๋ฌธ์ ์ด๊ธฐ๋ ํด! ๐ค
๐ ํฌ๋กค๋ง ์ํฐ์ผ
1. robots.txt ํ์ธํ๊ธฐ
๋ชจ๋ ์น์ฌ์ดํธ๋ `robots.txt` ํ์ผ๋ก ํฌ๋กค๋ง ๊ท์น์ ๋ช
์ํด. ๋ฐ๋์ ํ์ธํ๊ณ ๋ฐ๋ผ์ผ ํด!
2. ์์ฒญ ์๋ ์ ํ
์ด๋น ์๋ฐฑ ๊ฐ์ ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ๊ฐ ๋ค์ด๋ ์ ์์ด. ์ ์ ํ ๋๋ ์ด๋ฅผ ๋์!
3. ์บ์ฑ ํ์ฉ
๊ฐ์ ํ์ด์ง๋ฅผ ๋ฐ๋ณตํด์ ์์ฒญํ์ง ๋ง๊ณ ์บ์ฑ์ ์ฌ์ฉํด.
4. ์๋ณ ๊ฐ๋ฅํ User-Agent
์์ ์ ํฌ๋กค๋ฌ๋ฅผ ์๋ณํ ์ ์๋ User-Agent๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ข์. ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ์ฐ๋ฝ๋ฐ์ ์ ์๋๋ก!
package main
import (
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/extensions"
"time"
)
func main() {
c := colly.NewCollector()
// robots.txt ์๋ ํ์ธ
c.IgnoreRobotsTxt = false
// ์์ฒญ ์๋ ์ ํ
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 2,
Delay: 2 * time.Second,
RandomDelay: 1 * time.Second,
})
// ๋๋ค User-Agent ์ฌ์ฉ (ํ์ฅ ๊ธฐ๋ฅ)
extensions.RandomUserAgent(c)
extensions.Referer(c)
// ์บ์ฑ ์ค์
c.CacheDir = "./cache"
c.OnRequest(func(r *colly.Request) {
fmt.Println("๋ฐฉ๋ฌธ:", r.URL)
})
c.Visit("https://example.com")
}
๐ ๋๋ฒ๊น ๊ณผ ์๋ฌ ์ฒ๋ฆฌ
ํฌ๋กค๋งํ๋ค ๋ณด๋ฉด ์์์น ๋ชปํ ์๋ฌ๋ค์ด ๋ง์ด ๋ฐ์ํด. ์ ๋๋ก ๋ ์๋ฌ ์ฒ๋ฆฌ์ ๋๋ฒ๊น ๋ฐฉ๋ฒ์ ์์๋๋ฉด ์ ๋ง ์ ์ฉํด! ๐ง
๐ ๋ก๊น ์ค์ ํ๊ธฐ
package main
import (
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/debug"
"log"
"os"
)
func main() {
// ๋ก๊ทธ ํ์ผ ์์ฑ
logFile, err := os.Create("crawler.log")
if err != nil {
panic(err)
}
defer logFile.Close()
// ๋ก๊ฑฐ ์ค์
logger := log.New(logFile, "", log.LstdFlags)
c := colly.NewCollector(
colly.Debugger(&debug.LogDebugger{}),
)
// ์์ฒญ ๋ก๊น
c.OnRequest(func(r *colly.Request) {
logger.Printf("๋ฐฉ๋ฌธ: %s\n", r.URL)
})
// ์๋ต ๋ก๊น
c.OnResponse(func(r *colly.Response) {
logger.Printf("์๋ต ๋ฐ์: %s (์ํ: %d)\n",
r.Request.URL, r.StatusCode)
})
// ์๋ฌ ๋ก๊น
c.OnError(func(r *colly.Response, err error) {
logger.Printf("์๋ฌ ๋ฐ์: %s - %v\n", r.Request.URL, err)
})
c.Visit("https://example.com")
}
๐ ์ฌ์๋ ๋ก์ง ๊ตฌํํ๊ธฐ
๋คํธ์ํฌ ๋ฌธ์ ๋ก ์์ฒญ์ด ์คํจํ ์ ์์ด. ์ฌ์๋ ๋ก์ง์ ์ถ๊ฐํ๋ฉด ์์ ์ฑ์ด ๋์์ ธ!
package main
import (
"github.com/gocolly/colly/v2"
"time"
)
func main() {
c := colly.NewCollector()
// ์ฌ์๋ ์ค์
maxRetries := 3
retryCount := make(map[string]int)
c.OnError(func(r *colly.Response, err error) {
url := r.Request.URL.String()
if retryCount[url] < maxRetries {
retryCount[url]++
fmt.Printf("์ฌ์๋ %d/%d: %s\n",
retryCount[url], maxRetries, url)
// ์ ์ ๋๊ธฐ ํ ์ฌ์๋
time.Sleep(2 * time.Second)
r.Request.Retry()
} else {
fmt.Printf("์ต๋ ์ฌ์๋ ํ์ ์ด๊ณผ: %s\n", url)
}
})
c.Visit("https://example.com")
}
๐พ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ธฐ
๋๋์ ๋ฐ์ดํฐ๋ฅผ ์์งํ๋ค๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๋ ๊ฒ ํจ์จ์ ์ด์ผ. MongoDB ์์ ๋ฅผ ๋ณผ๊น? ๐๏ธ
package main
import (
"context"
"github.com/gocolly/colly/v2"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)
type Article struct {
Title string `bson:"title"`
Content string `bson:"content"`
URL string `bson:"url"`
CreatedAt time.Time `bson:"created_at"`
}
func main() {
// MongoDB ์ฐ๊ฒฐ
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
client, err := mongo.Connect(ctx,
options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
panic(err)
}
defer client.Disconnect(ctx)
collection := client.Database("crawler").Collection("articles")
c := colly.NewCollector()
c.OnHTML("article", func(e *colly.HTMLElement) {
article := Article{
Title: e.ChildText("h2"),
Content: e.ChildText("p"),
URL: e.Request.URL.String(),
CreatedAt: time.Now(),
}
// MongoDB์ ์ ์ฅ
_, err := collection.InsertOne(ctx, article)
if err != nil {
fmt.Println("์ ์ฅ ์คํจ:", err)
} else {
fmt.Println("์ ์ฅ ์๋ฃ:", article.Title)
}
})
c.Visit("https://example.com/articles")
}
๐ก Tip: PostgreSQL์ด๋ MySQL์ ์ ํธํ๋ค๋ฉด `database/sql` ํจํค์ง๋ GORM ๊ฐ์ ORM์ ์ฌ์ฉํ ์ ์์ด. ๊ฐ์์ ํ๋ก์ ํธ ์๊ตฌ์ฌํญ์ ๋ง๊ฒ ์ ํํ๋ฉด ๋ผ!
๐ JavaScript๋ก ๋ ๋๋ง๋๋ ํ์ด์ง ํฌ๋กค๋ง
์์ฆ ๋ง์ ์น์ฌ์ดํธ๋ค์ด React, Vue ๊ฐ์ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํด์ JavaScript๋ก ์ฝํ ์ธ ๋ฅผ ๋ ๋๋งํด. ์ด๋ฐ ๊ฒฝ์ฐ์๋ Colly๋ง์ผ๋ก๋ ๋ถ์กฑํ๊ณ ํค๋๋ฆฌ์ค ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ํด! ๐
๐ค Chromedp ์ฌ์ฉํ๊ธฐ
Chromedp๋ Go์์ Chrome์ ์ ์ดํ ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ผ. Colly์ ํจ๊ป ์ฌ์ฉํ๋ฉด ๊ฐ๋ ฅํด!
package main
import (
"context"
"github.com/chromedp/chromedp"
"github.com/gocolly/colly/v2"
"time"
)
func main() {
// Chrome ์ปจํ
์คํธ ์์ฑ
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
// ํ์์์ ์ค์
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
var htmlContent string
// JavaScript ์คํ ํ HTML ๊ฐ์ ธ์ค๊ธฐ
err := chromedp.Run(ctx,
chromedp.Navigate("https://example-spa.com"),
chromedp.WaitVisible("div.content", chromedp.ByQuery),
chromedp.Sleep(2*time.Second),
chromedp.OuterHTML("html", &htmlContent),
)
if err != nil {
panic(err)
}
// Colly๋ก ํ์ฑ
c := colly.NewCollector()
c.OnHTML("div.dynamic-content", func(e *colly.HTMLElement) {
fmt.Println("์ ๋ชฉ:", e.ChildText("h2"))
})
c.OnHTML(htmlContent, func(e *colly.HTMLElement) {
// HTML ํ์ฑ
})
}
โ ๏ธ ์ฃผ์: ํค๋๋ฆฌ์ค ๋ธ๋ผ์ฐ์ ๋ ๋ฆฌ์์ค๋ฅผ ๋ง์ด ์ฌ์ฉํด. ๋๋ ํฌ๋กค๋ง์๋ ์ ํฉํ์ง ์์ ์ ์์ผ๋ ๊ผญ ํ์ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํ์!
๐ ์ค์ ํ๋ก์ ํธ: ๊ฐ๊ฒฉ ๋น๊ต ํฌ๋กค๋ฌ
์ด์ ๋ฐฐ์ด ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด์! ์ฌ๋ฌ ์ผํ๋ชฐ์ ๊ฐ๊ฒฉ์ ๋น๊ตํ๋ ํฌ๋กค๋ฌ์ผ. ๐๏ธ
package main
import (
"encoding/csv"
"fmt"
"github.com/gocolly/colly/v2"
"os"
"strconv"
"strings"
"time"
)
type Product struct {
Name string
Price float64
Shop string
URL string
ImageURL string
}
func main() {
products := []Product{}
// ์ผํ๋ชฐ๋ณ ํฌ๋กค๋ฌ ์ค์
shops := map[string]string{
"ShopA": "https://shop-a.com/search?q=",
"ShopB": "https://shop-b.com/products?keyword=",
}
keyword := "๋ฌด์ ์ด์ดํฐ"
for shopName, baseURL := range shops {
c := colly.NewCollector(
colly.AllowedDomains(getDomain(baseURL)),
)
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 1,
Delay: 2 * time.Second,
})
// ShopA ํฌ๋กค๋ง
if shopName == "ShopA" {
c.OnHTML("div.product-item", func(e *colly.HTMLElement) {
product := Product{
Name: e.ChildText("h3.product-name"),
Price: parsePrice(e.ChildText("span.price")),
Shop: shopName,
URL: e.ChildAttr("a", "href"),
ImageURL: e.ChildAttr("img", "src"),
}
products = append(products, product)
})
}
// ShopB ํฌ๋กค๋ง
if shopName == "ShopB" {
c.OnHTML("article.item", func(e *colly.HTMLElement) {
product := Product{
Name: e.ChildText("div.title"),
Price: parsePrice(e.ChildText("div.price")),
Shop: shopName,
URL: e.ChildAttr("a.link", "href"),
ImageURL: e.ChildAttr("img.thumb", "src"),
}
products = append(products, product)
})
}
c.Visit(baseURL + keyword)
}
// ๊ฐ๊ฒฉ์ ์ ๋ ฌ
sortByPrice(products)
// CSV๋ก ์ ์ฅ
saveToCSV(products, "price_comparison.csv")
// ๊ฒฐ๊ณผ ์ถ๋ ฅ
fmt.Printf("\n์ด %d๊ฐ์ ์ํ์ ์ฐพ์์ต๋๋ค!\n\n", len(products))
for i, p := range products {
if i >= 10 {
break
}
fmt.Printf("%d. %s - โฉ%.0f (%s)\n",
i+1, p.Name, p.Price, p.Shop)
}
}
func parsePrice(priceStr string) float64 {
// ๊ฐ๊ฒฉ ๋ฌธ์์ด์์ ์ซ์๋ง ์ถ์ถ
priceStr = strings.ReplaceAll(priceStr, ",", "")
priceStr = strings.ReplaceAll(priceStr, "์", "")
priceStr = strings.TrimSpace(priceStr)
price, err := strconv.ParseFloat(priceStr, 64)
if err != nil {
return 0
}
return price
}
func getDomain(url string) string {
// URL์์ ๋๋ฉ์ธ ์ถ์ถ
parts := strings.Split(url, "/")
if len(parts) >= 3 {
return parts[2]
}
return ""
}
func sortByPrice(products []Product) {
// ๊ฐ๋จํ ๋ฒ๋ธ ์ ๋ ฌ
for i := 0; i < len(products); i++ {
for j := i + 1; j < len(products); j++ {
if products[i].Price > products[j].Price {
products[i], products[j] = products[j], products[i]
}
}
}
}
func saveToCSV(products []Product, filename string) {
file, err := os.Create(filename)
if err != nil {
fmt.Println("CSV ํ์ผ ์์ฑ ์คํจ:", err)
return
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// ํค๋ ์์ฑ
writer.Write([]string{"์ํ๋ช
", "๊ฐ๊ฒฉ", "์ผํ๋ชฐ", "URL", "์ด๋ฏธ์งURL"})
// ๋ฐ์ดํฐ ์์ฑ
for _, p := range products {
writer.Write([]string{
p.Name,
fmt.Sprintf("%.0f", p.Price),
p.Shop,
p.URL,
p.ImageURL,
})
}
fmt.Println("CSV ํ์ผ ์ ์ฅ ์๋ฃ:", filename)
}
์ด ํ๋ก์ ํธ๋ ์ฌ๋ฌ ์ผํ๋ชฐ์ ๋์์ ํฌ๋กค๋งํด์ ๊ฐ๊ฒฉ์ ๋น๊ตํ๊ณ CSV ํ์ผ๋ก ์ ์ฅํด. ์ค์ ๋ก ์ฌ์ฉํ๋ ค๋ฉด ๊ฐ ์ผํ๋ชฐ์ HTML ๊ตฌ์กฐ์ ๋ง๊ฒ ์ ํ์๋ฅผ ์์ ํด์ผ ํด! ๐
๐ ์ฑ๋ฅ ์ต์ ํ ํ
ํฌ๋กค๋ฌ์ ์ฑ๋ฅ์ ์ต๋๋ก ๋์ด์ฌ๋ฆฌ๋ ๋ฐฉ๋ฒ๋ค์ ์์๋ณด์! ๐
โก ์ต์ ํ ์ ๋ต
1. ๊ณ ๋ฃจํด ํ์ฉ
Go์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ธ ๊ณ ๋ฃจํด์ ์ ๊ทน ํ์ฉํ์. Colly๋ `Async(true)` ์ต์
์ผ๋ก ์ฝ๊ฒ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์์ด.
2. ์บ์ฑ ์ ๋ต
๊ฐ์ ํ์ด์ง๋ฅผ ๋ฐ๋ณตํด์ ์์ฒญํ์ง ์๋๋ก ์บ์ฑ์ ์ฌ์ฉํด. ๋์คํฌ ์บ์๋ Redis๋ฅผ ํ์ฉํ ์ ์์ด.
3. ์ ํ์ ๋ค์ด๋ก๋
ํ์ํ ๋ฐ์ดํฐ๋ง ๋ค์ด๋ก๋ํ์. ์ด๋ฏธ์ง๋ CSS ํ์ผ์ ํ์ ์๋ค๋ฉด ์ฐจ๋จํด.
4. ์ฐ๊ฒฐ ํ๋ง
HTTP ์ฐ๊ฒฐ์ ์ฌ์ฌ์ฉํ๋ฉด ์ฑ๋ฅ์ด ํฌ๊ฒ ํฅ์๋ผ. Colly๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฐ๊ฒฐ ํ๋ง์ ์ง์ํด.
5. ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
๋๋ ํฌ๋กค๋ง ์ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์กฐ์ฌํด์ผ ํด. ์ฃผ๊ธฐ์ ์ผ๋ก ๊ฐ๋น์ง ์ปฌ๋ ์
์ ์คํํ๊ฑฐ๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ์ ์ฅํ๊ณ ๋ฉ๋ชจ๋ฆฌ์์ ์ ๊ฑฐํ์.
package main
import (
"github.com/gocolly/colly/v2"
"runtime"
"time"
)
func main() {
c := colly.NewCollector(
colly.Async(true),
colly.CacheDir("./cache"),
)
// ๋์ ์์ฒญ ์ ์ต์ ํ
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 10, // CPU ์ฝ์ด ์์ ๋ง๊ฒ ์กฐ์
Delay: 100 * time.Millisecond,
})
// ๋ถํ์ํ ๋ฆฌ์์ค ์ฐจ๋จ
c.OnRequest(func(r *colly.Request) {
// ์ด๋ฏธ์ง, CSS, JS ํ์ผ ์ฐจ๋จ
if strings.HasSuffix(r.URL.Path, ".jpg") ||
strings.HasSuffix(r.URL.Path, ".css") ||
strings.HasSuffix(r.URL.Path, ".js") {
r.Abort()
}
})
// ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
processedCount := 0
c.OnScraped(func(r *colly.Response) {
processedCount++
// 1000๊ฐ๋ง๋ค ๊ฐ๋น์ง ์ปฌ๋ ์
if processedCount%1000 == 0 {
runtime.GC()
fmt.Printf("์ฒ๋ฆฌ ์๋ฃ: %d๊ฐ\n", processedCount)
}
})
// ํฌ๋กค๋ง ์์
for i := 1; i <= 100; i++ {
c.Visit(fmt.Sprintf("https://example.com/page/%d", i))
}
c.Wait()
}
๐ก๏ธ ๋ฒ์ ๊ณ ๋ ค์ฌํญ๊ณผ ์ค๋ฆฌ
๊ธฐ์ ์ ์ผ๋ก ๊ฐ๋ฅํ๋ค๊ณ ํด์ ๋ชจ๋ ๊ฑธ ํฌ๋กค๋งํด๋ ๋๋ ๊ฑด ์๋์ผ. ๋ฒ์ , ์ค๋ฆฌ์ ์ธก๋ฉด์ ๊ผญ ๊ณ ๋ คํด์ผ ํด! โ๏ธ
โ ๏ธ ์ฃผ์ํด์ผ ํ ์ฌํญ๋ค
1. ์ด์ฉ์ฝ๊ด ํ์ธ
๋๋ถ๋ถ์ ์น์ฌ์ดํธ๋ ์ด์ฉ์ฝ๊ด์์ ์๋ํ๋ ์ ๊ทผ์ ์ ํํ๊ณ ์์ด. ๋ฐ๋์ ํ์ธํ๊ณ ํ์ํ๋ฉด ํ๊ฐ๋ฅผ ๋ฐ์.
2. ๊ฐ์ธ์ ๋ณด ๋ณดํธ
๊ฐ์ธ์ ๋ณด๋ฅผ ์์งํ๋ฉด ๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ ์๋ฐ์ด ๋ ์ ์์ด. ํนํ ์ด๋ฉ์ผ, ์ ํ๋ฒํธ ๊ฐ์ ๋ฏผ๊ฐํ ์ ๋ณด๋ ์กฐ์ฌํด์ผ ํด.
3. ์ ์๊ถ
์น์ฌ์ดํธ์ ์ฝํ
์ธ ๋ ์ ์๊ถ์ผ๋ก ๋ณดํธ๋ฐ์. ์์งํ ๋ฐ์ดํฐ๋ฅผ ์์
์ ์ผ๋ก ์ฌ์ฉํ๋ ค๋ฉด ์ ์๊ถ์์ ํ๊ฐ๊ฐ ํ์ํด.
4. ์๋ฒ ๋ถํ
๊ณผ๋ํ ์์ฒญ์ผ๋ก ์๋ฒ์ ๋ถ๋ด์ ์ฃผ๋ฉด ์
๋ฌด๋ฐฉํด์ฃ๊ฐ ์ฑ๋ฆฝ๋ ์ ์์ด. ์ ์ ํ ์๋ ์ ํ์ ๊ผญ ์ค์ ํ์.
5. API ์ฐ์ ์ฌ์ฉ
๋ง์ ์๋น์ค๋ค์ด ๊ณต์ API๋ฅผ ์ ๊ณตํด. ํฌ๋กค๋ง๋ณด๋ค๋ API๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ๋ ์์ ํ๊ณ ํฉ๋ฒ์ ์ด์ผ.
๐ก Pro Tip: ํฌ๋กค๋ง ํ๋ก์ ํธ๋ฅผ ์์ํ๊ธฐ ์ ์ ๋ฒ๋ฅ ์ ๋ฌธ๊ฐ์ ์๋ดํ๋ ๊ฑธ ์ถ์ฒํด. ํนํ ์์ ์ ๋ชฉ์ ์ด๋ผ๋ฉด ๋์ฑ ์ค์ํด! ์ฌ๋ฅ๋ท์์ ๋ฒ๋ฅ ์๋ฌธ ์๋น์ค๋ฅผ ์ฐพ์๋ณผ ์๋ ์์ด.
๐ง ์ค์ ๋ฌธ์ ํด๊ฒฐ
ํฌ๋กค๋งํ๋ค ๋ณด๋ฉด ์์ฃผ ๋ง์ฃผ์น๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์์๋ณด์! ๐
โ ๋ฌธ์ 1: 403 Forbidden ์๋ฌ
์์ธ: ์๋ฒ๊ฐ ๋ด์ผ๋ก ์ธ์ํด์ ์ ๊ทผ์ ์ฐจ๋จํ์ด.
ํด๊ฒฐ:
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("User-Agent", "Mozilla/5.0...")
r.Headers.Set("Accept", "text/html,application/xhtml+xml")
r.Headers.Set("Accept-Language", "ko-KR,ko;q=0.9")
r.Headers.Set("Referer", "https://www.google.com")
r.Headers.Set("Connection", "keep-alive")
})
โ ๋ฌธ์ 2: ๋น ๋ฐ์ดํฐ ๋ฐํ
์์ธ: JavaScript๋ก ๋์ ์ผ๋ก ๋ก๋๋๋ ์ฝํ ์ธ ์ผ ๊ฐ๋ฅ์ฑ์ด ๋์.
ํด๊ฒฐ: Chromedp ๊ฐ์ ํค๋๋ฆฌ์ค ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋, ๋คํธ์ํฌ ํญ์์ ์ค์ API ์๋ํฌ์ธํธ๋ฅผ ์ฐพ์์ ์ง์ ํธ์ถํด.
โ ๋ฌธ์ 3: ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ
์์ธ: ๋๋์ ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ๊ณ ์์ด.
ํด๊ฒฐ:
// ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ํ์ผ์ด๋ DB์ ์ ์ฅ
c.OnHTML("div.item", func(e *colly.HTMLElement) {
data := extractData(e)
saveToDatabase(data) // ์ฆ์ ์ ์ฅ
// ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ์ง ์์
})
// ์ฃผ๊ธฐ์ ์ผ๋ก GC ์คํ
if count%1000 == 0 {
runtime.GC()
}
โ ๋ฌธ์ 4: IP ์ฐจ๋จ
์์ธ: ๊ฐ์ IP์์ ๋๋ฌด ๋ง์ ์์ฒญ์ ๋ณด๋์ด.
ํด๊ฒฐ:
- โข ์์ฒญ ์๋๋ฅผ ์ค์ฌ
- โข ํ๋ก์๋ฅผ ์ฌ์ฉํด
- โข VPN์ ํ์ฉํด
- โข ์๊ฐ๋๋ฅผ ๋ถ์ฐํด์ ํฌ๋กค๋งํด
โ ๋ฌธ์ 5: CAPTCHA
์์ธ: ์ฌ์ดํธ๊ฐ ๋ด ๋ฐฉ์ง๋ฅผ ์ํด CAPTCHA๋ฅผ ์ฌ์ฉํด.
ํด๊ฒฐ:
- โข ์์ฒญ ํจํด์ ๋ ์์ฐ์ค๋ฝ๊ฒ ๋ง๋ค์ด
- โข ์ฟ ํค์ ์ธ์ ์ ์ ์ ํ ๊ด๋ฆฌํด
- โข CAPTCHA ํด๊ฒฐ ์๋น์ค๋ฅผ ์ฌ์ฉํด (2Captcha ๋ฑ)
- โข ๊ฐ๋ฅํ๋ฉด API๋ฅผ ์ฌ์ฉํด
๐ ์ถ๊ฐ ํ์ต ๋ฆฌ์์ค
๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฐ ์๋ฃ๋ค์ ์ฐธ๊ณ ํด๋ด! ๐
๐ ์ ์ฉํ ๋งํฌ๋ค
๊ณต์ ๋ฌธ์:
โข Colly ๊ณต์ ๋ฌธ์: http://go-colly.org/
โข Go ๊ณต์ ๋ฌธ์: https://golang.org/doc/
GitHub ์ ์ฅ์:
โข Colly: https://github.com/gocolly/colly
โข Chromedp: https://github.com/chromedp/chromedp
์ปค๋ฎค๋ํฐ:
โข Go ํ๊ตญ ์ปค๋ฎค๋ํฐ
โข Reddit r/golang
โข Stack Overflow
๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ:
โข goquery: jQuery ์คํ์ผ์ HTML ํ์ฑ
โข soup: Python BeautifulSoup ์คํ์ผ
โข rod: ๋ ๋ค๋ฅธ ํค๋๋ฆฌ์ค ๋ธ๋ผ์ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๐ฏ ์ค์ ํ๋ก์ ํธ ์์ด๋์ด
๋ฐฐ์ด ๋ด์ฉ์ ํ์ฉํด์ ์ด๋ฐ ํ๋ก์ ํธ๋ค์ ๋ง๋ค์ด๋ณผ ์ ์์ด! ๐ก
1. ๋ถ๋์ฐ ๊ฐ๊ฒฉ ๋ชจ๋ํฐ๋ง ์์คํ ๐
๋ถ๋์ฐ ์ฌ์ดํธ๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ํฌ๋กค๋งํด์ ๊ฐ๊ฒฉ ๋ณ๋์ ์ถ์ ํ๊ณ ์๋ฆผ์ ๋ณด๋ด๋ ์์คํ . ์ํ๋ ์กฐ๊ฑด์ ๋งค๋ฌผ์ด ๋์ค๋ฉด ์ฆ์ ์๋ ค์ค!
2. ๋ด์ค ์ ๊ทธ๋ฆฌ๊ฒ์ดํฐ ๐ฐ
์ฌ๋ฌ ๋ด์ค ์ฌ์ดํธ์์ ํน์ ํค์๋ ๊ด๋ จ ๊ธฐ์ฌ๋ฅผ ์์งํ๊ณ ์์ฝํด์ ๋ณด์ฌ์ฃผ๋ ์๋น์ค. ํธ๋ ๋ ๋ถ์๋ ๊ฐ๋ฅํด!
3. ์ฑ์ฉ ๊ณต๊ณ ์๋ฆผ ์๋น์ค ๐ผ
์ฌ๋ฌ ์ฑ์ฉ ์ฌ์ดํธ๋ฅผ ํฌ๋กค๋งํด์ ์ํ๋ ์กฐ๊ฑด์ ๊ณต๊ณ ๊ฐ ์ฌ๋ผ์ค๋ฉด ์๋ฆผ์ ๋ณด๋ด๋ ์๋น์ค. ์ทจ์ ์ค๋น์๋ค์๊ฒ ์ ์ฉํด!
4. ์์ ๋ฏธ๋์ด ๋ถ์ ๋๊ตฌ ๐
ํน์ ์ฃผ์ ๋ ๋ธ๋๋์ ๋ํ ์์ ๋ฏธ๋์ด ๋ฐ์์ ์์งํ๊ณ ๋ถ์ํ๋ ๋๊ตฌ. ๋ง์ผํ ์ ํ์ฉํ ์ ์์ด!
5. ํ์ ๋ ผ๋ฌธ ๊ฒ์ ์์ง ๐
์ฌ๋ฌ ํ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ํฌ๋กค๋งํด์ ํตํฉ ๊ฒ์ ์๋น์ค๋ฅผ ๋ง๋ค์ด. ์ฐ๊ตฌ์๋ค์๊ฒ ์ ์ฉํ ๊ฑฐ์ผ!
๐ ๋ค์ ๋จ๊ณ๋ก
์ฌ๊ธฐ๊น์ง ์๋ค๋ฉด ์ด๋ฏธ Go ํฌ๋กค๋ง์ ๊ธฐ์ด๋ ๋ง์คํฐํ ๊ฑฐ์ผ! ์ถํํด! ๐
๐ฏ ๋ ๋์๊ฐ๊ธฐ
1. ๋ถ์ฐ ํฌ๋กค๋ง
์ฌ๋ฌ ์๋ฒ์์ ๋์์ ํฌ๋กค๋งํ๋ ์์คํ
์ ๊ตฌ์ถํด๋ด. Kafka๋ RabbitMQ ๊ฐ์ ๋ฉ์์ง ํ๋ฅผ ํ์ฉํ๋ฉด ์ข์.
2. ๋จธ์ ๋ฌ๋ ํตํฉ
์์งํ ๋ฐ์ดํฐ๋ฅผ ๋จธ์ ๋ฌ๋ ๋ชจ๋ธ์ ํ์ฉํด๋ด. ๊ฐ์ฑ ๋ถ์, ๋ถ๋ฅ, ์์ธก ๋ฑ ๋ค์ํ ์์ฉ์ด ๊ฐ๋ฅํด!
3. ์ค์๊ฐ ๋ชจ๋ํฐ๋ง
์น์์ผ์ด๋ Server-Sent Events๋ฅผ ํ์ฉํด์ ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์์งํ๊ณ ์ฒ๋ฆฌํด๋ด.
4. ํด๋ผ์ฐ๋ ๋ฐฐํฌ
AWS Lambda, Google Cloud Functions ๊ฐ์ ์๋ฒ๋ฆฌ์ค ํ๊ฒฝ์์ ํฌ๋กค๋ฌ๋ฅผ ์คํํด๋ด. ๋น์ฉ ํจ์จ์ ์ด์ผ!
5. ๋ชจ๋ํฐ๋ง๊ณผ ์๋ฆผ
Prometheus, Grafana ๊ฐ์ ๋๊ตฌ๋ก ํฌ๋กค๋ฌ์ ์ํ๋ฅผ ๋ชจ๋ํฐ๋งํ๊ณ , ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด Slack์ด๋ ์ด๋ฉ์ผ๋ก ์๋ฆผ์ ๋ฐ๋๋ก ์ค์ ํด๋ด.
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ ๋ง ๊ธด ์ฌ์ ์ด์์ง? ๐ Go์ธ์ด์ Colly๋ฅผ ํ์ฉํ ์น ํฌ๋กค๋ง์ ๋ํด ์ ๋ง ๋ง์ ๊ฑธ ๋ฐฐ์ ์ด. ๊ธฐ์ด๋ถํฐ ๊ณ ๊ธ ๊ธฐ๋ฅ, ์ค์ ํ๋ก์ ํธ๊น์ง ๋ค๋ค์ผ๋ ์ด์ ์ง์ ํฌ๋กค๋ฌ๋ฅผ ๋ง๋ค์ด๋ณผ ์ฐจ๋ก์ผ!
ํต์ฌ ์์ฝ ์ ๋ฆฌ:
โ
Go์ธ์ด๋ ๋น ๋ฅธ ์๋์ ๋ฐ์ด๋ ๋์์ฑ ์ฒ๋ฆฌ๋ก ํฌ๋กค๋ง์ ์ต์ ํ๋์ด ์์ด
โ
Colly๋ ์ง๊ด์ ์ด๊ณ ๊ฐ๋ ฅํ ์น ์คํฌ๋ํ ํ๋ ์์ํฌ์ผ
โ
์ ์ ํ ์๋ ์ ํ๊ณผ ์๋ฌ ์ฒ๋ฆฌ๋ ํ์์ผ
โ
robots.txt๋ฅผ ํ์ธํ๊ณ ๋ฒ์ , ์ค๋ฆฌ์ ์ธก๋ฉด์ ๊ณ ๋ คํด์ผ ํด
โ
์ค์ ์์๋ ๋ก๊น
, ์ฌ์๋, ์บ์ฑ ๋ฑ์ ๊ธฐ๋ฅ์ด ์ค์ํด
โ
JavaScript ๋ ๋๋ง ํ์ด์ง๋ ํค๋๋ฆฌ์ค ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ํด
โ
๋ฐ์ดํฐ๋ ์ ์ ํ ํ์(JSON, CSV, DB)์ผ๋ก ์ ์ฅํ์
ํฌ๋กค๋ง์ ์ ๋ง ์ ์ฉํ ๊ธฐ์ ์ด์ง๋ง, ์ฑ ์๊ฐ ์๊ฒ ์ฌ์ฉํด์ผ ํด. ํญ์ ๋์ ์น์ฌ์ดํธ์ ์ด์ฉ์ฝ๊ด์ ํ์ธํ๊ณ , ์๋ฒ์ ๋ถ๋ด์ ์ฃผ์ง ์๋๋ก ์กฐ์ฌํ์. ๊ทธ๋ฆฌ๊ณ ๊ฐ์ธ์ ๋ณด๋ ์ ์๊ถ์ด ์๋ ์ฝํ ์ธ ๋ ํนํ ์ฃผ์ํด์ผ ํด! ๐ก๏ธ
ํ๋ก๊ทธ๋๋ฐ์ ๋ฐฐ์ฐ๋ ๊ฑด ํผ์์๋ ๊ฐ๋ฅํ์ง๋ง, ๋๋ก๋ ์ ๋ฌธ๊ฐ์ ๋์์ด ํ์ํ ๋๊ฐ ์์ด. ๋งํ๋ ๋ถ๋ถ์ด ์๊ฑฐ๋ ๋ ๊น์ด ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ๊ฒฝํ ๋ง์ ๊ฐ๋ฐ์๋ค์๊ฒ ๋ฉํ ๋ง์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ์ค๋ฌด ๊ฒฝํ์ ๊ฐ์ง ์ฌ๋๋ค์ ์กฐ์ธ์ ์ ๋ง ๊ฐ์ง๊ฑฐ๋ ! ๐
๐ก ๋ง์ง๋ง ์กฐ์ธ: ์์ ํ๋ก์ ํธ๋ถํฐ ์์ํด์ ์ ์ ๋ณต์กํ ๊ฑธ ๋ง๋ค์ด๋ด. ์คํจ๋ฅผ ๋๋ ค์ํ์ง ๋ง๊ณ , ์๋ฌ ๋ฉ์์ง๋ฅผ ์น๊ตฌ์ฒ๋ผ ๋ํ์. ์๋ฌ๋ ์ฐ๋ฆฌ๊ฐ ๋ญ๊ฐ๋ฅผ ๋ฐฐ์ฐ๊ณ ์๋ค๋ ์ฆ๊ฑฐ๋๊น! ํ์ดํ ! ๐ช
์ด์ ๋น์ ๋ Go ํฌ๋กค๋ง ๋ง์คํฐ๊ฐ ๋ ์ค๋น๊ฐ ๋์ด! ๋ฉ์ง ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด๊ณ , ๋ฐฐ์ด ๋ด์ฉ์ ๋ค๋ฅธ ์ฌ๋๋ค๊ณผ๋ ๊ณต์ ํด๋ด. ํ๋ก๊ทธ๋๋ฐ ์ปค๋ฎค๋ํฐ๋ ์๋ก ๋์ฐ๋ฉฐ ์ฑ์ฅํ๋ ๊ณณ์ด๋๊น! ๐
ํ์ด์ ๋น์ด! ๊ทธ๋ฆฌ๊ณ ์ฆ๊ฑฐ์ด ์ฝ๋ฉ ๋๊ธธ! Happy Coding! ๐โจ
์ด ๊ธ์ด ๋์์ด ๋์๋ค๋ฉด ์ฃผ๋ณ ์ฌ๋๋ค๊ณผ ๊ณต์ ํด์ฃผ์ธ์! ๐
์ง๋ฌธ์ด๋ ํผ๋๋ฐฑ์ด ์๋ค๋ฉด ์ธ์ ๋ ํ์์ด์์! ๐
ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ๋ฅผ ๋ง๋ค์ด๊ฐ์! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.