Python3 网络爬虫开发实战

Python3 网络爬虫开发实战

第2版
✍️ 作者
崔庆才
📅 出版日期
2021-11-01
📄 页数
918页
🔢 ISBN
9787115577092
📚 分类
TP311.561
⭐ 评分
9.1 ★★★★☆

第1章 爬虫基础

写爬虫前需要了解一些基础知识,如 HTTP 原理、网页的基础知识、爬虫的基本原理、Cookie 的基本原理、多进程和多线程的基本原理等,了解这些内容有助于我们更好地理解和编写网络爬虫相关的程序。

本章我们就对这些基础知识做一个简单的总结。

第2章 基本库的使用

学习爬虫,其基本的操作便是模拟浏览器向服务器发出请求,那么我们需要从哪个地方做起呢? 请求需要我们自己构造吗? 我们需要关心请求这个数据结构怎么实现吗? 需要了解 HTTP、TCP、IP 层的网络传输通信吗? 需要知道服务器如何响应以及响应的原理吗?

可能你无从下手,不过不用担心,Python 的强大之处就是提供了功能齐全的类库来帮助我们实现这些需求。最基础的 HTTP 库有 urllib、requests、httpx 等。

拿 urllib 这个库来说,有了它,我们只需要关心请求的链接是什么,需要传递的参数是什么,以及如何设置可选的请求头,而无须深入到底层去了解到底是怎样传输和通信的。有了 urllib 库,只用两行代码就可以完成一次请求和响应的处理过程,得到网页内容,是不是感觉方便极了?

接下来,就让我们从最基础的部分开始了解 HTTP 库的使用方法吧。

第3章 网页数据的解析提取

上一章我们实现了一个最基本的爬虫,但提取页面信息时使用的是正则表达式,过程比较烦琐,而且万一有地方写错了,可能会导致匹配失败,所以使用正则表达式提取页面信息多少还是有些不方便。

对于网页的节点来说,可以定义 id、class 或其他属性,而且节点之间还有层次关系,在网页中可以通过 XPath 或 CSS 选择器来定位一个或多个节点。那么,在解析页面时,利用 XPath 或 CSS 选择器提取某个节点,然后调用相应方法获取该节点的正文内容或者属性,不就可以提取我们想要的任意信息了吗?

在 Python 中,怎样实现上述操作呢? 不用担心,相关的解析库非常多,其中比较强大的有 lxml、BeautifulSoup、pyquery、parsedl 等。本章就来介绍这几个解析库的用法。有了它们,我们就不用再为正则表达式发愁,解析效率也会大大提高。

第4章 数据的存储

用解析器解析出数据后,接下来就是存储数据了。数据的存储形式多种多样,其中最简单的一种是将数据直接保存为文本文件,如 TXT、JSON、CSV 等。还可以将数据保存到数据库中,如关系型数据库 MySQL,非关系型数据库 MongoDB、Redis 等。除了这两种,也可以直接把数据存储到一些搜索引擎(如 Elasticsearch)中,以便检索和查看。

本章我们就来了解一些基本的数据存储的操作。

第5章 Ajax数据爬取

有时我们用 requests 抓取页面得到的结果,可能和在浏览器中看到的不一样: 在浏览器中可以看到正常显示的页面数据,而使用 requests 得到的结果中并没有这些数据。这是因为 requests 获取的都是原始 HTML 文档,而浏览器中的页面是 JavaScript 处理数据后生成的结果,这些数据有多种来源: 可能是通过 Ajax 加载的,可能是包含在 HTML 文档中的,也可能是经过 JavaScript 和特定算法计算后生成的。

对于第一种来源,数据加载是一种异步加载方式,原始页面最初不会包含某些数据,当原始页面加载完后,会再向服务器请求某个接口获取数据,然后数据才会经过处理从而呈现在网页上,这其实是发送了一个 Ajax 请求。

按照 Web 的发展趋势来看,这种形式的页面越来越多。甚至网页的原始 HTML 文档不会包含任何数据,数据都是通过 Ajax 统一加载后呈现出来的,这样使得 Web 开发可以做到前后端分离,减小服务器直接渲染页面带来的压力。

