관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] Web crawling(웹 크롤링), web scraping, (swiftsoup, Alamofire, 한글 깨짐) 본문

iOS 응용 (swift)

[iOS - swift] Web crawling(웹 크롤링), web scraping, (swiftsoup, Alamofire, 한글 깨짐)

jake-kim 2021. 3. 17. 01:42

파싱을 위한 HTML 기초

  • HTML 구성
<!docutype html>
<head>
  ...
</head>
<body>
  ...
</body>
  • body 태그안에 자주 사용되는 태그
<br>
<div>
<p>
<span>
<form>
<h1>
<a>
<img>
<table>
<ul>
<li>
<nav>
  • css: 디자인
<style> h1 {color: red;} </style>
  • javascript: 동적인 부분을 변경시켜 주는 것
<script> alert("테스트") </script>

 

css의 id와 class 개념

  • id와 class의 차이점은 우선순위: id > class > 태그
  • class
<!DOCTYPE html>
<html>
<head>
  <style>
    .class1 {color: red; background-color: yellow;}
    h3.class1 {color: navy; background-color: blue;}
  </style>
</head>

<body>
  <h1 class="class1">클래스1 예제</h1>
  <h3 class="class1">h3인 클래스1 예제</h3>
</body>
</html>
  • id
<!DOCTYPE html>
<html>
<head>
  <style>
    #id1 {color: blue; background-color; yellow;}
  </style>
</head>

<body>
  <h1 id="id1">ID1 선택자</h1>
</body>
</html>

가장 중요한 개념

  • id /class / tag / attribute 구분
    • attribute는 tag안의 값
    • <a href="ios-development.tistory.com">인 경우, href의 정체는 attribute
  • SwiftSoup의 Element속성 중 id, class, tag 는 모두 element.select()로 접근하는 반면, attribute는 element.attr()로 접근하는 것을 주의

SwiftSoup 프레임워크 준비

pod 'SwiftSoup'

웹 크롤링 방법

  • 크롤링할 사이트를 크롬으로 열은 후 크롤링할 문구 오른쪽 클릭 -> 검사 선택

  • 검색을 눌렀을 때 오른쪽에 html 코드 정보 확인

Copy -> Copy Select 클릭

  • 결과: #mArticle > div:nth-child(7) > a.link_post > strong

웹 클롤링

  • Alamofire를 통해 해당 사이트에 있는 html을 획득
struct CrawlManager {

    static func crawlJakeBlog() {
        let urlStr = "https://ios-development.tistory.com/"
//        let classPath = "#mArticle > div:nth-child(6) > a.link_post > strong"

        AF.request(urlStr).responseString { (response) in
            guard let html = response.value else {
                return
            }
        }
    }

}
  • html을 파싱하여 데이터 획득
    • 위에서 얻은 class path사용: "#mArticle > div:nth-child(7) > a.link_post > strong"
struct CrawlManager {

    static func crawlJakeBlog() {
        let urlStr = "https://ios-development.tistory.com/"
//        let classPath = "#mArticle > div:nth-child(6) > a.link_post > strong"

        AF.request(urlStr).responseString { (response) in
            guard let html = response.value else {
                return
            }

            do {
                let doc: Document = try SwiftSoup.parse(html)
                // #newsContents > div > div.postRankSubjectList.f_clear
                let elements: Elements = try doc.select("#mArticle > div")
                for element in elements {
                    print(try element.select("a.link_post > strong").text())
                }

            } catch {
                print("crawl error")
            }
        }
    }

}
  • 결과

response.value값에 한글이 깨지는 경우

  • CFStringConvertEncodingToNSStringEncoding 사용
            let encodedHtml = NSString(data: response.data!, encoding: CFStringConvertEncodingToNSStringEncoding( 0x0422 ) )
            if let encodedHtml = encodedHtml {
                html = encodedHtml as String
            }
  • 전체 코드
struct CrawlManager {

    static func crawlJakeBlog() {
        let urlStr = "https://ios-development.tistory.com/"
//        let classPath = "#mArticle > div:nth-child(6) > a.link_post > strong"

        AF.request(urlStr).responseString { (response) in
            guard var html = response.value else {
                return
            }

            if let data = response.data {
                let encodedHtml = NSString(data: data, encoding: CFStringConvertEncodingToNSStringEncoding( 0x0422 ) )
                if let encodedHtml = encodedHtml {
                    html = encodedHtml as String
                }
            }

            do {

                let doc: Document = try SwiftSoup.parse(html)
                // #newsContents > div > div.postRankSubjectList.f_clear
                let elements: Elements = try doc.select("#mArticle > div")
                for element in elements {
                    print(try element.select("a.link_post > strong").text())
                }

            } catch {
                print("crawl error")
            }
        }
    }

}

* 만약 href에 접근하고 싶은 경우, element.select(a).attr("href") // 문자열로 반환

ex) <a href="ios-development.tistory.com/">

 

* 접근이 안되는 경우, html파일을 보고, tag로 하나하나 접근 - 아래에서 "프리미어리그" 파싱방법

  • 접근: doc.select("channel > item > title")
        let url = "https://trends.google.com/trends/trendingsearches/daily/rss?geo=KR"
        AF.request(url).responseString { (response) in
            guard let html = response.value else {
                return
            }
            do {
                let doc: Document = try SwiftSoup.parse(html)
                let elements: Elements = try doc.select("channel > item")

                for element in elements {
                    let title = try element.select("title")
                    print(try title.text())
                }

            } catch {
                print(error)
            }
        }

결과

Selector가 아닌 tagName으로 파싱 방법

ht:news_item_snippet 내용을 파싱하고 싶은 경우

  • selector로 파싱이 안될 경우, tagName을 알아내어 파싱 시도
                for element in elements {
                    print(element.tagName())
                    print("1---------------------------")
                    for elementChildren in element.children() {
                        print(elementChildren.tagName())
                        print("2---------------------------")
                        for htNewsItemChildren in try elementChildren.tagName("ht:news_item").children() {
                            print(htNewsItemChildren.tagName())
                        }
                    }
                }

tagName 확인

  • for문으로 접근
                for element in elements {
                    var isFindTargetContents = false

                    for elementChildren in element.children() {
                        let targetElement = try elementChildren.tagName("ht:news_item").children().first()
                        let description = try targetElement?.tagName("ht:news_item_snippet").text()

                        if let description = description {
                            if isFindTargetContents {
                                break
                            } else {
                                isFindTargetContents = true
                                print(description)
                            }
                        }
                    }
                }

결과

Comments