第3章爬虫与反爬虫 搭建完开发环境,学会基本的调试手段后,就可以正式开始学习AST反爬虫以及还原混淆JS代码了。不过在此之前,先给不熟悉网络爬虫的读者介绍一些关于爬虫的基本概念,以及如今常见的应对爬虫的反爬虫技术,从而对之后的AST反爬虫的功用有更深的了解。 3.1网络爬虫 网络爬虫又叫作网络蜘蛛、网络机器人等,可将其理解为一个在互联网上自动提取网页信息并进行解析抓取的程序。网络爬虫不仅能够复制网页信息和下载音视频,还可以做到行为链执行与网站的模拟登录。大数据时代,不论是人工智能还是数据分析,都需要有海量的数据在背后做支撑,如果单是依靠人力手动采集,不仅成本高昂且效率低下。在这一需求下,自动化且高效、可并发执行的网络爬虫担起了获取数据的重任。 3.1.1网络爬虫原理 理论上来说,任何编程语言都可以用来编写网络爬虫,只有难易之分。因为网络爬虫本质上只是对目标服务器发起HTTP请求,并对HTTP响应做出处理,提取关键信息进行清洗入库。这里的服务器可以理解为要爬取的网站站点,爬虫程序发起一次HTTP请求,网站服务器对请求做出一次响应,就构成了一次网络爬虫行为,但仅发起请求是不完整的,还需要将网站返回的信息进行数据解析和清洗,将最终需要的数据存储到数据库或本地文件里,才算是完成了一整套的爬虫流程。 如图31所示,完整的爬虫流程是编写的网络爬虫发起请求后,目标网站返回指定的请求响应,通过对请求响应返回的响应体进行解析,找到需要的信息进行数据存储。如果需要翻页或跳转,则从当前页面或响应体中提取出链接再次发起请求。 Python实现了许多第三方库来帮助开发者完成这个操作,在第1章中安装的requests库用于发起HTTP请求,省去了实现请求程序的时间,bs4解析库让开发者只需要专注于网页信息的定位和操作网站返回的主体信息。开发重心也就从协议处理转化到了具体网页的数据提取。在了解了爬虫的原理与基本流程之后,接着来探讨网络爬虫中请求和响应的具体内容。 图31网络爬虫流程 1. 发起请求 网络爬虫本质上是HTTP请求,因而每发起一次爬虫请求,实际上就是向目标服务器发送了一次请求报文。接下来需要具体了解一下HTTP请求报文。如表31所示,HTTP请求报文主要由四部分组成,分别是请求行、请求头部、空行和请求体。 表31HTTP请求报文 请求报文类别 请求报文组成内容 请求行请求方法空格统一资源定位符空格HTTP协议版本\n\r 请求头部 请求头部键∶请求头部值\n\r …∶…\n\r 请求头部键∶请求头部值\n\r 空行\n\r 请求体请求包体 1) 请求行 在请求行中,主要起作用的部分是请求方法、统一资源定位符和HTTP协议版本。不同的请求方法用来处理不同的任务,以下是常用的8种HTTP请求方法。 (1) GET: 向目标服务器请求资源,返回实体主体。 (2) POST: 向目标服务器发送资源,例如提交表单。 (3) HEAD: 与GET类似,不过它用于获取报头,不会返回具体内容。 (4) PUT: 向目标服务器发送数据以覆盖指定内容。 (5) DELETE: 请求服务器删除URL指定内容。 (6) OPTIONS: 返回目标服务器针对特定资源的HTTP请求方法。 (7) TRACE: 用于测试诊断,回显服务器收到的请求。 (8) CONNECT: HTTP 1.1协议预留给能够将链接修改为管道方式的代理服务器。 在这些请求方法中,在实际开发中使用最多的是GET和POST,前者常用来获取网页资源,后者常用来模拟登录。 请求行中的统一资源定位符实际上就是URL(Uniform Resource Locator),如果要浏览一个网页页面,首先需要的就是在浏览器中输入它的URL地址。日常生活中经常用搜索引擎来辅助完成查找,搜索引擎也是依赖网络爬虫来搜集数据的,只不过它在爬虫基础上拓展了更多技术,需要对数据进行组织处理后根据用户的检索进行反馈。编写网络爬虫一定程度上是在模拟正常用户浏览网页的行为,不过是用代码的方式进行呈现,所以编写的网络爬虫在发起请求寻找资源时,也需要有URL作为导引。 至于HTTP协议版本,需要了解的是HTTP 1.0 只定义了上述所列举的前三种方法,即GET、POST和HEAD,在HTTP 1.1 才新增了后5种方法。 2) 请求头部 请求头部主要由一系列的键值对组成,用来说明服务器需要的附属信息。通常反爬虫的检测会在请求头部里进行,检测是否包含关键键值对,如果不存在或数据不匹配就会被判定为机器人。下面介绍8个实用的请求头键值对。 (1) AcceptCharset: 表示客户端可以接受的字符集。 (2) Cookie: 网站用来识别身份所用的加密键值对,需要登录才能访问的网站通常需要携带。 (3) Connection: 表示是否需要持久连接,close代表本次响应后连接可以被关闭; keepalive表示长久连接,等待客户端的下次请求。在HTTP 1.1下默认会保持持久连接。 (4) ContentLength: 请求体的长度。 (5) ConetntType: 请求体的数据类型。 (6) Host: 请求的主机名。 (7) Referer: 指明用户从该URL出发到达此页面,常用于防盗链技术。 (8) UserAgent: 服务器用来识别浏览器类型,可更改这个参数达到切换计算机端与手机端的效果。 3) 空行 空行必须存在于HTTP请求头部之后,也就是输入“\n\r”,它的作用在于通知目标服务器此后不会再出现请求头部,将会进入请求体。这是一种简单而实用的数据分割方式。 4) 请求体 在GET方法中一般不存在请求体,请求体适用于POST方法,内容是用户在填写表单时提交的数据,通常会在请求头部的ContentLength与ContentType中进行附属说明。它的格式是用“&”连接的键值对,如name=test&password=123。 由此而知,如果要编写一个网络爬虫,首先要获取请求行需要的URL,之后根据具体需求判断请求方式,如果要让目标服务器更多地了解自己编写的爬虫程序,就需要在请求头部里添加键值对,最后如果想提交数据,就需要在请求体中编写键值对连接串。 打开预先下载好的Fiddler抓包工具进行抓包,以下是访问百度首页的HTTP发包信息: GET https://www.baidu.com/ HTTP/1.1 Host: www.baidu.com Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36 Sec-Fetch-User: ?1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie:PSTM=1593078681; BAIDUID=1D487D44FE512A2B72D79B17C511AEBA:FG=1; BD_UPN=12314753; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598 通过观察可知,它是完全符合HTTP请求报文结构的,浏览器在访问页面时,自动添加了许多请求头部,从UserAgent可以看出这里使用Chrome Google浏览器发起HTTP请求,而且由于发起的是GET请求,因此不存在请求体。 2. 解析响应 服务器根据请求返回的HTTP响应报文内包含了客户端需要的数据,一般可以通过观察报文中的一些关键信息来确认响应的实际情况。HTTP响应报文的主要结构如表32所示。 表32HTTP响应报文 响应报文类别响应报文内容 状态行空格状态码空格状态码描述\n\r 响应头 响应头部键∶响应头部值\n\r …∶…\n\r 响应头部键∶响应头部值\n\r 空行\n\r 响应体响应包体内容 可以发现HTTP响应报文与HTTP请求报文相似,依然是四个部分: 状态行、响应头、空行和响应体。在状态行中去除了请求方法、统一资源定位符与HTTP协议版本,新增了状态码和状态码描述,可以这样理解,HTTP响应主要用来回应浏览器的请求,浏览器根据响应报文判断返回响应的内容完整性与服务器状态,从而确定请求是否需要重发、内容是否发生更新等。所以首先需要增加状态码来明确响应状态,让浏览器客户端能够实时地了解请求反馈。 1) 响应行 响应行中的状态码 state code主要由三位数字构成,第一位数字用来辨别响应类型,后两位则是单纯用来计数区分。状态码的响应类别主要分为以下5类。 (1) 1xx: 服务器成功接收客户端请求,客户端可继续发送请求。 (2) 2xx: 服务器成功接收请求,并着手进行处理。 (3) 3xx: 服务器要求客户端重定向。 (4) 4xx: 服务器表明客户端请求非法。 (5) 5xx: 服务器发生错误。 由此可见,以1开头的状态码一般表示请求已经被接收,但并未被处理; 以2开头的状态码通常代表成功接收并被处理; 以3开始的状态码代表资源被转移,需要重定向; 以4开头的状态码很可能是程序的请求编写错误; 以5开头的状态码是服务器内部发生错误,与浏览器客户端的请求内容无关。下面是一些必须熟记的10种状态码及其描述。 (1) 200 OK: 表示请求成功,请求报文中所希望的资源会在响应中被返回,通常在浏览器中正常看到网页内容时就会返回200状态码。 (2) 206 Partial Content: 服务器返回部分内容,虽然请求成功,但请求返回的内容是不完整的。此状态码常见于断点续传或者大文件的分段下载传输。 (3) 301 Moved Permanently: 请求的资源被永久移动到了其他地方,并且之后也不会再将资源移动回来。通常新的URL地址会在HTTP响应头的Location键中返回。 (4) 302 Moved Temporarily: 请求的资源被临时移动到了其他地方,因此用户今后也应该继续向该URL发起请求,而不是去请求响应体中返回的新的URL地址。 (5) 400 Bad Request: 通常发生这个错误是因为在编写HTTP请求时,写错了请求参数或者语义有误,导致服务器无法理解。 (6) 403 Forbidden: 服务器理解了请求,但是拒绝返回内容。此状态码通常是因为网络爬虫恶意访问网站而导致IP地址被标记,再次请求就会被服务器拒绝。 (7) 404 Not Found: 请求内容未在服务器找到,通常是由于网站开发者将内容删除。 (8) 405 Method Not Allowed: 请求方式错误,多数情况是因为请求方法不当,例如本应当是模拟登录的POST请求,误写为了GET请求。 (9) 500 Internal Server Error: 服务器遇到突发状况,导致无法完成请求处理,通常是因为服务器源代码错误。 (10) 502 Bad Gateway: 作为网关或者代理工作的服务器在执行请求时,从上游服务器收到无效响应。 2) 响应头 在网络爬虫中,开发者并不需要对响应头有过多关注,只需要偶尔观察其中的个别键值对用于查看辅证响应状态码的描述,需要了解的是如下三个响应头。 (1) Location: 是在301或者302状态码下返回的重定向后的URL地址。 (2) Connection: colose代表本次响应后连接将被关闭; keepalive表示长久连接,服务器会等待客户端的下次请求。 (3) Server: 服务器用来处理请求的软件信息及其版本,与HTTP请求中的Useragent类似。不过它代表的是服务器端的信息。 3) 空行 响应头之后必须添加空行,输入“\n\r”,表示接下来进入响应体内容。 4) 响应体 响应体中是服务器返回给客户端的内容。爬虫开发者一般会在这里进行链接提取或内容爬取,不过在面对不同的响应体内容时,需要用不同的方式去处理。如果返回HTML源代码,可以使用Python中的bs4包进行DOM解析或编写正则表达式进行匹配; 如果请求的是音视频,会返回二进制文件,这时就要写一个二进制文件存储函数进行数据下载; 如果请求的是网站接口,通常返回JSON格式数据,需要使用Python中的内置包JSON进行格式解析。 因此,在面对HTTP响应报文时,主要通过观察状态码来了解响应的具体情况与服务器状态,如果请求成功,则继续处理响应体中的内容,再通过观察响应体的格式来编写具体的爬虫解析程序。 这里将Fiddler中抓到的百度首页响应包进行展示: HTTP/1.1 200 OK Bdpagetype: 2 Bdqid: 0xf12fb0dc002ff2a1 Cache-Control: private Connection: keep-alive Content-Type: text/html;charset=utf-8 Date: Sun, 05 Jul 2020 03:08:38 GMT Expires: Sun, 05 Jul 2020 03:08:38 GMT Server: BWS/1.1 Set-Cookie: BDSVRTM=262; path=/ Strict-Transport-Security: max-age=172800 Traceid: 1593918518283822567417379303945988469409 X-Ua-Compatible: IE=Edge,chrome=1 Content-Length: 329114