크롤링이란?
크롤링은 웹 페이지를 그대로 가져와 데이터를 추출해내는 것을 말한다.
크롤링을 하기위해서는 해당 페이지가 크롤링을 허용하고 있어야한다.
iOS 의 대표적인 크롤링 라이브러리는 Kanna 와 SwiftSoup가 있다고 한다.
Swiftsoup가 가장 대중적이라고 하니 Swiftsoup으로 진행을 해보겠다.
SwiftSoup 라이브러리 설치
Cocoapods 를 사용하여 SwiftSoup를 설치할 수 있다.
CocoaPods는 Xcode 를 사용하는 개발자들에게 꼭 필요한 프로젝트 매니저이다.
아래 코드를 사용하여 cocoapods 를 설치해준다.
sudo gem install cocoapods
Shell
복사
코코아팟 라이브러리를 적용하고 싶은 프로젝트 경로에 들어가서 아래의 명령어를 입력하여 podfile 을 생성해준다. 이제 podfile 을 수정하여 라이브러리를 다운 받을 수 있다. 여기서 podfile 이란 cocoapods 가 관리할 라이브러리들을 설정하기 위한 파일이다.
pod init
Shell
복사
이제 podfile을 수정하여 SwiftSoup를 설치해보자
podfile 안에 아래의 코드를 추가하고
pod 'SwiftSoup'
Shell
복사
pod install 을 하면 설치가 된.....다고 했는데.. 안된다
검색을 해보니 이 문제는 m1 칩 맥북이라 생긴 문제인것 같다.
위의 코드를 사용하여 설치를 다시 하고 podfile 을 수정하니 제대로 설치가 되었다.
(밑에 노란글씨는.. 맘에 안들지만.. 일단은 에러가 아니라 워닝이기 때문에 넘어가보기로 한다 ㅠ)
.xcworkspace 파일을 열어 import SwiftSoup를 넣어보니 에러가 안뜨는 것으로 보아하니 SwiftSoup 라이브러리 설치가 잘 된것같다!
첫번째 글의 제목/링크/내용 가져오기
아래의 코드는 html 파일을 파싱하는 방법이다.
html : html을 저장하고 있는 String 변수
doc : html을 document 로 변환하여 저장하는 Document 형 변수
doc 변수의 내용이 궁금하여 출력을 해보니 html 이 구조가 이쁘게(?) 출력되는 것을 볼 수 있다.
do {
let html = "<html><head><title>First parse</title></head>"
+ "<body><p>Parsed HTML into a doc.</p></body></html>"
let doc: Document = try SwiftSoup.parse(html)
//print(doc)
return try doc.text()} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
Swift
복사
아래 코드는 요소에서 속성, 텍스트 및 HTML을 추출하는 코드이다.
link : doc 에서 처음으로 만나는 "a" 내용을 저장하는 Element 변수
text : html 파일의 body 내용을 저장하는 String 변수
linkHref : link 에서 "href" 속성의 내용을 저장하는 String 변수
linkText : link 의 내용을 저장하는 String 변수
linkOuterH : link.outerHtml 의 결과를 저장하는 String 변수 (link 를 String으로 저장하는 것 같음)
linkInnerH : link.html() 의 결과를 저장하는 String 변수
import SwiftSoup
import Foundation
do {
let html: String = "<p>An <a href='http://example.com/'><b>example</b></a> link.</p>";
let doc: Document = try SwiftSoup.parse(html)
let link: Element = try doc.select("a").first()!
// print("doc => \n", doc, "\n")
// print("link => ", link, "\n")
let text: String = try doc.body()!.text(); // "An example link"
let linkHref: String = try link.attr("href"); // "http://example.com/"
let linkText: String = try link.text(); // "example""
// print("text => ", text, "\n")
// print("linkHref => ", linkHref, "\n")
// print("linkText => ", linkText, "\n")
let linkOuterH: String = try link.outerHtml(); // "<a href="http://example.com"><b>example</b></a>"
let linkInnerH: String = try link.html(); // "<b>example</b>"
// print("linkOuterH => ", linkOuterH, "\n")
// print("linkInnerH => ", linkInnerH, "\n")
} catch Exception.Error(let type, let message) {
print(message)
} catch {
print("error")
}
Swift
복사
각 변수들을 출력해보면 아래와 같다.
이제 네이버 검색창에 apple 을 검색하고 검색결과 더보기 버튼을 눌렀을 시 나열되는
링크들의 제목을 크롤링하기로 해보자!
import SwiftSoup
import Foundation
// Naver 에서 Apple 을 검색했을 때 처음으로 나오는 글 출력
func SearchFirstAppleInNaver() {
let url = URL(string: "https://search.naver.com/search.naver?display=15&f=&filetype=0&page=2&query=apple&research_url=&sm=tab_pge&start=1&where=web")
guard let myURL = url else { return }
do {
let html = try String(contentsOf: myURL, encoding: .utf8)
let doc: Document = try SwiftSoup.parse(html)
let hl: Element = try doc.select(".hl").select("strong").first()!
let a: Element = try doc.select(".link_tit").select("a").first()!
let link: String = try a.attr("href")
let preview : Element = try doc.select(".total_dsc").select("a").first()!
let preview_text : String = try preview.text()
print(try hl.text())
print(link)
print(preview_text)
} catch Exception.Error(type: let type, Message: let message) {
print(type)
print(message)
} catch{
print("")
}
}
SearchFirstAppleInNaver()
Swift
복사
그러자 아래와 같은 에러가 떴다.
검색을 해보니 이 문제는 podfile 의 use_frameworks! 부분을 주석처리하여 해결할 수 있다고 한다.
문제를 해결하니 아래와 같이 결과가 출력되는것을 확인 할 수 있었다.
이제 apple 을 검색했을 때 처음으로 나오는 글의 제목/링크/내용을 가져온 것이다.
여러 글의 제목/링크/내용 가져오기
이제 apple 을 검색했을 때나오는 여러 글들을 모두 가져와 출력해보자.
import SwiftSoup
import Foundation
// Naver 에서 Apple 을 검색했을 때 나오는 글들을 배열에 저장하고 출력
func SearchAllAppleInNaver() {
let url = URL(string: "https://search.naver.com/search.naver?display=15&f=&filetype=0&page=2&query=apple&research_url=&sm=tab_pge&start=1&where=web")
guard let myURL = url else { return }
do {
let html = try String(contentsOf: myURL, encoding: .utf8)
let doc: Document = try SwiftSoup.parse(html)
let hls: Elements = try doc.select(".hl").select("strong")
let alinks: Elements = try doc.select(".link_tit").select("a")
let preview_texts : Elements = try doc.select(".total_dsc").select("a")
for (index, hl) in hls.enumerated()
{
print("\n\n<< \(index+1)번째 글 >>")
print("제목: \(try hl.text())")
print("링크: \(try alinks[index].attr("href"))")
print("미리보기: \(try preview_texts[index].text())")
}
} catch Exception.Error(type: let type, Message: let message) {
print(type)
print(message)
} catch{
print("")
}
}
SearchAllAppleInNaver()
Swift
복사
원하는 글의 제목/링크/내용 가져오기
keyword 를 매개변수로 받도록 함수를 수정하였다.
apple 은 많이 해봤으니까 iOS 로 검색해보자 ㅎㅎ
import SwiftSoup
import Foundation
// Naver 에서 keyword를 검색했을 때 나오는 글들의 정보를 출력
func SearchInNaver(_ keyword: String) {
let url = URL(string: "https://search.naver.com/search.naver?display=15&f=&filetype=0&page=2&query=" + keyword + "&research_url=&sm=tab_pge&start=1&where=web")
guard let myURL = url else { return }
do {
let html = try String(contentsOf: myURL, encoding: .utf8)
let doc: Document = try SwiftSoup.parse(html)
let hls: Elements = try doc.select(".hl").select("strong")
let alinks: Elements = try doc.select(".link_tit").select("a")
let preview_texts : Elements = try doc.select(".total_dsc").select("a")
for (index, hl) in hls.enumerated()
{
print("\n\n<< 결과 \(index+1) >>")
print("제목: \(try hl.text())")
print("링크: \(try alinks[index].attr("href"))")
print("미리보기: \(try preview_texts[index].text())")
}
} catch Exception.Error(type: let type, Message: let message) {
print(type)
print(message)
} catch{
print("")
}
}
SearchInNaver("iOS")
Swift
복사
이제 class 를 만들어 구조를 깔끔하게 만들어보자
아래의 코드는 Naver 라는 이름의 class 를 만들고 naver 인스턴스를 만든후 iOS 를 검색하는 코드이다.
import SwiftSoup
import Foundation
class Naver {
// Naver 에서 keyword를 검색했을 때 나오는 글들의 정보를 출력
func Search(_ keyword: String) {
let url = URL(string: "https://search.naver.com/search.naver?display=15&f=&filetype=0&page=2&query=" + keyword + "&research_url=&sm=tab_pge&start=1&where=web")
guard let myURL = url else { return }
do {
let html = try String(contentsOf: myURL, encoding: .utf8)
let doc: Document = try SwiftSoup.parse(html)
let hls: Elements = try doc.select(".hl").select("strong")
let alinks: Elements = try doc.select(".link_tit").select("a")
let preview_texts : Elements = try doc.select(".total_dsc").select("a")
for (index, hl) in hls.enumerated()
{
print("\n\n<< \(index+1)번째 글 >>")
print("제목: \(try hl.text())")
print("링크: \(try alinks[index].attr("href"))")
print("미리보기: \(try preview_texts[index].text())")
}
} catch Exception.Error(type: let type, Message: let message) {
print(type)
print(message)
} catch{
print("")
}
}
}
let naver: Naver = Naver()
naver.Search("iOS")
Swift
복사
이제 keyword 를 입력받는 부분을 구현해보자
import SwiftSoup
import Foundation
let keywordSearchInNaver: KeywordSearchInNaver = KeywordSearchInNaver()
class Naver {
// Naver 에서 keyword를 검색했을 때 나오는 글들의 정보를 출력
func Search(_ keyword: String) {
let url = URL(string: "https://search.naver.com/search.naver?display=15&f=&filetype=0&page=2&query=" + keyword + "&research_url=&sm=tab_pge&start=1&where=web")
guard let myURL = url else { return }
do {
let html = try String(contentsOf: myURL, encoding: .utf8)
let doc: Document = try SwiftSoup.parse(html)
let hls: Elements = try doc.select(".hl").select("strong")
let alinks: Elements = try doc.select(".link_tit").select("a")
let preview_texts : Elements = try doc.select(".total_dsc").select("a")
for (index, hl) in hls.enumerated()
{
print("\n\n<< \(index+1)번째 글 >>")
print("제목: \(try hl.text())")
print("링크: \(try alinks[index].attr("href"))")
print("미리보기: \(try preview_texts[index].text())")
if (index == 9){
break
}
}
} catch Exception.Error(type: let type, Message: let message) {
print(type)
print(message)
} catch{
print("")
}
}
}
class KeywordSearchInNaver {
let naver: Naver = Naver()
func loop() {
while(true) {
print("검색할 단어 > ", terminator: "")
let keyword: String? = readLine()
if let tmp = keyword {
print("=====================================================")
naver.Search(tmp)
}
}
}
}
keywordSearchInNaver.loop()
Swift
복사
이렇게 swiftsoup으로 크롤링을 성공했다 ㅎ
조금 더 깔끔한 코드를 위해 Info 구조체를 만들었고
enumerated 를 사용하면 IndexOutofRange 에러가 나는 것을 해결하기 위해 0..min(hls.count, alinks.count) 까지만 Info 배열에 넣었다.
import SwiftSoup
import Foundation
struct Info {
var title: String
var link: String
var preview: String
}
let keywordSearchInNaver: KeywordSearchInNaver = KeywordSearchInNaver()
class Naver {
// Naver 에서 keyword를 검색했을 때 나오는 글들의 정보를 출력
func Search(_ keyword: String) -> [Info] {
var InfoArray:[Info] = []
let url = URL(string: "https://search.naver.com/search.naver?display=15&f=&filetype=0&page=2&query=" + keyword + "&research_url=&sm=tab_pge&start=1&where=web")
let myURL = url
if let myURL = myURL {
do {
let html = try String(contentsOf: myURL, encoding: .utf8)
let doc: Document = try SwiftSoup.parse(html)
let hls: Elements = try doc.select(".hl").select("strong")
let alinks: Elements = try doc.select(".link_tit").select("a")
let preview_texts : Elements = try doc.select(".total_dsc").select("a")
for idx in 0..<min(hls.count, alinks.count) {
let information: Info = Info(title: try hls[idx].text(), link: try alinks[idx].attr("href"), preview: try preview_texts[idx].text())
InfoArray.append(information)
}
return InfoArray
} catch Exception.Error(type: let type, Message: let message) {
print(type)
print(message)
return [Info(title: "", link: "", preview: "")]
} catch{
print("")
return [Info(title: "", link: "", preview: "")]
}
}
return [Info(title: "", link: "", preview: "")]
}
}
class KeywordSearchInNaver {
let naver: Naver = Naver()
func loop() {
while(true) {
print("검색할 단어 > ", terminator: "")
let keyword: String? = readLine()
if let tmp = keyword {
let results: [Info] = naver.Search(tmp)
self.printResult(results)
}
}
}
func printResult(_ results: [Info]) {
// guard let results = results else {
// print("검색 결과가 없습니다.")
// return
// }
//
for (idx, ret) in results.enumerated() {
print("\n\n<< \(idx+1)번째 글 >>")
print("제목: \(ret.title)")
print("링크: \(ret.link)")
print("미리보기: \(ret.preview)")
}
print("==================================\n")
}
}
keywordSearchInNaver.loop()
Swift
복사
내 코드의 한계점
→ 한글이나 검색결과가 없는 단어를 입력받으면 특별한 메세지가 출력되지 않고 그냥 빈칸이 출력된다
→ url 을 찾는 방법은 아직도 모르겠음.. (일단은 구글링으로 Naver 검색창만 구현가능..ㅠ)