所以如果遇到这样的页面,直接利用 requests 等库来抓取原始 HTML 文档,是无法获取有效数据的,这时需要分析网页后台向接口发送的 Ajax 请求。如果可以用 requests 模拟 Ajax 请求,就可以成功抓取页面数据了。

所以,本章我们的主要目的是了解什么是 Ajax,以及如何分析和抓取 Ajax 请求。

第6章 异步爬虫

我们知道爬虫是 IO 密集型任务,例如使用 requests 库来爬取某个站点,当发出一个请求后,程序必须等待网站返回响应,才能接着运行,而在等待响应的过程中,整个爬虫程序是一直在等待的,实际上没有做任何事情。对于这种情况,我们有没有优化方案呢? 当然有,本章我们就来了解一下异步爬虫的基本概念和实现。

当然有,本章我们就来了解一下异步爬虫的基本概念和实现。

第7章 JavaScript动态渲染页面爬取

在第 5 章中,我们了解了 Ajax 数据的分析和爬取方式,这其实也是 JavaScript 动态渲染页面的一种情形,通过直接分析 Ajax,使我们仍然可以借助 requests 或 urllib 实现数据爬取。

不过 JavaScript 动态渲染的页面不止 Ajax 一种。例如,有些页面的分页部分由 JavaScript 生成,而非原始 HTML 代码,这其中并不包含 Ajax 请求。再例如 ECharts 的官方实例,其图形都是经过 JavaScript 计算之后生成的。还有类似淘宝这种页面,即使是 Ajax 获取的数据,其 Ajax 接口中也含有很多加密参数,使我们难以直接找出规律,也很难直接通过分析 Ajax 爬取数据。

为了解决这些问题,我们可以直接模拟浏览器运行,然后爬取数据,这样就可以实现在浏览器中看到的内容是什么样,爬取的源码就是什么样——所见即所爬。此时我们无须去管网页内部的 JavaScript 使用什么算法渲染页面,也不用管网页后台的 Ajax 接口到底含有哪些参数。

Python 提供了许多模拟浏览器运行的库,例如 Selenium、Splash、Puppetter、Playwright 等,可以帮助我们实现所见即所爬,有了这些库,就不用再为如何爬取动态渲染的页面发愁了。

第8章 验证码的识别

各类网站采用了各种各样的措施反爬虫,其中一个便是验证码。随着技术的发展,验证码的花样越来越多,由最初只是几个数字组合而成的简单图形,发展到加入了英文字母和混淆曲线,还有一些网站使用中文字符验证码,这无疑使识别变得愈发困难。

12306 验证码的出现使行为验证码开始发展,相信用过 12306 的用户多少都为它的验证码头疼过,需要识别文字,然后点击与文字描述相符的图片,只有所点的图片完全正确,才能通过验证。随着技术的发展,这种交互式验证码越来越多,如滑动验证码需要将滑块拖动到指定位置才能完成验证,点选验证码需要点击正确的图形或文字才能通过验证。

验证码变复杂的同时,爬虫的工作也变得越发艰难,有时候必须通过验证才可以访问页面。

本章统一讲解验证码的识别问题,涉及的验证码有图形验证码、滑动验证码、点选验证码和手机验证码等,这些验证码的识别方式和思路各有不同,有的直接使用图像处理库就能完成,有的则需要借助深度学习技术完成,还有的要借助一些工具和平台完成。虽说技术各有不同,但了解这些验证码的识别方式之后,我们就可以举一反三,使用类似的方法识别其他类型的验证码。

第9章 代理的使用

在使用爬虫的过程中经常会遇到这样的情况,爬虫最初还可以正常运行,正常爬取数据,一切看起来都是那么美好,然而一杯茶的工夫过去,就可能出现了错误,比如返回 403 Forbidden,这时打开网页,可能会看到"您的 IP 访问频率太高"这样的提示,或者跳出一个验证码让我们识别,通过之后才可以正常访问,但是过一会儿又会变成这样。

