搜索引擎与爬虫

爬虫系列:采集 JavaScript

上一期我们介绍了爬虫系列:穿越网页表单与登录窗口进行采集的相关内容,这期文章我们介绍如何采集 JavaScript 动态生成的内容(数据)。

客户端脚本语言是运行在浏览器而非服务器上的语言。客户端脚本语言成功的前提是浏览器拥有正确解释这种语言的能力(这也是在浏览器上禁止 JavaScript 非常容易的原因)。

在一定程度上,由于很难让所有浏览器开发商都认可同一个标准,所以客户端语言比服务器语言还要少很多。不过这在网络数据采集的时候是件好事:要处理的语言越少越好。

通常,在网上遇到的客户端语言只有两种:ActionScript(开发 Flash 应用的语言)和 JavaScript。今天 ActionScript 的使用率比 10 年前低很多,经常用于流媒体文件播放,用作在线游戏平台,或者是网站上那些没人想看更没人点击的“介绍”页面。总之,采集 Flash 页面的需求并不多,所以我们还是重点介绍新式页面中普遍使用的客户端语言:JavaScript。

到目前为止,JavaScript 是网络上最常用也是支持者最多的客户端脚本语言。它可以收集用户的追踪数据,不需要重载页面直接提交表单,在页面上嵌入多媒体文件,甚至是运行网页游戏。那些看起来非常简单的页面背后通常使用了许多 JavaScript 文件。你可以在网页源代码的 <script> 标签之间看到它们:

<script type="text/javascript">
    require([
        'jquery'
    ], function($){
        $("#navbar_sidebar").click(function () { 
            $('body').addClass('active-sidebar');
        });

        $(document).mouseup(function(e) {
            var ctContentSidebar = $(".sidebar");
            if (!ctContentSidebar.is(e.target) && ctContentSidebar.has(e.target).length === 0) {
                $('body').removeClass('active-sidebar');
                $('body').removeClass('active-sidebar-right');
            }
        });
    });
</script>

JavaScript 简介

对要采集的语言做些了解会很有作用。自己熟悉一下 JavaScript 总会有好处。

JavaScript 是一种弱类型语言,其语法通常可以与 C++ 和 Java 做对比。虽然语法中的一些元素,比如操作符、循环条件和数组,都和 C++、Java 语法很接近,但是 JavaScript 弱类型和脚本形式被一些程序员看成是折磨人的怪兽。

例如,下面的 JavaScript 程序通过递归方式计算 Fibonacci 序列,最后把结果打印到浏览器的开发者控制台里:

<script>
    function fibonacci(a,b){
        var nextNum=a+b;
        console.log(nextNum+ " is in the Fibonacci sequence");
        if(nextNum<100){
            fibonacci(b,nextNum);
        }
    }
    fibonacci(1,1)
</script>

可以看到 JavaScript 里面的所有变量都使用 var 关键词经行定义。这与 PHP 里面的 $ 符号类似,或者与 Java 和 C++ 里的类型声明(int,String,List 等)。Python 不太一样,它没有这种显式的变量声明。

JavaScript 还有一个非常好的特性,就是把函数作为变量使用:

<script>
    function fibonacci(){
        var a=1;
        var b=1;
        return function(){
            var temp=b;
            b=a+b;
            a=temp;
            return b;
        }
    }
    var fibInstance=fibonacci();
    console.log(fibInstance()+" is in the Fibonacci sequence");
    console.log(fibInstance()+" is in the Fibonacci sequence");
    console.log(fibInstance()+" is in the Fibonacci sequence");
</script>

第一次看到这段代码可能有点头晕,不过如果我们把这个特性看成 Lamdba 表达式,就会很简单了就。变量 fibonacci 被定义成一个函数。它的函数值返回一个递增的 Fibonacci 序列里较大的值。每次当它被调用时会返回一个递增的 Fibonacci 序列里较大的值。每次当它被调用时会返回 Fibonacci 的计算函数,再次执行序列计算,并增加函数变量的值。

虽然这样看起来有点儿复杂,但是在解决一些问题的时,比如计算 Fibonacci 序列值,这种模式还是比较合适的。在处理用户行为和回调函数时,把函数作为变量进行传递时非常方便的,另外在阅读 JavaScript 代码的时候必须适应这种编程方式。

常用 JavaScript 库

