(转载)利用webkit抓取动态网页和链接

简介: 做爬虫的时候最头疼的就是遇到一些动态加载的页面或者是一些动态生成的链接。 比如我们的博客园就是个例子:   凤凰网的评论链接也是一样: 今天我们就用Webkit来解决这个问题。   预备知识可以看一下我前面几篇文章,准备工作参照利用InjectedBundle定制自己的Webkit(二)中的客户端程序。

做爬虫的时候最头疼的就是遇到一些动态加载的页面或者是一些动态生成的链接。

比如我们的博客园就是个例子:

 

凤凰网的评论链接也是一样:

今天我们就用Webkit来解决这个问题。

 

预备知识可以看一下我前面几篇文章,准备工作参照利用InjectedBundle定制自己的Webkit(二)中的客户端程序。

一切就绪之后我们开始!

 

首先介绍一些重要的函数和回调

在创建一个Page之后我们可以设置一些回调函数,其中有一个是:

WKPageLoaderClient::didFinishDocumentLoadForFrame

原型是:

typedef void (*WKPageDidFinishLoadForFrameCallback)(WKPageRef page, WKFrameRef frame, WKTypeRef userData, const void *clientInfo);

这个函数是在一个Frame加载完毕之后调用。由于每个Page都有一个 mainFrame,而mianFrame又可能拥有若干个childFrame,只有在所有childFrame加载完毕之后mainFrame才算加 载完毕,所以我们可以认为当mainFrame回调didFinishDocumentLoadForFrame的时候就是整个Page加载完毕的时候。 换句话说这个时候所有的动态内容也都加载完毕了。我们可以在这个回调中获取我们需要的页面内容。

一个简单的例子:

WKContextRef context = WKContextGetSharedProcessContext();
RECT webViewRect = { 0, 0, 0, 0};
WKViewRef view = WKViewCreate(webViewRect, context, 0, m_window);
WKPageLoaderClient loaderClient = { 0 };
loaderClient.version = kWKPageLoaderClientCurrentVersion;
loaderClient.didFinishLoadForFrame = didFinishLoadForFrame;
WKPageSetPageLoaderClient(WKViewGetPage(view), &loaderClient);

大家都知道JavaScript功能强大,而Webkit给我们提供了运行自己的JS的接口,所以要提取我们想要的内容并非难事。调用方法如下:

WKStringRef script = WKStringCreateWithUTF8CString("1.1 + 1.5");
WKPageRunJavaScriptInMainFrame(page, script, 0, scriptResultCallback);

这是一个简单的例子运行1.1 + 1.5简单的浮点计算,这里面用到的page就是回调得到的WKPageRef,scriptResultCallback是执行完毕之后的回调。接下来编写回调函数处理执行结果:

void scriptResultCallback(WKSerializedScriptValueRef value, WKErrorRef, void* context)
{
  JSGlobalContextRef scriptContext = JSGlobalContextCreate(0);
  JSValueRef exc;
  JSValueRef var = WKSerializedScriptValueDeserialize(value, scriptContext, &exc);
  double dd = JSValueToNumber(scriptContext, var, &exc);
  wchar_t info[1024];
  swprintf(info, L"result is: %f", dd);
  ::MessageBox(NULL, info, L"Script", MB_OK);
  JSGlobalContextRelease(scriptContext);
}

运行结果如下:

 

既然能够得到正确的结果那我们开始解决第一个问题:提取动态内容

下面的例子是用JS把页面body部分的代码提取出来

var wholeHtmlString = '';    // 存放HTML

function myPrintTag(node)
{
  if (node.nodeName == '#text')  // 文本块直接打印内容
  {
    wholeHtmlString += node.textContent;
    wholeHtmlString += '\n';
    return 'text';
  }
  else if (node.nodeName == '#comment')  // 过滤注释
  {
    return 'comment';
  }
  else if (node.nodeName == 'SCRIPT')  // 过滤JS
  {
    return 'script';
  }

  wholeHtmlString += '<';
  wholeHtmlString += node.nodeName;
  wholeHtmlString += ' ';
  if (node.hasAttributes())
  {
    for (var i = 0; i < node.attributes.length; i++)  // 输出节点属性
    {
      var attr = node.attributes.item(i);
      wholeHtmlString += attr.name;
      wholeHtmlString += '=\'';
      wholeHtmlString += attr.value;
      wholeHtmlString += '\' ';
    }
  }
  wholeHtmlString += '>\n';
  return 'normal';
}