出现上述现象的原因是网站采取了一些反爬虫措施。例如服务器会检测某个 IP 在单位时间内的请求次数,如果这个次数超过了指定的阈值,就直接拒绝服务,并返回一些错误信息,这种情况可以称为封 IP。这样,网站就成功把我们的爬虫封禁了。

既然服务器检测的是某个 IP 在单位时间的请求次数,那么借助某种方式把 IP 伪装起来,让服务器识别不出是由我们本机发起的请求,不就可以成功防止封 IP 了吗? 这时代理就派上用场了。本章会详细介绍代理的基本知识以及各种代理的使用方式,包括代理的设置、代理池的维护、付费代理的使用、ADSL 拨号代理的搭建方法等内容,希望能够帮助爬虫脱离封 IP 的苦海。

第10章 模拟登录

很多情况下,网站的一些数据需要登录才能查看,如果想要爬取这部分数据的话,就需要实现模拟登录的一些机制。

模拟登录现在主要分为两种模式,一种是基于 Session 和 Cookie 的模拟登录,一种是基于 JWT (JSON Web Token) 的模拟登录。

对于第一种模式,我们已经学习过 Session 和 Cookie 的用法。简单来说,打开网页后模拟登录,服务器会返回带有 Set- Cookie 字段的响应头,客户端会生成对应的 Cookie,其中保存着与 SessionID 相关的信息,之后发送给服务器的请求都会携带这个生成的 Cookie。服务器接收到请求后,会根据 Cookie 中保存的 SessionID 找到对应的 Session,同时校验 Cookie 里的相关信息,如果当前 Session 是有效的并且校验成功,服务器就判断当前用户已经登录,返回所请求的页面信息。所以,这种模式的核心是获取客户端登录后生成的 Cookie。

对于第二种模式也是如此,现在有很多网站采取的开发模式是前后端分离式,所以使用 JWT 进行登录校验越来越普遍。在请求数据时,服务器会校验请求中携带的 JWT 是否有效,如果有效,就返回正常的数据。所以,这种模式其实就是获取 JWT。

基于分析结果,我们可以手动在浏览器里输入用户名和密码,再把 Cookie 或者 JWT 复制到代码中来请求数据,但是这样做明显会增加人工工作量。实现爬虫的目的不就是自动化吗? 所以我们要做的就是用程序来完成这个过程,或者说用程序模拟登录。

本章我们将介绍模拟登录的相关内容。

第11章 JavaScript 逆向爬虫

随着大数据时代的发展,各个公司的数据保护意识越来越强,大家都在想尽办法保护自家产品的数据,不让它们轻易地被爬虫爬走。由于网页是提供信息和服务的重要载体,所以对网页上的信息进行保护就成了一个至关重要的环节。

网页是运行在浏览器端的,当我们浏览一个网页时,其 HTML 代码、JavaScript 代码都会被下载到浏览器中执行。借助浏览器的开发者工具,我们可以看到网页加载过程中所有网络请求的详细信息,也能清楚地看到网站运行的 HTML 代码和 JavaScript 代码。这些代码里就包含了网站加载的全部逻辑,比如加载哪些资源,请求接口是如何构造的,页面是如何渲染的,等等。正因为代码是完全透明的,所以如果我们能研究明白其中的执行逻辑,就可以模拟各个网络请求,进行数据爬取了。

然而,事情没有想象得那么简单,随着前端技术的发展,前端代码的打包技术、混淆技术、加密技术也层出不穷,借助于这些技术,各个公司可以在前端对 JavaScript 代码采取一定的保护,比如变量名混淆、执行逻辑混淆、反调试、核心逻辑加密等,这些保护手段使得我们没法很轻易地找出 JavaScript 代码中包含的执行逻辑。

