iOS 实现网页爬虫

实现方案

  1. 利用WKWebView打开一个待爬取的网页
  2. 在webView渲染完成之后注入一段爬虫脚本
  3. 在脚本回调里面获取爬取的数据

代码

以天猫的商品爬取为例

先打印网页内容

注入脚本document.body.innerHTML

- (void)viewDidLoad {
    [super viewDidLoad];
    self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 100.f, FULL_WIDTH, 200.f)];
    self.webView.navigationDelegate = self;
    [self.view addSubview:self.webView];
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://detail.tmall.com/item.htm?id=578502467835&ali_refid=a3_430406_1007:1121266184:N:1060515764_0_100:61033457550edeff91391950420fef46&ali_trackid=1_61033457550edeff91391950420fef46&spm=a21bo.2017.201874-sales.57"]]];
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
    [self.webView evaluateJavaScript:@"document.body.innerHTML" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"抓取结果:%@", result);
    }];
}
打印结果.png

格式化之后


商品图dom.png
商品名dom.png
商品价格dom.png

写脚本

商品图获取:
document.getElementsByClassName('item')[0].getElementsByTagName('img')[0].src

价格获取:
document.getElementsByClassName('real-price')[0].getElementsByClassName('price')[0].textContent

商品名获取:
document.getElementsByClassName('main')[0].textContent

组合成字典的形式返回(完整脚本)

(function() {
    var init = function() {
        return {
            imgSrc: document.getElementsByClassName('item')[0].getElementsByTagName('img')[0].src,
            price: document.getElementsByClassName('real-price')[0].getElementsByClassName('price')[0].textContent,
            title: document.getElementsByClassName('main')[0].textContent
        };
    };
    return init();
})()

注入新的脚本

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
    [self.webView evaluateJavaScript:@"(function(){var init = function(){return {imgSrc:document.getElementsByClassName('item')[0].getElementsByTagName('img')[0].src,price:document.getElementsByClassName('real-price')[0].getElementsByClassName('price')[0].textContent,title:document.getElementsByClassName('main')[0].textContent};}; return init();})()" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"抓取结果:%@", result);
    }];
}
结果打印.png

注意点

(1) html的解析一定要以客户端返回的为准, 与浏览器打开看到的html是不一样的
(2) 脚本有问题的时候error会提示Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" 根据提示修改脚本即可

(3) 服务端的脚本可以通过下面的方法转成string

[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://xxxxx.js"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}

2019.02.16更新

因为网页数据大多数是异步返回的, 在didFinishNavigation回调触发的时候, 页面上想被抓取的数据并没有返回
增加一个dom变更的监听, 利用一个debounce防止调用过于频繁

var timer = null;
var body = document.getElementsByTagName("body")[0];
    body.addEventListener("DOMSubtreeModified", function(evt) {
          clearTimeout(timer);
          timer = setTimeout(function(){
                spider();
            }, 1000);
    }, false);

这个时候只能通过js去调用oc
初始化的时候去创建一个webView的config

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
//注册方法名
[configuration.userContentController addScriptMessageHandler:self name:@"spider"];
self.webview = [WKWebView initWithFrame:frame configuration:configuration];

实现WKScriptMessageHandler协议

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    if ([message.name isEqualToString:@"spider"])
    {
        //js的传过来的数据
        NSLog(@"%@",message.body);
    }
}

js脚本

var spider = function() {
  ...
  //window.webkit.messageHandlers..postMessage()
  window.webkit.messageHandlers.spider.postMessage(spiderData);
  ...
}

你可能感兴趣的:(iOS 实现网页爬虫)