Search
Duplicate

SwiftSoup 로 크롤링해보기

간단소개
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
Swift
Scrap
태그
swift
crawling
9 more properties

크롤링이란?

크롤링은 웹 페이지를 그대로 가져와 데이터를 추출해내는 것을 말한다.
크롤링을 하기위해서는 해당 페이지가 크롤링을 허용하고 있어야한다.
iOS 의 대표적인 크롤링 라이브러리는 KannaSwiftSoup가 있다고 한다.
Swiftsoup가 가장 대중적이라고 하니 Swiftsoup으로 진행을 해보겠다.

SwiftSoup 라이브러리 설치

Cocoapods 를 사용하여 SwiftSoup를 설치할 수 있다.
CocoaPodsXcode 를 사용하는 개발자들에게 꼭 필요한 프로젝트 매니저이다.
아래 코드를 사용하여 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 라이브러리 설치가 잘 된것같다!

첫번째 글의 제목/링크/내용 가져오기

SwiftSoup 사이트의 Readme 를 참고하면서 코드를 따라가보았다.
아래의 코드는 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
복사
그러자 아래와 같은 에러가 떴다.
검색을 해보니 이 문제는 podfileuse_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 검색창만 구현가능..ㅠ)