function myProcessNode(parent)
{
  var nodeType = myPrintTag(parent);  // 输出当前节点
  if (nodeType == 'normal')
  {
    if (parent.hasChildNodes())
    {
      for (var i = 0; i < parent.childNodes.length; i++)  // 输出孩子节点
      {
        myProcessNode(parent.childNodes.item(i));
      }
    }
    wholeHtmlString += '</';
    wholeHtmlString += parent.nodeName;
    wholeHtmlString += '>\n';
  }
}

function myPrintHtml()
{
  myProcessNode(document.body);  // 输出body部分
  return wholeHtmlString;
}

myPrintHtml();

要注意的地方是注释和文本节点转成HTML的时候需要特殊处理,利用这种方式可以轻松地自定义需要得到的部分。

 

接下来解决第二个问题:提取动态链接

我们浏览网页的时候要跳转到一个新的链接通常都是用鼠标点击一下即可,我们就可以模拟这一过程来提取出动态生成的链接。先看JS代码:

var clickEvt = document.createEvent('Event');
clickEvt.initEvent('click', true, true);
myObject.dispatchEvent(clickEvt);

myObject是我们想要获取链接的DOM节点,利用给目标DOM节点发送一个click消息就能够模拟鼠标点击事件,然后要做的就是捕获跳转的请求。

在页面将要导航到新的URL的时候,会调用一个回调:

WKPagePolicyClient::decidePolicyForNavigationAction

在将要创建一个新的页面的时候,会调用一个回调:

WKPagePolicyClient::decidePolicyForNewWindowAction

利用这两个回调,就能捕获到上文提到的跳转请求。下面看一个例子:

首先注册一下回调函数

WKPagePolicyClient policyClient = { 0 };
policyClient.version = kWKPagePolicyClientCurrentVersion;
policyClient.decidePolicyForNavigationAction = decidePolicyForNavigationAction;
policyClient.decidePolicyForNewWindowAction = decidePolicyForNewWindowAction;
WKPageSetPagePolicyClient(page), &policyClient);

然后编写回调函数

void decidePolicyForNavigationAction(WKPageRef page, WKFrameRef frame,
  WKFrameNavigationType navigationType, WKEventModifiers modifiers, WKEventMouseButton mouseButton,
  WKURLRequestRef request, WKFramePolicyListenerRef listener, WKTypeRef userData,
  const void* clientInfo)
{
  didRecvNewNavigation(frame, request, listener);
}

void decidePolicyForNewWindowAction(WKPageRef page, WKFrameRef frame,
  WKFrameNavigationType navigationType, WKEventModifiers modifiers, WKEventMouseButton mouseButton,
  WKURLRequestRef request, WKStringRef frameName, WKFramePolicyListenerRef listener, WKTypeRef userData,
  const void* clientInfo)
{
  didRecvNewNavigation(frame, request, listener);
}

之后我们在didRecvNewNavigation中统一处理

void didRecvNewNavigation(WKFrameRef frame, WKURLRequestRef request, WKFramePolicyListenerRef listener)
{
  if (页面加载完毕)
  {
    WKURLRef url = WKURLRequestCopyURL(request);
    // 处理获得的url
    WKFramePolicyListenerIgnore(listener);  // 不加载该url
  }
  else
  {
    if (WKFrameIsMainFrame(frame))  // 如果是主frame
    {
      WKFramePolicyListenerUse(listener);  // 加载url
    }
    else
    {
      WKFramePolicyListenerIgnore(listener);  // 不加载该url
    }
  }
}

