본문 바로가기

개발일지/광고데이터 연결 PJT - 차콜진센

20201228_메인스크립트 - 데이터 수집 및 저장

메인 스크립트를 통해 크게 2가지 작업을 한다. 

1. 각 플랫폼 데이터 수집 -> 데이터베이스 저장

2. 데이터베이스에서 데이터 취득 -> 구글 스프레드시트에 자동 입력

 

이번 포스트에선 1. 각 플랫폼 데이터 수집에 대해 설명해보고자 한다. 

 

먼저, 각 플랫폼에서 데이터를 수집하는 방법은 크롤링 혹은 API를 통해서이다. 이렇게 수집한 데이터를 DB에 저장하며 1번 작업이 끝나게 된다. 아래와 같은 흐름으로 코드를 작성했다. 

 

각 플랫폼 코드 객체화 및 초기화 -> 데이터 수집 -> 데이터 저장

1) 각 플랫폼 코드 객체화/초기화 및 데이터 수집

 

# main.rb

def retrieve_dtoc_data(date)
  begin
    puts "start retrieve #{date} d2c data"

    sixpack = Sixpack.new(ENV['SIXPACK_ID'], ENV['SIXPACK_PW'])

    project_root = File.dirname(File.absolute_path(__FILE__))
    mcc_config_path = File.join(project_root, "googleads", "google_ads_config.rb")
    gdn = Gdn.new(mcc_config_path)

    ec = EcLifecreate.new(ENV['EC_ID'], ENV['EC_PW'])
    
    (중략)

    [
      sixpack,
      gdn,
      ec,
      (중략)
    ].each do |platform|
      platform.request(date)
    end

    puts "end retrieve #{date} d2c data"
  rescue => e
    puts "Failed to update d2c report"
    puts "#{e}: #{e.message}\n" + e.backtrace.join("\n")
  end
end

 

위의 sixpack, gdn, ec 등이 바로 각 플랫폼 코드의 객체이다. 각 객체를 생성할 때 초기화를 하는데 이를 통해 ID와 Password를 인스턴스 변수에 담고, (필요하다면) Capybara 세팅까지 완료한다. 초기화된 플랫폼 객체는 인스턴스 메서드인 request를 갖고 있으며 이 메서드를 통해 데이터 수집 / DB 저장 등을 진행한다.

 

해당 플랫폼이 API를 제공하지 않는다면 크롤링을 통해 데이터를 수집해야 한다. 이때 필요한 것이 Capybara gem이다. Capybara는 테스트용 프레임워크이며 이를 통해 웹 크롤링이 가능하다.  초기화를 할 때 아래와 같이 Capybara의 환경설정도 진행한다. chrome headless로 드라이버 등록을 하고 필요한 옵션들을 적용 후 세션을 인스턴스 변수에 담는다.

 

# Sixpack.rb

class Sixpack

  def initialize(id, password)
    @id = id
    @password = password

    Capybara.register_driver :chrome_headless do |app|
      options = ::Selenium::WebDriver::Chrome::Options.new

      options.add_argument("--headless")
      options.add_argument("--no-sandbox")
      options.add_argument("--disable-dev-shm-usage")
      options.add_argument("--remote-debugging-port=9111")

      client = Selenium::WebDriver::Remote::Http::Default.new
      client.read_timeout = 10000

      Capybara::Selenium::Driver.new(app, browser: :chrome, options: options, http_client: client)
    end

    Capybara.run_server = false
    Capybara.default_max_wait_time = 10
    Capybara.javascript_driver = :chrome_headless

    @session = Capybara::Session.new(:chrome_headless)
  end
  
  ...

 

초기화를 통해 Capybara 세팅이 완료되면 크롤링을 진행한다. 각 플랫폼 사이트를 분석하여 효율적인 크롤링 코드를 짜는 게 중요하다. 각 플랫폼마다 코드가 다르므로 사이트에 맞게 크롤링 코드를 작성했다. 

 