在前几章的案例中,我们也试着爬取了各种形式的网站。其中有些网站的数据接口是没有任何验证或加密参数的,我们可以轻松模拟并爬取其中的数据。但有的网站稍显复杂,网站的接口中增加了一些加密参数,同时对 JavaScript 代码采取了上文所述的一些防护措施。当时我们没有尝试去破解,而是用类似 Selenium 等工具模拟浏览器的执行方式,进行"所见即所得"的爬取。其实对于后者,我们还有另外一种解决方案: 逆向 JavaScript 代码,找出其中的加密逻辑,直接实现该加密逻辑进行爬取。如果加密逻辑过于复杂,我们也可以找出一些关键入口,从而实现对加密逻辑的单独模拟执行和数据爬取。这些方案的难度可能很大,比如关键入口很难寻找或者加密逻辑难以模拟,可是一旦成功找到突破口,我们便不用借助 Selenium 等工具进行整页数据的渲染,爬取效率会大幅提高。

在本章中,我们首先会对 JavaScript 防护技术进行介绍,然后介绍一些常用的 JavaScript 逆向技巧,包括浏览器工具的使用、Hook 技术、AST 技术、特殊混淆技术的处理、WebAssembly 技术的处理。了解了这些技术,我们可以更从容地应对 JavaScript 防护技术。

第12章 App 数据的爬取

截至目前,我们介绍的都是爬取网页数据相关的内容。但随着移动互联网的发展,越来越多的企业不再提供网页端的服务,而是直接开发了 App,更多更全的信息都是通过 App 展示的。

那我们可以爬取 App 的数据吗? 当然可以。

大部分 App 使用的数据通信协议也是基于 HTTP/HTTPS 的,App 内部一些页面交互和数据通信的背后也都有对应的 API 来处理,例如某个页面呈现的数据几乎都来源于某个 API。为了更好地理解 App 中的数据加载,我们将其类比于网页中的 Ajax 请求和数据渲染,其基本过程是 App 向服务器发起一个 HTTP/HTTPS 请求,然后接收并解析服务器的响应内容,之后将得到的数据呈现出来。

在网页中,我们可以借助浏览器开发者工具中的 Network 面板看到网页中产生的所有网络请求和响应内容,然而 App 怎么办呢? 要想拦截 App 中的网络请求,就得用到抓包工具了,例如 Charles、Fiddler、mitmproxy 等,我们可以通过这些工具拦截 App 和 API 通信的请求内容和响应内容,如果能从中找到一定的规律,就可以用程序直接构造请求来模拟 API 的请求,从而完成数据爬取。

和网页一样,App 为了更好地保护数据不被爬取,其对应的 API 请求中也会出现加密参数,如果我们因此找不到对应的规律,那么即使抓到了包,也不好直接构造请求完成数据爬取。这时就需要想各种办法了,例如直接拦截所有请求的响应内容并实时处理、使用与网页中的 Selenium 类似的工具完成"所见即所爬"、直接 HookApp 中的关键方法来获取数据、直接逆向 App 找到其中的接口参数逻辑,等等。

本章的介绍侧重于比较基础的 App 爬取技术,例如 App 数据包的抓取和 App 的自动化等技术,会主要介绍 Charles、mitmproxy、Appium 和 Airstest 这些工具的使用方法,学会这些足以应对大多数 App 的爬取。

当然只学会本章所讲的技术还不够,可能还是会抓包失败,或者在使用自动化工具爬取数据时碰壁。换句话说,仅仅停留在表层是不够的,在某些情况下需要对 App 进行逆向来找到其核心逻辑以及数据请求究竟是怎么实现的,这就涉及 App 的逆向、脱壳、模拟执行 so 文件等技术了,这些会在第 13 章单独讲解。

第13章 Android 逆向

在第 12 章中,我们了解了爬取 App 数据的一些基本操作,主要包括抓包原理和使用 Appium、Airtest 这些自动化工具完成爬取的流程。但很多时候,这些内容并不足以保证我们有效地完成爬取工作,例如在抓包过程中遇到问题,大家常听说的 SSLPinning 就可能导致无法正常抓包。另外,使用 Appium、Airtest 这些工具爬取数据的效率并不高,因为这些都属于纯 UI 层的操作,如果爬取过程中一些点击、滑动等交互比较复杂,那么实现起来就相对困难。除了这些,利用 Appium、Airtest 爬取图片或视频数据时也确实比较麻烦。

