爬虫篇:动态网页的处理方式(中)——渲染动态网页

2023-11-18

每篇一句:

A strong man will struggle with the storms of fate.


前言:

上一篇文章中我们介绍了爬取动态网页的一种方式:逆向工程

这种方式有一点美中不足:这种方式要求我们对JavaScript以及Ajax有一定的了解,而且当网页的JS代码混乱,难以分析的时候,上述过程会花费我们大量的时间和精力。

这时候,如果对爬虫的执行效率没有过多要求,又不想浪费太多时间在了解JavaScript代码逻辑、找寻Ajax请求链接上,我们可以尝试另一种方式——渲染动态网页


浏览器渲染引擎:

简介:

​ 在介绍这种方式之前,我们需要首先了解一些浏览器渲染引擎的基本知识。

渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。浏览器向服务器发送请求,得到服务器返回的资源文件后,经过需要渲染引擎的处理,将资源文件显示在浏览器窗口中。

目前使用较为广泛的渲染引擎有两种:

  • webkit——使用者有Chrome, Safari
  • Geoko——使用者有Firefox
渲染主流程:
  • 渲染引擎首先通过网络获得所请求文档的内容,通常以8K分块的方式完成。
  • 下面是渲染引擎在取得内容之后的基本流程:

     解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树

  • 渲染引擎开始解析html,并将标签转化为内容树中的dom节点。如果遇到JS,那么此时会启用单独连接进行下载,并且在下载后进行解析。

  • 接着,它解析外部CSS文件及style标签中的样式信息。这些样式信息以及html中的可见性指令将被用来构建另一棵树——render树。

    Render树由一些包含有颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。

  • Render树构建好了之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。

  • 再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。

    想要了解更多有关浏览器渲染引擎的知识,可以阅读这些:

思考:

了解了浏览器渲染引擎的基本原理,我们可以发现:

  • 当浏览器渲染引擎完成了dom树以及render树的构建之后,树中就已经包含了我们在浏览器窗口中可以看到的所有数据。

那么我们就有了一种爬取动态网页的新思路:

  • 在浏览器渲染引擎执行layout以及printing之前,得到dom树或者render树,从树中获取动态加载的数据。

渲染动态网页:

有两种选择:

  1. 自己从头实现一个浏览器渲染引擎,在合适的时机返回构建的dom树或render树:

    这需要进行大量的工作,需要考虑html、js、css等不同格式文件的解析方式以及解析顺序等。

    本人参考以下资料进行了尝试,但最终没有成功,有兴趣的可以尝试下。

  2. 利用已有的渲染引擎。

    接下来将使用WebKit 渲染引擎,通过 PySide 这个python库可以获得该引擎的一个便捷接口。

示例:

还是以 新浪读书——书摘 为例,可以发现:页面中文章列表的部分是动态加载的。

使用PySide库进行处理的示例代码如下:

# coding=utf-8

from PySide.QtGui import *
from PySide.QtCore import *
from PySide.QtWebKit import *


if __name__ == '__main__':

    url = "http://book.sina.com.cn/excerpt/rwws/"

    app = QApplication([])  # 完成其他Qt对象之前,必须先创建该对象
    webview = QWebView()  # 该对象是Web 对象的容器

    # 调用show方法显示窗口
    # webview.show()

    # 设置循环事件, 并等待网页加载完成
    loop = QEventLoop()
    webview.loadFinished.connect(loop.quit)
    webview.load(QUrl(url))
    loop.exec_()

    frame = webview.page().mainFrame()  # QWebFrame类有很多与网页交互的有用方法

    # 得到页面渲染后的html代码
    html = frame.toHtml()

    print html