虽然了解 JavaScript 语言本身的语法很重要,但是在现代的网络中你必然要使用至少一种 JavaScript 语言的第三方库。在你查看网页源代码的时候,你可能看到很多常用的 JavaScript 库。

用 Python 执行 JavaScript 的代码效率非常低,既费时又费力。尤其是在处理较大规模的 JavaScript 代码时。如果有绕过 JavaScript 并直接解析它的方法(不需要执行它就可以获得信息)会非常实用,可以帮你避开一大堆 JavaScript 的麻烦事。

  • jQuery

jQuery 是一个十分常见的库,70% 最流行的网站(约 200 万)和约 30% 的其他网站(约 2 亿)都在使用。一个网站使用 jQuery 的特征,就是源代码包含了 jQuery 入口,比如:

如果你在一个网站上看到了 jQuery,那么你采集这个网站的时候就需要格外小心。jQuery 可以动态的创建 HTML 内容,只是在 JavaScript 代码执行之后才会显示。如果你用传统的方法采集页面内容,就只能获得 JavaScript 代码执行之前页面的内容。

另外,这些页面可能包含动画、用户交互内容和嵌入式媒体,这些内容对网络数据采集都是挑战。

  • Google Analytics

有一半的网站都在使用 Google Analytics,他可能是网站最常用的 JavaScript 库和最受欢迎的用户追踪工具。

很容易判断一个页面是不是使用了 Google Analytics。如果网站使用了它,在页面底部会有类似如下所示的 JavaScript 代码:

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-TWMC54YNYC"></script>
<script>
    window.dataLayer = window.dataLayer || [];

    function gtag() {
        dataLayer.push(arguments);
    }

    gtag('js', new Date());

    gtag('config', 'G-TWMC54YNYC');
</script>

如果一个网站使用了 Google Analytics 或者其他类似的网络分析系统,而你不想让网站管理员知道你在采集数据,就要确保把那些分析工具的 cookie 或者所有 cookie 都关掉。

Ajax 和动态 HTML

到目前为止,我们与网站服务器唯一的通信方式,就是发送 HTTP 请求获取新页面。如果提交表单之后,或从服务器获取信息之后,网站的页面不需要重新刷新,那么你访问的网站就在用 Ajax 技术。

与一些人的印象不太一样,Ajax 其实并不是一门语言,而是用来完成网络任务(可以认为它与网络数据采集差不多)的一系列技术。Ajax 全称是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML),网站不需要使用单独的页面请求就可以和网络服务器进行交互(收发信息)。需要注意的是:你不应该说”这个网站是 Ajax 写的”。正确的说法应该是“这个表单用 Ajax 与网络服务器通信”。

和 Ajax 一样,动态 HTML(dynamic HTML, DHTML)也是一系列用于解决网络问题的技术集合。DHTML 是用客户端语言改变页面的 HTML 元素(HTML、CSS,或者二者皆被改变)。比如,页面上的按钮只有当用户移动鼠标之后才出现,背景色可能每次点击都会改变,或者用一个 Ajax 请求触发页面加载一段新内容。

值得注意的是,虽然“动态”这个词往往和“移动”或“变化”联系在一起,但是那些使用了交互 HTML 组件、图像可以移动,或者带有嵌入式媒体文件的网页,并不一定就是动态 HTML,即使页面看起来是动态的。另外,一些表面看起来极其单调、静态的页面,底层却可能是用 DHTML 处理的,关键要看有没有用 JavaScript 控制 HTML 和 CSS 元素。

如果你采集过许多网站,很可能会遇到这样一种情况。你在浏览器上看到的内容,与你用爬虫从网站上采集的内容不一样。你可能会怀疑自己是不是哪个细节没处理好,希望找出内容采集不出来的原因。

有时你还会发现,网页用一个加载页面把你引到另一个页面上,但是网页的 URL 链接在这个过程中一直没有变化。

这些都是因为你的爬虫不能执行那些让页面产生各种神奇效果的 JavaScript 代码。如果网站的 HTML 页面没有运行 JavaScript,就可能和你在浏览器里看到的样子完全不同,因为浏览器可以正确地执行 JavaScript。

那些使用了 Ajax 或 DHTML 技术改变/加载内容的页面,可能有一些采集手段,但是用 Python 解决这个问题只有两种途径:直接从 JavaScript 代码里采集内容,或者用 Python 的第三方库运行 JavaScript,直接采集你在浏览器里看到的页面。