实际上,App 中的很多数据是通过接口获取并渲染在 App 中的。如果我们能直接从根源出发,找出 App 构造接口请求的真正逻辑,包括一些加密参数怎么生成、一些风控怎么避免,就可以利用 Python 脚本构造请求或者通过 Hook 的方式爬取数据了,这样不仅能省去一些基于 Appium、Airtest 的 UI 交互操作,还能大大提高爬取效率。另外,SSLPinning 技术其实是在 App 内部做了一些限制和校验,我们找到根源就可解决其带来的抓包问题。

总之,要想真正地挖掘底层深处的逻辑,就必须学习一些 Android 逆向相关的知识。例如借助 jadx、JEB 等工具对 apk 文件进行反编译,还原 Java 代码并分析内部的逻辑; 借助 Hook 工具 (如 Xposed、Frida) 拦截和改写一些关键逻辑,从而截获关键的数据或加密信息; 借助反汇编工具 (如 IDAPro) 逆向分析、调试、模拟 AndroidNative 层的实现逻辑,探寻 Native 层的实现逻辑。

本章就主要介绍 Android 逆向的相关知识,包括以下内容。

jadx、JEB 等工具的用法,实现 apk 文件的反编译和代码分析。

Xposed 框架、Frida 等工具的用法,Hook 或模拟 AndroidJava 层和 Native 层的代码。

如何利用 Xposed 框架、Frida 工具解决抓包过程中常见的 SSLPinning 问题。

反编译过程中常用的脱壳技术和相关原理。

AndroidNative 层 so 文件的逆向分析方法。

AndroidNative 层 so 文件的模拟调用和常见的数据爬取方法。

第14章 页面智能解析

在前面所讲的内容中,解析页面利用的都是规则匹配,这种方式可能需要借助浏览器找到最佳的 Selector、XPath,甚至需要正则表达式辅助提取细节,同时利用 Beautiful Soup、pyquery、Re 等库提取和解析内容。在一般情况下,这么做是没有问题的。

但如果切换了场景,例如分析舆情,就需要爬取成千上万个新闻网站,把这些网站上的新闻文章都爬取下来,包括标题、正文、发布时间等,我们会发现不同新闻网站的页面差别非常大,标题、正文、发布时间对应的正则表达式、Selector、XPath 等各不相同。这时如果手动针对每一个网站写正则表达式、Selector、XPath 等,那工作量实在太大了。如果配置不当,还会产生解析错误的问题。例如正则表达式在某些情况下无法匹配,Selector、XPath 编写错误或者提取不全。另外,如果页面突然改版了,之前配置的规则可能就没法用了,这也是一个隐患。

目前有一种更智能的方法可以帮我们解析出网站内的新闻列表链接、标题、正文、发布时间等,用起来很方便。但内容的爬取过程毕竟是用算法实现的,所以正确率达不到 100% ,而且即便是人工写出来的正则表达式、Selector、XPath,也难免会有不兼容和错误的情况,因此在能容忍一定错误的情况下,用比较智能的解析方案爬取页面内容是明智的。

本章中我们就来学习一下智能解析页面的原理和实现算法。

第15章 Scrapy 框架的使用

前面的章节给大家展示了很多案例,其中大多实现了爬虫的整个流程,将不同的功能定义成不同的方法,甚至抽象出模块的概念。比如在 9.5 节,我们已经有了爬虫框架的雏形,实现了调度器、队列、请求对象、异常重试机制等,如果我们将各个组件独立出来,把它们定义成不同的模块,其实也就慢慢形成了一个框架。有了框架之后,我们就不必关心爬虫的流程了,异常处理、任务调度等都会集成在框架中。我们只需要关心爬虫的核心逻辑即可,如页面信息的提取、下一步请求的生成等。这样,不仅开发效率会提高很多,而且爬虫的健壮性也更强。

9.5 节的实现算是一个爬虫框架的雏形,但其距离一个标准的爬虫框架还很远。我们要以它为基础,继续完善,编写一个爬虫框架吗? 可以是可以,但是没必要。因为 Python 爬虫生态圈中已经有一个成熟、稳定且强大的爬虫框架了,它就是 Scrapy。