通过print语句,我们可以发现:页面的源码html中已经包含了动态加载的内容。

  • 与网站交互:

    得到动态加载的内容后,需要解决的另一个问题是翻页问题。还好PySide库的QWebKit模块还有一个名为QWebFrame的类,支持很多与网页的交互操作。

    • 如“点击”:
    
    # 根据CSS Selector 找到所需“进行翻页”的元素
    
    elem = frame.findFirstElement('#subShowContent1_loadMore')
    
    # 点击:通过evaluateJavaScript()函数可以执行Js代码
    
    elem.evaluateJavaScript('this.click()')
    • 除了点击事件,还可以进行填充表单,滚动窗口等操作
  • 需要注意的是,在进行了翻页、或者获取更多内容时,一个最大的难点在于如何确定页面是否完成了加载,因为我们难以估计Ajax事件或者Js准备数据的时间。对于这个问题有两种解决思路:

    1. 等待固定的一段时间,比如time.sleep(3):这种方法容易实现,但效率较低。

    2. 轮询网页,等待特定内容出现:

      这种方法虽然会在检查是否加载完成时浪费CPU周期,但更加可靠。

      以下是一个简单的实现:

      elem = None
      while not elem:
       app.processEvents()
       elem = frame.findAllElemnets('#pattern')

      代码循环,直到出现特定元素。每次循环,调用app.processEvents()方法,用于给Qt事件循环执行任务的时间,比如响应点击事件。

更多有关PySide的内容请看这里:PySide官方文档

但是PySide毕竟是一个为了Python的GUI 编程而开发的, 其功能对于爬虫来说实在是太过于庞大,所以我们可以把爬虫经常使用的功能进行封装,来提升编写爬虫的效率。


对PySide 常用功能的封装 —— ghost.py

ghost.py 是目前一个针对爬虫且功能比较完善的PySide的封装模块,使用它可以很方便的进行数据采集。

还是以获取列表页中每篇文章详情页地址为目标,直接看示例代码:

# coding=utf-8

import re
import time

from ghost import Ghost, Session


class SinaBookSpider(object):

    # 初始化相关参数
    gh = Ghost()
    ss = Session(gh, display=True)  # 设置display为true, 方便调试

    total = 1526  # 预先计算的总数据量
    count = 0  # 已爬取的数据量

    # 记录解析以及翻页位置
    location = 0
    click_times = 0

    def run(self):
        """
        开始爬虫
        :return:
        """
        # 打开网页
        self.ss.open("http://book.sina.com.cn/excerpt/rwws/")
        # 等待数据加载完成
        self.ss.wait_for_selector('#subShowContent1_static > div:nth-child(20)')

        self.parselist()

        while self.count < self.total:
            if self.click_times is 0:
                # 点击加载更多
                self.ss.click('#subShowContent1_loadMore')
                # 每次翻页,或加载更多,要等待至加载完成
                self.ss.wait_for_selector('#subShowContent1_static > div:nth-child(21)')

                self.click_times += 1
                self.parselist()
            elif self.click_times is 1:
                self.ss.click('#subShowContent1_loadMore')
                self.ss.wait_for_selector('#subShowContent1_static > div:nth-child(41)')

                self.click_times += 1
                self.parselist()
            elif self.click_times is 2:
                self.ss.click('#subShowContent1_page .pagebox_next a')
                self.ss.sleep(2)

                self.click_times = 0
                self.location = 0
                self.parselist()

    def parselist(self):
        """
        解析列表页
        :return:
        """
        html = self.ss.content.encode('utf8')
        # print html

        pattern = re.compile(r'<div class="item"><h4><a href="(.*?)" target="_blank">', re.M)
        links = pattern.findall(html)

        for i in range(self.location, len(links)):
            print links[i]
            self.count += 1
            self.location += 1
        print self.count


if __name__ == '__main__':
    spider = SinaBookSpider()
    spider.run()

代码地址:dynamic-web-process —— GitHub