나의 경우 최대한 visit메서드를 통해 필요한 데이터가 있는 URL로 들어가 데이터를 추출하는 방법을 자주 사용했는데, 예를 들어보자면,

 

@session.visit "https://sixpack.work/manaver/partner_media?id=#{media_id}"

 

이렇게 원하는 페이지의 URL에 직접 방문하는 방식이었고, URL에 필요한 id나 date정보 등은 위와 같이 리터럴 템플릿을 사용해 삽입하였다. 

위의 경우, media id를 취득하기 위해 a 태그의 href 부분을 사용했다. 내가 원하는 데이터가 한 페이지에 몰려있지 않을 가능성이 크기에 이렇게 a 태그를 뒤져보거나 하면서 페이지 이동을 최소화하려 노력했다.

 

REGEX = /\d+/

...

href_arr = tr.find(:xpath, "#{tr.path}/td[1]/a")['href'].split('/') 
media_id = href_arr.select { |e| REGEX.match(e) }.first

 

a태그의 href는 "https://sixpack-c.work/.../media/11/dates" 이런 식이 었고 숫자 부분이 media id였다. 그렇기에 '/'로 split을 하여 숫자로만 이루어져 있는 데이터를 추출한다면 media id를 얻을 수 있었다. 

일단 split을 진행하면 배열로 리턴이 된다. 이 배열, 여기선 href_arr의 데이터를 select 메서드를 통해 순회하며 조건에 맞는 값을 찾는다. regex를 통해 숫자로만 이루어진 데이터를 찾도록 했다. 

REGEX.match(e) 로 조건에 맞는 값을 찾았을 때 boolean값을 리턴한다.  true를 리턴하는 배열 요소를 media_id변수에 담았다. 

 


2) 데이터 저장

 

이렇게 필요한 데이터 추출과정이 끝나면, 데이터베이스에 저장하는 작업을 진행한다. 

 

# sixpack.rb

...

obj = {
	creative_id:   media_id,
	creative_name: media_name,
	imp:           imp   || 0,
	click:         click || 0,
	conversion:    cv    || 0,
	net:           net   || 0,
}

DailyReport.process(obj)

...

 

각 데이터를 hash의 형태로 만든 후 DailyReport모델의 process메서드에 매개변수로 넘긴다. process 메서드를 호출하기 위해 따로 DailyReport의 객체를 만드는 작업을 진행하지 않았는데, 이는 process 메서드가 인스턴스 메소드가 아닌 클래스 메서드라는 얘기다. 

 

# daily_report.rb

class DailyReport < ApplicationRecord
  
  scope :data_on_ids_date, ->(advertiser_id, order_id, schedule_id, creative_id, date) {
    where(date: date, advertiser_id: advertiser_id, order_id: order_id, schedule_id: schedule_id, creative_id: creative_id)
  }
  ...중략
  
  def self.process(obj)
    return unless obj[:date] && obj[:advertiser_id] && obj[:order_id] && obj[:schedule_id] && obj[:creative_id]

    db_record = self.data_on_ids_date(obj[:advertiser_id], obj[:order_id], obj[:schedule_id], obj[:creative_id], obj[:date]).first
    if db_record
      db_record.update!(obj)
    else
      self.create!(obj)
    end
  end

end

 

이번 프로젝트에선 rails의 scope기능을 정말 많이 사용하였는데, 위의 경우도 마찬가지이다. scope란 자주 사용하는 query를 하나의 예약어로 묶어 사용할 수 있는 기능이다. 실제로, where(...) 이 긴 where 절을 매번 쓸 필요 없이 data_on_ids_date라는 scope 한 줄 만으로 쉽게 쿼리를 작성할 수 있다. 

 

data_on_ids_date 스코프를 통해 데이터가 있는지 확인을 한다. 만약 존재한다면 update를, 존재하지 않는다면 새 값을 입력하도록 했다. 

 


 

이렇게 데이터 수집부터 데이터베이스에 저장하는 과정까지 완료했다. 다음은 이 데이터를 가져와 스프레드시트에 입력하는 과정을 설명해보겠다.