第16章 分布式爬虫

在上一章中,我们了解了 Scrapy 爬虫框架的用法。这些框架都是在同一台主机上运行的,爬取效率比较有限。如果能够用多台主机协同爬取,那么爬取效率必然会成倍增长,这就是分布式爬虫的优势。

本章我们就来了解一下分布式爬虫的基本原理,以及 Scrapy 实现分布式爬虫的流程。

第17章 爬虫的管理和部署

在前一章中,我们成功实现了 Scrapy 分布式爬虫,但是在这个过程中我们发现有很多不方便的地方。比如在将 Scrapy 项目放到各台主机上运行时,我们采用的是文件上传或者 Git 同步的方式,这样需要各台主机都进行操作,如果有 100 台、1000 台主机,那么工作量可想而知。另外,如果代码需要改动的话,那么还需要额外把改动同时更新到所有主机上,操作非常烦琐,并且也容易出错。

本章中,我们就来了解一下分布式爬虫在部署方面可以采取的一些措施,以方便地实现爬虫任务的批量部署和管理。

本章主要介绍两种 Scrapy 分布式爬虫管理方案: 基于 Scrapyd 的管理方案和基于 Kubernetes 的管理方案。

相关资源

本书中的所有代码都放在了 GitHub 上,书中每个实例对应的章节末也有说明。

另外,本书还设有专门的读者交流群,可以搜索"进击的 Coder"微信公众号获取,欢迎各位读者加入!

附录 爬虫与法律

近年来,很多和网络爬虫相关的法律案件和新闻报道层出不穷,如《裁判文书网数据竞被标价售卖: 爬虫程序抓取或构成侵权》。看到相关报道后,一些数据、爬虫从业者甚至发出了这样的感叹一"爬虫写得好,监狱进得早; 爬虫玩得溜,牢饭吃个够。",还有人会将所有罪过都归咎于爬虫本身,甚至有人认为爬虫本身就涉及违法犯罪,于是不再敢学习和编写任何爬虫程序。

实际上,上面的这些认识是不对的。爬虫作为一种计算机技术,其技术本身是具有中立性的,法律上也从来没有禁止爬虫技术,当今互联网的很多技术是依托于爬虫技术而发展的,如搜索引擎、数据分析、人工智能和聚合导航等。爬虫是采集数据时的一个重要手段,不同数据本身的敏感性、安全性和版权等又是不同的,因此如果我们不注意甄别哪些数据可以爬取、哪些不可以,或者使用了一些非法手段进行数据爬取,就有可能触及法律红线,构成违法犯罪。基于上述内容,下面专门讲解一下爬虫与法律的相关事宜,希望各位读者可以合理、合法地使用爬虫技术。

一、相关法律法规

目前,和爬虫、数据相关的法律法规《中华人民共和国数据安全法》已经出台并于 2021 年 9 月 1 日起正式施行。另外,除了《中华人民共和国数据安全法》之外,《中华人民共和国网络安全法》《中华人民共和国刑法》《中华人民共和国反不正当竞争法》《中华人民共和国著作权法》也是重要的参考。经过业内法律相关人士统计,可能会和爬虫技术挂钩的罪名有: 侵犯公民个人信息罪、非法获取计算机信息系统罪、侵犯著作权罪、掩饰隐瞒犯罪所得罪、破坏计算机信息系统罪、传播淫秽物品牟利罪、提供侵入计算机信息系统程序罪等。

二、判罚标准

根据业界相关律师的说法,目前相关案件的判罚是综合三个要素来看的,分别是动机、行为、结果。一般来说,如果动机单纯且行为正常,同时没有造成不好的影响,那这种数据爬取是不会有问题的。可如果其中某个要素不符合要求,比如结果非常恶劣,那么即使动机和行为没什么问题,也可能会判定为违法犯罪。下面分别介绍一下这三个要素。

三、注意事项

下面我们再来重点说一下爬虫相关的一些雷区,在做爬虫开发时大家一定要注意避开相关的危险行为。

以上就介绍了一些爬虫和法律相关的知识。学会爬虫之后,知法、懂法也是非常重要的!