补充:

  • ghost.py对直接获取元素支持的不是很好,但可以借助BeautifulSoup正则表达式来解决。

  • ghost.py支持与网页的简单交互,如点击,填充表单等

    - set_field_value(*args, **kwargs)
    - fill(*args, **kwargs)
    - click(*args, **kwargs)
  • ghost.py很好的解决了确定元素加载完成的问题,通过以下方法可以让爬虫等待,直到满足设置的条件。

    - wait_for(condition, timeout_message, timeout=None)
    - wait_for_page_loaded(timeout=None)
    - wait_for_selector(selector, timeout=None)
    - wait_for_text(text, timeout=None)
    - wait_while_selector(selector, timeout=None)

    有关更多ghost.py的方法请看这里:Ghost.py


文中有什么错误或不足之处,欢迎指出!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

爬虫篇:动态网页的处理方式(中)——渲染动态网页 的相关文章

  • 托管函数和非托管函数如何互相传递ansi字符串?

    先来介绍两个预处理指令 pragma unmanaged pragma managed 这两个预处理指令 控制函数编译成托管函数 还是非托管函数 是函数级别的预控制指令 1 托管函数传送字符串给非托管函数 分三步走 a 先调用StringT
  • echars水状_Echarts饼状图属性设置

    标题 title text 学生生源地来源分布图 subtext 模拟数据 x 设置水平安放位置 默认左对齐 可选值 center left right number x坐标 单位px x center y 设置垂直安放位置 默认全图顶端
  • IPv6地址结构、地址分类以及表示方法

    IPv6地址的表示方法 IPv6地址总长度为128比特 通常分为8组 每组为4个十六进制数的形式 每组十六进制数间用冒号分隔 例如 FC00 0000 130F 0000 0000 09C0 876A 130B 这是IPv6地址的首选格式

