当前位置: 首页 > news >正文

(转载)利用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

相关文章:

  • Silverlight数据绑定引擎
  • 拆箱陷阱
  • 带你攀顶云端高级认证,有这回事?
  • Tomcat7 安装使用及jvm连接数参数调优
  • 开发可统计单词个数的Android驱动程序(3)
  • 使用Vitamio打造自己的Android万能播放器(12)—— 播放网络视频缓冲处理
  • 调试工具-gprof
  • 分页插件jquery.pagination.js
  • C#中ref和out的使用小结
  • Cocoa.Programming.for.Mac.OS.X 3rd 前8章小知识点
  • 用apache搭建web服务器
  • android: scrollbarStyle
  • LINQ 关键字
  • Linux 一次性杀死用户所有进程
  • 解决websphere6.1必须为元素类型web-app声明属性version
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • 03Go 类型总结
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • css的样式优先级
  • JavaScript DOM 10 - 滚动
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • rc-form之最单纯情况
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 程序员最讨厌的9句话,你可有补充?
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 简单实现一个textarea自适应高度
  • 前嗅ForeSpider采集配置界面介绍
  • Python 之网络式编程
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • $(selector).each()和$.each()的区别
  • (¥1011)-(一千零一拾一元整)输出
  • (23)Linux的软硬连接
  • (C语言)编写程序将一个4×4的数组进行顺时针旋转90度后输出。
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (篇九)MySQL常用内置函数
  • (转)VC++中ondraw在什么时候调用的
  • .NET CLR Hosting 简介
  • .Net CoreRabbitMQ消息存储可靠机制
  • .NET/C# 使用反射注册事件
  • .NET开发不可不知、不可不用的辅助类(三)(报表导出---终结版)
  • /run/containerd/containerd.sock connect: connection refused
  • [AutoSar NVM] 存储架构
  • [Avalon] Avalon中的Conditional Formatting.
  • [C++]18:set和map的使用
  • [CareerCup] 13.1 Print Last K Lines 打印最后K行
  • [CVPR2021]Birds of a Feather: Capturing Avian Shape Models from Images
  • [C和指针].(美)Kenneth.A.Reek(ED2000.COM)pdf
  • [git] windows系统安装git教程和配置
  • [iOS开发]iOS中TabBar中间按钮凸起的实现
  • [javascript]Tab menu实现
  • [Java安全入门]三.CC1链
  • [JS]JavaScript 简介
  • [LaTex]arXiv投稿攻略——jpg/png转pdf