Parse HTML với iOS

Đã bao giờ bạn tự hỏi "Làm thế nào để phân tích một source html và tìm những phần mình muốn lấy?" Thực tế, bạn sẽ làm việc với một vài html từ nhiều trang khác nhau, nếu nghĩ tới việc sử dụng regular expressions thì khá là vất vả mỗi khi có thay đổi về cấu trúc html. Trong bài viết này, tôi sẽ giới thiệu tới bạn cách mà có thể parsing html thành một Objective-C data model, cụ thể hơn là bạn có thể convert để làm việc với XML và truy vấn theo kiểu XPath. Nếu các bạn làm việc với XML thì đã quen với kiểu cây thư mục, với html cũng vậy, mình cũng có thể phân tích thành cây thư mục. Hãy xem ví dụ sau:

Tôi có một đoạn source html như sau:

<html>
  <head>
    <title>Some webpage</title>
  </head>
  <body>
    <p class=”normal”>This is the first paragraph</p>
    <p class=”special”>This is the second paragraph. <b>This is in bold.</b></p>
  </body>
</html>

Sẽ được phân tích dưới dạng cây như sau:

tree.png

Nhìn vào cây thư mục trên, Nếu bạn muốn truy cập đến title của html thì bạn có thể sử dụng XPath expression như sau:

/html/head/title

Thì kết quả sẽ là "Viblo"

Tương tự, nếu bận muốn truy cập đến dữ liệu thẻ p với class = special để lấy "second paragraph", bạn cũng có thể sử dụng expression như sau:

/html/body/p[@class='special']

Bạn đã sử dụng cú pháp [@class='special'] để chỉ ra cái node mà mình muốn tìm là html->body->p trong đó thẻ p có class = special. Nếu có nhiều thẻ p mà cùng có class = special thì expression này sẽ trả về một mảng node thoả mãn.

Với iOS thì bạn có thể sử dụng Libxml2Hpple để parse html Libxml2 là thư viện của apple cung cấp, nhưng để sử dụng Libxml2 thì rất khó sử dụng, thế nên có một thư việc khá nổi tiếng của The Topfunky Corporation là Hpple (https://github.com/topfunky/hpple), Hpple được viết lại (wraps) trên libxml2, tạo ra một cấu trúc XML và truy vẫn như XPath. Bây giờ sẽ bắt đầu với project cụ thể nhé:

  1. Tạo project và add thư viện
  • Tạo 1 project mới

Screen Shot 2016-07-04 at 10.56.35 AM.png

Screen Shot 2016-07-04 at 10.58.12 AM.png

Screen Shot 2016-07-04 at 10.58.25 AM.png

Kéo thả 6 file của thư viện hpple vào group vừa tạo trong pj: Screen Shot 2016-07-04 at 11.01.15 AM.png

  • Add libxml2

Vào Build Phases > Link Binary With Libraries > ấn nút + > Add Other > chọn đến thư mục đường dẫn sau "/usr/lib/" > chọn libxml2.2.dylib > add

Screen Shot 2016-07-04 at 11.07.51 AM.png

Build Setting > Header Search Paths > nhập $(SDKROOT)/usr/include/libxml2

Screen Shot 2016-07-04 at 11.17.22 AM.png

  1. Bắt đầu Parsing trang https://www.raywenderlich.com Mình hãy thử làm mốt ví dụ nhỏ: Lấy tiêu đề các bài viết trang chủ https://www.raywenderlich.com Trước khi parsing mình phải phân tích html source trước, nếu bạn dùng chrome có thể sử dụng inspect để view source. Khi view sourc trang https://www.raywenderlich.com bạn sẽ thấy:

Screen Shot 2016-07-04 at 11.58.09 AM.png

Nhìn vào source thì mình có thể viết XPath như sau:

//div[@class='item-info']/a

Đây là đoạn code swift trong ViewController.swift:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.startParsing()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func startParsing() {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
            let urlLoad = NSURL(string: "https://www.raywenderlich.com")
            let data = NSData(contentsOfURL: urlLoad!)
            let parser = TFHpple(HTMLData: data)
            let xpathQueryContent = "//div[@class='entry-content clearfix']/h2"
            let listElements = parser.searchWithXPathQuery(xpathQueryContent)
            for k in 0 ..< listElements.count {
                let title = listElements[k].content!
                print("--->>title : \(title)")
            }
            dispatch_async(dispatch_get_main_queue(), { () -> Void in

            })
        })
    }
}

Sau khi chạy pj thì được kết quả:

Screen Shot 2016-07-04 at 11.57.52 AM.png

Cám ơn đã đọc bài ^^