设计型网站案例,十大正规交易平台,wordpress插件多说,北京城建集团官网有时我们用requests 抓取页面得到的结果#xff0c;可能和在浏览器中看到的不一样:在浏览器中可以看到正常显示的页面数据#xff0c;而使用requests 得到的结果中并没有这些数据。这是因为 requests 获取的都是原始 HTML 文档#xff0c;而浏览器中的页面是JavaScript 处理…有时我们用requests 抓取页面得到的结果可能和在浏览器中看到的不一样:在浏览器中可以看到正常显示的页面数据而使用requests 得到的结果中并没有这些数据。这是因为 requests 获取的都是原始 HTML 文档而浏览器中的页面是JavaScript 处理数据后生成的结果这些数据有多种来源:可能是通过 Ajax 加载的可能是包含在 HTML文档中的,也可能是经过 JavaScript 和特定算法计算后生成的。
对于第一种来源数据加载是一种异步加载方式原始页面最初不会包含某些数据当原始页面加载完后会再向服务器请求某个接口获取数据然后数据才会经过处理从而呈现在网页上这其实是发送了一个 Ajax 请求。
按照 Web 的发展趋势来看这种形式的页面越来越多。甚至网页的原始 HTML 文档不会包含任何数据数据都是通过 Ajax 统一加载后呈现出来的这样使得 Web 开发可以做到前后端分离减小服务器直接渲染页面带来的压力。
所以如果遇到这样的页面直接利用requests等库来抓取原始 HTML文档是无法获取有效数据的这时需要分析网页后台向接口发送的 Aiax 请求。如果可以用 requests 模拟 Aiax 请求就可以成功抓取页面数据了。
所以本章我们的主要目的是了解什么是 Ajax以及如何分析和抓取 Ajax 请求。
什么是Ajax
Ajax全称为 Asynchronous JavaScript and XML即异步的 JavaScript 和 XML。它不是一门编程语言而是利用 JavaScript 在保证页面不被刷新、页面链接不改变的情况下与服务器交换数据并更新部分网页内容的技术。
对于传统的网页如果想更新其内容就必须刷新整个页面但有了Aiax可以在页面不被全部刷新的情况下更新。这个过程实际上是页面在后台与服务器进行了数据交互获取数据之后再利用JavaScript 改变网页这样网页内容就会更新了。
可以到 W3School上体验几个实例感受一下https://www.w3school.com.cn/js/js_ajax_http_send.asp
实例引入
浏览网页的时候我们会发现很多网页都有“下滑查看更多”的选项。拿微博来说可以登录https://m.weibo.cn/为例一直下滑可以发现下滑几条微博之后再向下就没有了转而会出现一个加载的动画不一会儿下方就继续出现了新的微博内容这个过程其实就是 Ajax 加载的过程如下图所示。 能够看出页面其实并没有整个刷新这意味着页面的链接没有变化但是网页中却多了新内容也就是后面刷出来的新微博。这就是通过 Ajax 获取新数据并呈现的过程。
基本原理
初步了解了 Ajax之后我们接下来详细了解它的基本原理。从发送 Aiax 请求到网页更新的这个过程可以简单分为以下3步–发送请求、解析内容、渲染网页。下面分别详细介绍一下这几个过程。
发送请求
我们知道JavaScript可以实现页面的各种交互功能Ajax也不例外它也是由JavaScript实现的,实现代码如下:
var xmlhttp;if(window.XMLHttpRequest){xmlhttpnew XMLHttpRequest();
}else{//code for IE6、 IE5xmlhttpnew ActiveXObject(Microsoft.XMLHTTP);
}
xmlhttp.onreadystatechange-function(){if(xmlhttp.readyState4xmlhttp.status200{document.getElementById(myDiv).innerHTMLxmlhttp.responseText;}
}
xmlhttp.open(POST,/ajax/,true);
xmlhttp.send();这是 JavaScript 对 Ajax 最底层的实现实际上就是先新建一个 XMLHttpRequest 对象 xmlhttp然后调用 onreadvstatechange属性设置监听最后调用 open和 send方法向某个链接(也就是服务器)发送请求。前面用 Python 实现请求发送之后可以得到响应结果但这里的请求发送由 JavaScript 完成。由于设置了监听所以当服务器返回响应时onreadystatechange 对应的方法便会被触发然后在这个方法里面解析响应内容即可。
解析内容
服务器返回响应之后onreadystatechange 属性对应的方法就被触发了此时利用 xmlhttp 的responseText属性便可得到响应内容。这类似于Python 中利用requests 向服务器发起请求然后得到响应的过程。返回内容可能是 HTML可能是 JSON接下来只需要在方法中用 JavaScript 进一步处理即可。如果是 JSON 的话可以进行解析和转化。
渲染网页
JavaScript有改变网页内容的能力因此解析完响应内容之后就可以调用JavaScript 来基于解析完的内容对网页进行下一步处理了。例如通过 document.getElementBvId().innerHTML操作可以更改某个元素内的源代码这样网页显示的内容就改变了。这种操作也被称作 DOM 操作即对网页文档进行操作如更改、删除等。
上面“发送请求”部分,代码里的 document.getElementById(“myDiv”).innerHTMLxmlhttp.responseText便是将 ID 为 myDiv 的节点内部的 HTML 代码更改为了服务器返回的内容这样 myDiv 元素内部便会呈现服务器返回的新数据对应的网页内容看上去就更新了。
我们观察到网页更新的3个步骤其实都是由 JavaScript 完成的它完成了整个请求、解析和渲染的过程。
再回想微博的下拉刷新其实就是 JavaScript 向服务器发送了一个 Ajax 请求然后获取新的微博数据对其做解析并渲染在网页中。
因此我们知道真实的网页数据其实是一次次向服务器发送 Ajax 请求得到的要想抓取这些数据需要知道 Ajax 请求到底是怎么发送的、发往哪里、发了哪些参数。我们知道这些以后不就可以用 Python 模拟发送操作并获取返回数据了吗?
Ajax分析方法
这里还以之前的微博为例我们知道下拉刷新的网页内容由 Aiax 加载而得而且页面的链接没有发生变化那么应该到哪里去查看这些Ajax请求呢?
分析案例 此处还需要借助浏览器的开发者工具下面以Chrome浏览器为例来介绍。
首先用 Chrome 浏览器打开微博链接 https://m.weibo.cn可以登录自己的微博然后在页面中单击鼠标右键从弹出的快捷菜单中选择“检查”选项此时便会弹出开发者工具如下图所示。 前面也提到过这里展示的就是页面加载过程中浏览器与服务器之间发送请求和接收响应的所有记录。
事实上Ajax有其特殊的请求类型叫作xhr。在下图中我们可以发现一个名称以 getindex 开头的请求其 Type 就为 xhr意味着这就是一个 Ajax 请求。用鼠标单击这个请求可以查看其详细信息。
从上图的右侧可以观察这个 Ajax 请求的 Request Headers、URL和 Response Headers 等信息。其中 Request Headers 中有一个信息为 X-Requested-With:XMLHttpRequest这就标记了此请求是 Ajax 请求如下图所示。 随后单击一下 Preview就能看到响应的内容如下图所示。这些内容是 JSON 格式的这里Chrome为我们自动做了解析单击左侧箭头即可展开和收起相应内容。 JavaScript接收到这些数据之后再执行相应的渲染方法整个页面就渲染出来了。
另外也可以切换到 Response 选项卡从中观察真实的返回数据如下图所示: 接下来切回第一个请求观察一下它的 Response 是什么如下图所示。 这是最原始的链接返回的结果其代码只有不到 50 行结构也非常简单只是执行了一些 JavaScript 语句。
所以说微博页面呈现给我们的真实数据并不是最原始的页面返回的而是执行JavaScript 后再次向后台发送 Ajax请求浏览器拿到服务器返回的数据后进一步渲染得到的。
过滤请求
利用 Chrome 开发者工具的筛选功能能够筛选出所有 Ajax请求。在请求的上方有一层筛选栏直接单击 XHR之后下方显示的所有请求便都是 Ajax 请求了如下图所示。 接下来不断向上滑动微博页面可以看到页面底部有一条条新的微博被刷出开发者工具下方也出现了一个个新的 Ajax 请求这样我们就可以捕获所有的 Ajax 请求了。
随意点开其中一个条目都可以清楚地看到其Request URL、Request Headers、Response Headers、Response Body 等内容此时想要模拟 Ajax 请求的发送和数据的提取就非常简单了。
下图展示的内容便是我的某一页微博的列表信息 到现在为止我们已经可以得到 Aiax 请求的详细信息了接下来只需要用程序模拟这些 Ajax 请求就可以轻松提取我们所需的信息。
Ajax分析与爬虫实战
本节我们会结合一个实际的案例来看一下 Ajax 分析和爬取页面的具体实现。
准备工作
开始分析之前需要做好如下准备工作。
安装好 Python 3(最低为 3.6 版本 )并成功运行 Python 3 程序。 -了解 Python HTTP 请求库 requests 的基本用法。了解 Ajax 基础知识和分析 Ajax 的基本方法。
以上内容在前面的博客中均有讲解如尚未准备好建议先熟悉一下这些内容。
爬取目标
本节我们以一个示例网站来试验一下 Ajax的爬取其链接为:https://spa1.scrape.center/该示例网站的数据请求是通过 Ajax完成的,页面的内容是通过JavaScript 渲染出来的,页面如下图所示。
此时我们需要爬取的数据包括电影的名称、封面、类别、上映日期、评分剧情简介等信息。 本节我们需要完成的目标如下
分析页面数据的加载逻辑。用 requests 实现 Ajax 数据的爬取。将每部电影的数据分别保存到 MongoDB 数据库。 由于本节主要讲解 Ajax所以数据存储和加速部分就不再展开详细实现了主要是讲解 Ajax 分析和爬取的实现。 好现在就开始吧。
初步探索
我们先尝试用之前的 requests 直接提取页面看看会得到怎样的结果。用最简单的代码实现一下requests 获取网站首页源码的过程代码如下:
import requestsurl https://spa1.scrape.center/
html requests.get(url).text
print(html)运行结果如下:
!DOCTYPE htmlhtml langenheadmeta charsetutf-8meta http-equivX-UA-Compatible
contentIEedgemeta nameviewport contentwidthdevice-width,initial-scale1
link relicon href/favicon.icotitleScrape | Movie/titlelink href/css/chunk-
700f70e1.1126d090.css relprefetchlink href/css/chunk-d1db5eda.0ff76b36.cssrelprefetchlink href/js/chunk-700f70e1.0548e2b4.js relprefetchlinkhref/js/chunk-d1db5eda.b564504d.js relprefetchlink href/css/app.ea9d802a.cssrelpreload asstylelink href/js/app.17b3aaa5.js relpreload asscriptlinkhref/js/chunk-vendors.683ca77c.js relpreload asscriptlinkhref/css/app.ea9d802a.css relstylesheet/headbodynoscriptstrongWeresorry but portal doesnt work properly without JavaScript enabled. Please enableit to continue./strong/noscriptdiv idapp/divscript src/js/chunk-vendors.683ca77c.js/scriptscript src/js/app.17b3aaa5.js/script/body/html可以看到爬取结果就只有这么一点HTML内容而我们在浏览器中打开这个网站却能看到如下图所示的页面。 在 HTML中我们只能看到源码引用的一些JavaScript和CSS 文件并没有观察到任何电影数据信息。
遇到这样的情况说明我们看到的整个页面都是JavaScript渲染得到的浏览器执行了HTML中引用的 JavaScript 文件JavaScript 通过调用一些数据加载和页面渲染方法才最终呈现了上图展示的结果。这些电影数据一般是通过 Aiax加载的JavaScript在后台调用 Aiax数据接口得到数据之后再对数据进行解析并渲染呈现出来得到最终的页面。所以要想爬取这个页面直接爬取 Ajax 接口再获取数据就好了。
上面我们已经了解了 Ajax 分析的基本方法下面一起分析一下 Ajax 接口的逻辑并实现数据爬取吧。
爬取列表页
首先分析列表页的 Ajax 接口逻辑打开浏览器开发者工具切换到Network面板勾选 PreserveLog 并切换到 XHR 选项卡如下图所示。
接着重新刷新页面再单击第2页、第3页、第4页的按钮这时可以观察到不仅页面上的数据发生了变化开发者工具下方也监听到了几个 Ajax 请求如下图所示。
我们切换了4页,每次翻页也出现了对应的 Aiax 请求。可以点击査看其请求详情,观察请求 URL、参数和响应内容是怎样的如下图所示。 这里我点开了最后一个结果观察到其Ajax接口的请求 URL为 https://spa1.scrape.center/api/movie?limit10ofset40这里有两个参数:一个是limit这里是10一个是 offset这里是 40。
观察多个 Ajax 接口的参数,我们可以总结出这么一个规律:limit 一直为10,正好对应每页 10条数据;offset 在依次变大页数每加1offset 就加10因此其代表页面的数据偏移量。例如第2页的 offset 为 10 就代表跳过 10 条数据返回从 11 条数据开始的内容再加上 limit 的限制最终页面呈现的就是第 11条至第 20 条数据。
接着我们再观察一下响应内容切换到Preview 选项卡结果如下图所示。 可以看到结果就是一些 JSON 数据其中有一个 results 字段是一个列表列表中每一个元素都是一个字典。观察一下字典的内容里面正好可以看到对应电影数据的字段如 name、alias、cover、categories。对比一下浏览器页面中的真实数据会发现各项内容完全一致而且这些数据已经非常结构化了完全就是我们想要爬取的数据真的是得来全不费工夫。
这样的话我们只需要构造出所有页面的 Ajax接口就可以轻松获取所有列表页的数据了先定义一些准备工作导人一些所需的库并定义一些配置代码如下:
import requests
import logginglogging.basicConfig(levellogging.INFO, format%(asctime)s-%(levelname)s:%(message)s)
INDEX_URl https://spa1.scrape.center/api/movie/?limit{limit}offset-{offset}这里我们引人了 requests 和 logging 库并定义了 logging 的基本配置。接着定义了 INDEX URL,这里把 limit 和 offset 预留出来变成占位符可以动态传人参数构造一个完整的列表页 URL。
下面我们实现一下详情页的爬取。还是和原来一样我们先定义一个通用的爬取方法其代码如下:
def scrape_api(url):logging.info(scraping %s..., url)try:response requests.get(url)if response.status_code 200:return response.json()logging.error(get invalid status code %s while scraping %s, response.status_code, url)except requests.RequestException:logging.error(error occurred while scraping %s, url, exc_infoTrue)这里我们定义了一个 scrape api方法和之前不同的是这个方法专门用来处理 JSON接口。最后的 response 调用的是 json 方法它可以解析响应内容并将其转化成 JSON 字符串。
接着在这个基础之上定义一个爬取列表页的方法其代码如下:
def scrape_index(page):url INDEX_URL.format(limitLIMIT, offsetLIMIT * (page - 1))return scrape_api(url)这里我们定义了一个 scrape index方法它接收一个参数 page该参数代表列表页的页码。scrape_index方法中,先构造了一个url,通过字符串的 format方法,传入 limit 和 offset 的值。这里 limit 就直接使用了全局变量 LIMIT 的值;offset 则是动态计算的计算方法是页码数减一再乘以 limit例如第1页的 offset 就是0第2页的 offset 就是 10以此类推。构造好 url后直接调用 scrape_api 方法并返回结果即可。
这样我们就完成了列表页的爬取每次发送 Ajax请求都会得到 10部电影的数据信息。
由于这时爬取到的数据已经是 JSON 类型了所以无须像之前那样去解析 HTML 代码来提取数爬到的数据已经是我们想要的结构化数据因此解析这一步可以直接省略啦。
到此为止我们能成功爬取列表页并提取电影列表信息了。
爬取详情页
虽然我们已经可以拿到每一页的电影数据但是这些数据实际上还缺少一些我们想要的信息如剧情简介等信息所以需要进一步进入详情页来获取这些内容。
单击任意一部电影如《教父》,进入其详情页,可以发现此时的页面 URL已经变成了 https://spa1scrape.center/detail/40页面也成功展示了《教父》详情页的信息如下图所示。 另外我们也可以观察到开发者工具中又出现了一个 Ajax请求其 URL为 https://spa1.scrape. center/api/movie/40/通过 Preview 选项卡也能看到 Ajax 请求对应的响应信息如下图所示。 稍加观察就可以发现Ajax 请求的 URL后面有一个参数是可变的这个参数是电影的id这里是 40对应《教父》这部电影。
如果我们想要获取 id为 50的电影,只需要把 URL最后的参数改成 50 即可,即 https://spa1.scrape. center/api/movie/50/请求这个新的 URL便能获取 id 为 50 的电影对应的数据了。
同样响应结果也是结构化的JSON 数据其字段也非常规整我们直接爬取即可。
现在详情页的数据提取逻辑分析完了怎么和列表页关联起来呢?电影 id从哪里来呢?我们回过头看看列表页的接口返回数据如下图所示。 可以看到列表页原本的返回数据中就带有id这个字段所以只需要拿列表页结果中的 id 来构造详情页的 Aiax 请求的 URL 就好了。
接着我们就先定义一个详情页的爬取逻辑代码如下:
DETAIL_URL https://spa1.scrape.center/api/movie/{id}def scrape_detail(id):url DETAIL_URL.format(idid)return scrape_api(url)这里定义了一个 scrape_detail方法它接收一个参数 id。这里的实现也非常简单先根据定义好的 DETAIL_URL 加 id 构造一个真实的详情页 Ajax 请求的 URL再直接调用 scrape_api 方法传人这个 url即可。
最后我们定义一个总的调用方法对以上方法串联调用代码如下:
def main():for page in range(1, TOTAL_PAGE 1):index_data scrape_index(page)for item in index_data.get(results):id item.get(id)detail_data scrape_detail(id)logging.info(detail data %s, detail_data)if __name__ __main__:main()我们定义了一个 main 方法该方法首先遍历获取页码 page然后把 page 当作参数传递给scrape_index方法得到列表页的数据。接着遍历每个列表页的每个结果获取每部电影的 id。之后把 id 当作参数传递给 scrape_detail方法来爬取每部电影的详情数据并将此数据赋值为detail_data最后输出 detail_data 即可。
运行结果如下: 由于内容较多这里省略了部分内容。
可以看到整个爬取工作已经完成了这里会依次爬取每一个列表页的 Ajax 接口然后依次爬取每部电影的详情页 Ajax 接口并打印出每部电影的 Ajax 接口响应数据而且都是 JSON 格式。至此所有电影的详情数据我们都爬取到啦。
全部代码
import requests
import logginglogging.basicConfig(levellogging.INFO, format%(asctime)s-%(levelname)s:%(message)s)
INDEX_URL https://spa1.scrape.center/api/movie/?limit{limit}offset-{offset}def scrape_api(url):logging.info(scraping %s..., url)try:response requests.get(url)if response.status_code 200:return response.json()logging.error(get invalid status code %s while scraping %s, response.status_code, url)except requests.RequestException:logging.error(error occurred while scraping %s, url, exc_infoTrue)LIMIT 10def scrape_index(page):url INDEX_URL.format(limitLIMIT, offsetLIMIT * (page - 1))return scrape_api(url)DETAIL_URL https://spa1.scrape.center/api/movie/{id}def scrape_detail(id):url DETAIL_URL.format(idid)return scrape_api(url)TOTAL_PAGE 10def main():for page in range(1, TOTAL_PAGE 1):index_data scrape_index(page)for item in index_data.get(results):id item.get(id)detail_data scrape_detail(id)logging.info(detail data %s, detail_data)if __name__ __main__:main()