Go

[Go] HTTP 요청 크롤링 병렬 처리, 하이웍스 크롤링

_HelloWorld_ 2025. 1. 22. 13:38

사용자 프로필, 업무 정보, 상세 프로필 데이터를 병렬로 수집하고 이를 처리하는 과정을 구현한 예제


1. 주요 상수 정의

API 요청에 필요한 URL들을 상수로 정의합니다.

const (
    LoginURL         = "https://auth-api.office.hiworks.com/office-web/login"
    ProfileURL       = "https://cache-api.office.hiworks.com/me"
    WorksURL         = "https://hr-timecheck-api.office.hiworks.com/v4/web/user-work-info"
    ProfileDetailURL = "https://office.hiworks.com/moasoftware.onhiworks.com/insa/org_ajax/"
)

2. 에러 핸들링 함수

에러 발생 시 프로그램을 종료합니다.

 

func handleError(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

3. 로그인 요청 함수

ID와 비밀번호를 사용해 로그인 요청을 보내고, 응답 쿠키를 반환합니다.

func login() (map[string]string, error) {
    payload := `{
        "id": "___@moasoftware.onhiworks.com",
        "password": "______",
        "ip_security_level": "1"
    }`
    _, res, err := network.PostJSONRequest(LoginURL, payload, nil)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()

    // 응답 쿠키 파싱
    cookieMap := make(map[string]string)
    for _, cookie := range res.Cookies() {
        cookieMap[cookie.Name] = cookie.Value
    }

    return cookieMap, nil
}

4. 프로필 정보 조회 함수

ProfileURL로 요청을 보내 사용자 프로필 데이터를 가져옵니다.

func fetchProfile(cookies map[string]string, wg *sync.WaitGroup, profileChan chan<- *types.MyProfileResponse, errChan chan<- error) {
    defer wg.Done()
    body, err := network.GetRequest(ProfileURL, cookies)
    if err != nil {
        errChan <- err
        return
    }
    var profile_response types.MyProfileResponse
    if err = json.Unmarshal(body, &profile_response); err != nil {
        errChan <- err
        return
    }
    profileChan <- &profile_response
}

5. 업무 정보 조회 함수

WorksURL로 요청을 보내 사용자의 출퇴근 기록을 가져옵니다.

func fetchWorksInfo(cookies map[string]string, wg *sync.WaitGroup, worksChan chan<- *types.MyWorksInfoResponse, errChan chan<- error) {
    defer wg.Done()
    body, err := network.GetRequest(WorksURL, cookies)
    if err != nil {
        errChan <- err
        return
    }
    var works_response types.MyWorksInfoResponse
    if err = json.Unmarshal(body, &works_response); err != nil {
        errChan <- err
        return
    }
    worksChan <- &works_response
}

메인 함수

로그인 후 데이터를 병렬로 수집하며, 필요한 데이터가 모두 수집되면 프로그램을 종료합니다.

func main() {
    start := time.Now()

    cookies, err := login()
    handleError(err)
    if cookies["PHPSESSID"] == "" {
        log.Fatal("로그인 실패")
    } else {
        log.Println("로그인 성공")
    }

    var wg sync.WaitGroup
    profileChan := make(chan *types.MyProfileResponse)
    worksChan := make(chan *types.MyWorksInfoResponse)
    detailChan := make(chan *types.ProfileDetail)
    errChan := make(chan error, 3)

    wg.Add(2)
    go fetchProfile(cookies, &wg, profileChan, errChan)
    go fetchWorksInfo(cookies, &wg, worksChan, errChan)

    go func() {
        wg.Wait()
        close(profileChan)
        close(worksChan)
        close(detailChan)
        close(errChan)
    }()

    var profile *types.MyProfileResponse
    var works *types.MyWorksInfoResponse
    var profileDetail *types.ProfileDetail

    for {
        select {
        case p := <-profileChan:
            profile = p
            wg.Add(1)
            go fetchProfileDetail(cookies, "55734", &wg, detailChan, errChan)
        case w := <-worksChan:
            works = w
        case pd := <-detailChan:
            profileDetail = pd
        case err := <-errChan:
            handleError(err)
            return
        }

        if profile != nil && works != nil && profileDetail != nil {
            break
        }
    }

    em, err := parse.ParseEmployInfo(profileDetail.Result)
    if err != nil {
        handleError(err)
    }
    fmt.Println(em.Name, em.Department, em.Position, em.PhoneNumber, em.Email, em.EmployeeID, em.HireDate, em.BirthDate)

    end := time.Now()
    fmt.Println("실행 시간:", end.Sub(start))
}

병렬 처리 기법인 고루틴, 데이터 전달과 동기화를 위한 채널, 작업 완료 대기를 위한 WaitGroup 및 동시성 작업 구조를 단순화하는 익명 함수를 활용하여 설계

 

  • 프로그램에서 fetchProfile, fetchWorksInfo, fetchProfileDetail 등의 함수가 각각 별도의 고루틴으로 실행
  • 프로그램은 각 작업의 결과를 전달하거나 오류를 처리하기 위해 여러 개의 채널을 사용
  • 프로그램은 비동기 작업을 병렬로 실행하고, 모든 작업이 완료될 때까지 대기하도록 WaitGroup을 활용
  • 프로그램에서 익명 함수는 wg.Wait() 이후 모든 채널을 닫는 작업을 수행합니다. 이는 코드의 간결함을 높이고 반복적인 작업 줄임
  • 각 채널에서 데이터를 수신할 때마다 특정 작업을 실행하며, 모든 데이터가 수집되면 루프를 종료

주요 흐름

  1. 로그인: Hiworks API를 통해 인증 후 쿠키를 수신.
  2. 병렬 작업 시작:
    • fetchProfile과 fetchWorksInfo를 고루틴으로 실행.
  3. 채널로 데이터 수집:
    • select 구문으로 채널에서 데이터 수신.
  4. 추가 작업 병렬 실행:
    • 프로필 데이터 수신 후 fetchProfileDetail 실행.
  5. 작업 완료 대기:
    • WaitGroup을 활용해 모든 고루틴 작업 완료 대기.
  6. 결과 처리:
    • 수집한 데이터를 JSON으로 파싱하고 필요한 정보를 출력.

'Go' 카테고리의 다른 글

[Go][gin] 리액트 정적 파일 서빙  (0) 2025.02.15
[Go] HTTP Client 크롤링 최적화  (0) 2025.01.22
Go언어 재설치하기 ( Ubuntu )  (0) 2025.01.01