구글과 유튜브 검색 자료를 스크래핑 하라는 업무를 받아서 진행하게 되었다.
많은 앱들이 이러한 스크래핑 작업에 경우 서버에 아이디 비번 정도만 넘겨서
처리된 값들을 파싱하지만 이번에는 처음 웹뷰 제작 및 웹뷰를 숨기고 작업 Scraping 실행,
DB 입력까지 직접 해 볼 수 있던 기회였다.
SwiftSoup 이라는 라이브러리를 패키지 임포트 하여 진행하였다.
SwiftSoup Github
ReadMe에 친절하게 사용하는 법이 설명되어 있으니 확인해보자
하기는 코드랩을 진행했을 때의 코드이다.
public enum KeywordType : Int {
case KEYWORD_TYPE_UNKNOWN = -1
case KEYWORD_TYPE_GOOGLE = 0
case KEYWORD_TYPE_YOUTUBE = 1
}
public struct Keyword : Hashable{
public let key: CLong = 0
public let identifier: CLong = 0
public let keywordType: KeywordType = .KEYWORD_TYPE_UNKNOWN
public let keywordEngineName: String = ""
public let mainType: Int? = 0 // 메인 종류 ( 유튜브[1:유튜브, 2:유튜브뮤직] / 구글[null] )
public let subType: Int? = 0 // 검색어 종류 ( 0:기타, 1:검색, 2:시청, 3:방문, 4:조회 )
public let name: String? = nil // 검색어
public let dateTime: Date? = nil // 시간 ( ex. 20200815150000 )
public let image: String? = nil // 썸네일 이미지 URL
public let isConnected: Bool = false
// let hashTags: RealmList<HashTagItem> = RealmList()
public init() {
}
}
// Google
func insertGoogleKeywordData(url : URL, keywordEngineType : Int) {
var keywords : [Keyword] = []
var index = getLastIndex() + 1
print("Start Index :: \(index)")
let lastDate = getLastDate(keywordType: keywordEngineType)
print("GET LAST DATE :: \(lastDate)")
//Scraping code be delayed
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [self] in // Change `2.0` to the desired number of seconds.
do {
print("Google Keyword Scraping Start")
let html = try String(contentsOf: url, encoding: .utf8)
// Get all html
guard let doc: Document = try? SwiftSoup.parseBodyFragment(html) else {
// Exception Handling
print("Fail Scrap HTML DOC")
scrapingFailAction()
return
}
// Select List of Searched Keywords
let searchKeywords : Elements? = try? doc.select("c-wiz[class*='xDtZAf']")
if searchKeywords != nil && searchKeywords?.count != 0 {
// Resolve Each Keywords
for searchKeyword : Element in searchKeywords!.array() {
// Date
let searchDate : String = try searchKeyword.attr("data-date")
// Time
let searchTime : String = try searchKeyword.select("div[class='H3Q9vf XTnvW']").first()?.text().components(separatedBy: "•")[0].replacingOccurrences(of: " ", with: "") ?? ""
// Date + Time Formatting
let formattedDate : Date = dateFormattingForGoogle(timeString: searchTime, formattedDate: searchDate)
// Compare With Realm Data (lastDate)
if formattedDate > lastDate {
// Keyword Buffuer
let keyword = Keyword()
// DateTime
keyword.dateTime = formattedDate
print("DATE_TIME :: \(keyword.dateTime)")
// id
keyword.id = index
// KeywordType
keyword.keywordType = keywordEngineType
// MainType
keyword.mainType = WebViewMainType.GOOGLE.rawValue // Google 일 경우
print("MAIN_TYPE :: \(keyword.mainType)")
// Get Row
let searchHtmlRow = try searchKeyword.select("div[class='QTGV3c']").first()?.text() ?? ETC
//SubType
keyword.subType = getSubType(searchHtmlRow: searchHtmlRow)
print("SUBTYPE :: \(keyword.subType)")
// KeyWord Name
let name : Element? = try searchKeyword.select("a[jsname='eLJrl']").first() ?? nil
keyword.name = String(try name?.text() ?? "" )
print("KEYWORD :: \(keyword.name)")
// Image
let image : String = try searchKeyword.select("img[class='us8hCd']").attr("src")
keyword.image = image
print("IMAGE :: \(image)")
// Append keyword Data
keywords.append(contentsOf: [keyword])
index += 1
}
}
// Call Insert Method
print("Keyword : \(keywords.count)")
insertFormattedKeyword(keywordEngineType: keywordEngineType, keywords: keywords)
} else {
// Exception Handling
scrapingFailAction()
}
} catch Exception.Error(let type, let message) {
print("Error Type : \(type) Error Message : \(message)")
} catch {
print("error")
}
}
}
WebScraping을 하다보면 페이지를 강제로 스크롤해서 내리거나, "더보기"와 같은 버튼을 해제하기 위해 페이지를 내려야 하는데 이런 경우 상기의 사진과 같은 overflow:hidden에 걸리게 된다. 이렇게 되면 예를 들면, 34개의 데이터를 가져와야 하는데 6개째에서 overflow:hidden 쪽으로 이동되면서 상기의 사진의 위치에서 검색을 멈추게된다. safari에서 생기는 문제인데 apple developer 쪽에서 확인해본 결과
아직 해결책이 뚜렷하게는 없는듯 하다, 따라서 안드로이드는 크롬을 쓰기 때문에 같은 스크래핑을 하더라도 성능차이가 생기기 쉬웠다.