判断页面加载完成的时机之前已经说了,只要用一个状态变量记录即可。这里主要讲一 下WKFramePolicyListenerRef,这个可以设置Webkit是否加载指定的URL,也就是可以过滤掉不需要的加载,提高效率。因为一 般我们需要的内容都处于mainFrame中,所以这里只加载了mainFrame的内容。

至此我们就完成了动态内容和链接的提取,通过适当的改造就可以变成自己定义的多功能爬虫了。

来源:http://www.cnblogs.com/Jiajun/archive/2012/12/12/2813888.html

img_e00999465d1c2c1b02df587a3ec9c13d.jpg
微信公众号: 猿人谷
如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】
如果您希望与我交流互动,欢迎关注微信公众号
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

目录
相关文章
|
2月前
|
数据采集 Web App开发 JSON
浏览器插件:WebScraper基本用法和抓取页面内容(不会编程也能爬取数据)
本文以百度为实战案例演示使用WebScraper插件抓取页面内容保存到文件中。以及WebScraper用法【2月更文挑战第1天】
271 2
浏览器插件:WebScraper基本用法和抓取页面内容(不会编程也能爬取数据)
|
2月前
|
数据采集 前端开发 JavaScript
Objective-C爬虫:实现动态网页内容的抓取
Objective-C爬虫:实现动态网页内容的抓取
|
2月前
|
前端开发 JavaScript 开发者
网页
网页
13 0
|
12月前
|
前端开发
做了一个网页
做了一个网页
55 0
|
12月前
|
前端开发
简单做了一个网页
简单做了一个网页
76 0
|
存储 Web App开发 数据采集
30分钟开发一款抓取网站图片资源的浏览器插件
由于业务需求, 笔者要为公司开发几款实用的浏览器插件,所以大致花了一天的时间,看完了谷歌浏览器插件开发文档,在这里特地总结一下经验, 并通过一个实际案例来复盘插件开发的流程和注意事项.
346 0
|
设计模式 前端开发 JavaScript
网页书籍介绍
网页书籍介绍
|
JavaScript 存储 前端开发
|
JavaScript 前端开发 缓存
|
JavaScript 前端开发

热门文章

最新文章

  • 1
    流量控制系统,用正则表达式提取汉字
    25
  • 2
    Redis09-----List类型,有序,元素可以重复,插入和删除快,查询速度一般,一般保存一些有顺序的数据,如朋友圈点赞列表,评论列表等,LPUSH user 1 2 3可以一个一个推
    26
  • 3
    Redis08命令-Hash类型,也叫散列,其中value是一个无序字典,类似于java的HashMap结构,Hash结构可以将对象中的每个字段独立存储,可以针对每字段做CRUD
    25
  • 4
    Redis07命令-String类型字符串,不管是哪种格式,底层都是字节数组形式存储的,最大空间不超过512m,SET添加,MSET批量添加,INCRBY age 2可以,MSET,INCRSETEX
    27
  • 5
    S外部函数可以访问函数内部的变量的闭包-闭包最简单的用不了,闭包是内层函数+外层函数的变量,简称为函数套函数,外部函数可以访问函数内部的变量,存在函数套函数
    23
  • 6
    Redis06-Redis常用的命令,模糊的搜索查询往往会对服务器产生很大的压力,MSET k1 v1 k2 v2 k3 v3 添加,DEL是删除的意思,EXISTS age 可以用来查询是否有存在1
    30
  • 7
    Redis05数据结构介绍,数据结构介绍,官方网站中看到
    21
  • 8
    JS字符串数据类型转换,字符串如何转成变量,+号只要有一个是字符串,就会把另外一个转成字符串,- * / 都会把数据转成数字类型,数字型控制台是蓝色,字符型控制台是黑色,
    19
  • 9
    JS数组操作---删除,arr.pop()方法从数组中删除最后一个元素,并返回该元素的值,arr.shift() 删除第一个值,arr.splice()方法,删除指定元素,arr.splice,从第一
    19
  • 10
    定义好变量,${age}模版字符串,对象可以放null,检验数据类型console.log(typeof str)
    19