随机推荐

  • Python从入门到精通,100天就够了!(2)——分支结构与循环结构

    文章目录 一 分支结构 选择结构 1 分支结构初体验 2 分支结构练习 二 循环结构 1 for in 循环 2 for in 循环练习 3 while 循环 4 break和continue 5 while 循环练习 6 嵌套的循环结构
  • 【零基础 快速学Java】韩顺平 p35-51 变量、数据类型(整型,浮点,字符,布尔)、重要编码,API文档

    课程 p35 51 变量 变量是程序的基本组成单位 变量有三个基本要素 类型 名称 值 变量相当于内存中一个数据存储空间的表示 通过变量名可以访问变量 值 四个常用数据类型 int double char String String的S要大
  • LuCI - 网页用户配置界面开发

    luci 参考资料 http luci subsignal org trac wiki Documentation http wiki openwrt org doc techref preinit mount first boot htt
  • 【IOS 开发学习总结-OC-4】objective-c 运算符

    运算符是用于数据运算 赋值和比较等的特殊符号 objective c 语言中的运算符有以下几种 算术运算符 赋值运算符 比较运算符 逻辑运算符 位运算符 类型相关运算符 算术运算符 有7个基本运算符 加法运算符 减法运算符 还可以作为负号使
  • Ubuntu下安装jdk1.7

    转载 http blog csdn net zth1002 article details 51383508 1 去到官网下载jdk 点击打开链接 2 安装WinSCP工具连接到Linux系统上面在 usr lib里面新建一个jvm文件夹
  • UPF与低功耗设计实现实例 -- 附UPF与DC综合脚本

    原文链接 https www eefocus com industrial electronics 473034 本文摘自 数字集成电路低功耗物理实现技术与 UPF 孙轶群 sun yiqun nationz com cn 国民技术股份有限
  • Tomcat Port8009与AJP13协议

    Tomcat最主要的功能是提供Servlet JSP容器 尽管它也可以作为独立的Java Web服务器 它在对静态资源 如HTML文件或图像文件 的处理速度 以及提供的Web服务器管理功能方面都不如其他专业的HTTP服务器 如IIS和Apa
  • java.util.ConcurrentModificationException

    错误类型 java util ConcurrentModificationException 出现原因 在foreach增强循环中进行了元素删除操作 只要是集合这样做都会出现这个问题 ConcurrentModificationExcept
  • Unity 2D人物移动实现

    Unity 2D人物移动实现 效果展示 代码 using System Collections using System Collections Generic using UnityEngine public class Parentne
  • 基础网络故障检测

    基本网络故障检测 背景 随着网络的普遍应用 客户在使用网络时随时随刻会出现网络不通或严重卡顿现象 因如今的网络应用量大且拓扑负责 好的网络故障检测方法技术支持工程师必备的小技能包 下面我分享下我学到和用到的企业网络故障检查和排错思路 问题
  • 9:00面试,9:06就出来了,问的问题实在有点变态。。。

    从小厂出来 没想到在另一家公司又寄了 到这家公司开始上班 加班是每天必不可少的 看在钱给的比较多的份上 就不太计较了 没想到5月一纸通知 所有人不准加班 加班费不仅没有了 薪资还要降40 这下搞的饭都吃不起了 还在有个朋友内推我去了一家互联
  • 你了解docker技术的意义及用途吗?

    要说2015年扩张得最快的技术 那一定要数Docker不可 随着 互联网 的被重视以及大数据 云服务的兴起 相关的技术更是如雨后春笋般冒起 当中 有很多性能优良的技术更是先赢一步 不仅进入IT者的视界 更被众多的企业委以重任 现在 就随大圣
  • fcgi程序两种编写风格

    fcgi进程可以写成单线程的 也可以写成多线程的 单线程就是main函数中有一个死循环 一直等待接受请求 有请求过来时 就处理请求 并返回结果 没有并发性 多线程也分两种模式 一种是main函数起多个线程 每个线程都独立接受请求 另一种是m
  • Debian10搭建Apache2

    文章目录 1 所需设备 2 任务描述 3 配置Apache2 3 测试 1 所需设备 Debian10Server IPaddress 10 1 1 1 netmask 255 255 255 0 IPaddress 10 1 1 2 ne
  • 数组--二维数组

    JAVA的二维数组 二维数组 在二维数组中的每一个元素中都是一个一维数组 意思就是两个一维数组相嵌套而成的数组 二维数组的声明 有一下两种 int a int a 在声明时 一般推荐第一种情况 方便代码阅读 二维数组在创建时也要给定数组的长
  • Android Studio :Could not find com.android.tools.build:aapt2

    Android Studio 又一次升级 从Android Studio3 2升级到Android Studio3 3 每次升级都是抱着必死的很大的决心进行升级 就怕Android Studio罢工 到时候一番乱搞 问题大概是下面这个样子的
  • 机器学习练习题(二)

    从牛客网找来得题目 解析是题目下的高赞答案 1 下面有关分类算法的准确率 召回率 F1 值的描述 错误的是 a 准确率是检索出相关文档数与检索出的文档总数的比率 衡量的是检索系统的查准率 b 召回率是指检索出的相关文档数和文档库中所有的相关
  • 若依框架快速开发项目(避坑超详细)

    若依框架快速开发项目 避坑超详细 初衷 若依框架使用及其普遍 是一个非常优秀的开源框架 框架本身的权限系统 字典设置以及相关封装 安全拦截相当完善 本人受益匪浅 学学到了许多 在这里 先向原创作者致敬 本人刚刚接触这个框架的时候 很迷茫 几
  • 前端多级搜索条件,不走后台

    handleSearch 备份数据 let arr JSON parse JSON stringify this tableData form是查询条件 通过遍历key值来循环处理 Object keys this form forEach
  • 爬虫篇:动态网页的处理方式(中)——渲染动态网页

    每篇一句 A strong man will struggle with the storms of fate 前言 上一篇文章中我们介绍了爬取动态网页的一种方式 逆向工程 这种方式有一点美中不足 这种方式要求我们对JavaScript以及