Ajax 分析与爬取实战

准备工作

  • 安装好 Python3
  • 了解 Python HTTP 请求库 requests 的基本用法
  • 了解 Ajax 基础知识和分析 Ajax 的基本方法

爬取目标

  以一个示例网站来实验一下 Ajax 的爬取,链接为:https://spa1.scrape.center/,该示例网站的数据请求是通过 Ajax 完成的,页面的内容是通过 JavaScript 渲染出来的。

  这个网格同样能实现翻页,可以单击页面最下方的页码来切换到下一页。

  单击每部电影进入对应的详情页,这些页面的结构也是一样的。

  要爬取的数据包括电影的名称、封面、类别、上映日期、评分、剧情简介等信息。

  要完成的目标:

  • 分析页面的加载逻辑
  • 用 requests 实现 Ajax 数据的爬取
  • 将每部电影的数据分别保存到 MongoDB 数据库

初步探索

  先尝试用之前的 requests 直接提取页面。

import requests

url = 'https://spa1.scrape.center/'
html = requests.get(url).text
print(html)

运行结果:

<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>Scrape | Movie</title><link href=/css/chunk-700f70e1.1126d090.css rel=prefetch><link href=/css/chunk-d1db5eda.0ff76b36.css rel=prefetch><link href=/js/chunk-700f70e1.0548e2b4.js rel=prefetch><link href=/js/chunk-d1db5eda.b564504d.js rel=prefetch><link href=/css/app.ea9d802a.css rel=preload as=style><link href=/js/app.17b3aaa5.js rel=preload as=script><link href=/js/chunk-vendors.683ca77c.js rel=preload as=script><link href=/css/app.ea9d802a.css rel=stylesheet></head><body><noscript><strong>We're sorry but portal doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.683ca77c.js></script><script src=/js/app.17b3aaa5.js></script></body></html>

  爬取的结果只有只有一点HTML内容,在浏览器中打开却可以看到很多信息。

  在 HTML 中,只能看到源码引用的一些 JavaScript 和 CSS 文件,并没有观察到任何电影信息。

  遇到这种情况,说明看到的整个页面都是 JavaScript 渲染得到的,浏览器执行了 HTML 中引用的 JavaScript 文件,JavaScript 通过调用一些数据加载和页面渲染的方法,才最终呈现了途中所示的效果。

  这些电影数据一般是通过Ajax加载的,JavaScript 在后台调用 Ajax 数据接口,得到数据之后,在对数据进行解析并渲染呈现出来的,得到最终的页面。所以要想爬取这个页面,直接爬取 Ajax 接口,再获取数据就好了。

  了解了 Ajax 分析的基本方法,下面分析 Ajax 接口的逻辑并实现数据爬取。

爬取列表页

  首先分析列表页的 Ajax 接口逻辑,打开浏览器开发者工具,切换到 Network 面板,勾选 Preserve Log 并切换到 XHR 选项卡。

  接着重新刷新页面,再单击第 2 页、第 3 页、第 4 页的按钮,这是可以观察到不仅页面上的数据发生了变化,开发者工具下方也监听到了几个 Ajax 请求。

  切换了 4 页,每次翻页也出现了对应的 Ajax 请求。 可以点击查看其请求详情,观察请求 URL 、参数和响应内容是怎样的。

  点开最后一个结果,观察到其 Ajax 接口的请求 URL 为 https://spa1.scrape.center/api/movie/?limit=10&offset=30,这里有两个参数:一个是limit,这里是 10;一个是 offset,这里是 30。

  观察多个 Ajax 接口的参数,可以总结出这个一个规律:limit 一直为 10,正好对应每页的 10 条数据;offset 在依次变大,页数每增加 ,offset 就加 10,因此其代表页面上的数据偏移量。例如第 2 页的 offset 为 10 就代表跳过 10 条数据,返回从 11 条数据开始的内容,再加上 limit 的限制,最终页面呈现的就是第 11 条 至第 12 条数据。

  观察一下响应内容:

  可以看到结果就是一些 JSON 数据,其中有一个 results 字段,是一个列表,列表中每一个元素都是一个字典,观察一下字典的内容,里面正好可以看到对应电影数据的字段,如 name、alias、cover、categories。对比一下浏览器中的真实数据,会发现各项的内容完全一致,而且这些数据已经非常结构化了,完全就是想要爬取的数据。

import requests
import logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)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_info=True)

  这里定义一个 scrape_api 方法,和之前不同的是,这个方法专门用来处理 JSON 接口。最后的 response 调用的是 json 方法,它可以解析响应内容并将其转化成 JSON 字符串。

  接着在此基础上,定义一个爬取列表页的方法:

def scrape_index(page):
url = INDEX_URL.format(limit=LIMIT, offset=LIMIT * (page-1))
return scrape_api(url)

  定义一个 scrape_index 方法,接收一个参数 page,该参数代表列表页的页码。

  scrape_index 方法中,先构造了一个 url,通过字符串的 format 方法,传入 limit 和 offset 方法的值。这里 limit 直接使用了全局变量的 LIMIt值;offset 则是动态计算的,计算方法是页码数减一再乘 limit,例如第一页的 offset 是 0,第二页的 offset 就是 10,以此类推,构造好 url 后,直接调用 scrape_api 方法并返回结果即可。

  完成了列表页的爬取,每次发送 Ajax 请求都会得到 10 部电影的数据信息。

  由于这是爬取的数据已经是 JSON 类型了, 所以无需像之前那样去解析 HTML 代码来提取数据,爬到的数据已经是想要的结构化数据。

  这样秩序构造出所有页面的 Ajax 接口,就可以轻松获取所有列表页的数据了。

爬取详情页

  虽然已经拿到每一页的电影数据,但是实际上缺少一些信息,如剧情简介等信息,所以需要进一步进入详情页来获取这些信息。

  单击任意一部电影,如《霸王别姬》,进入其详情页,可以发现此时的页面 URL 已经变成了 https://spa1.scrape.center/detail/1,页面也成功展示了《教父》详情页的信息。

  另外可以观察到开发者工具中多了一个 Ajax 请求,其 URL 为
https://spa1.scrape.center/api/movie/1/,通过 Preview 选项卡也能看到 Ajax 请求对应的响应信息。

  稍加观察就可以发现,Ajax 请求的 URL 后面有一个参数是可变的,这个参数是电影的 id,这里是 30,对应的是《完美世界》这部电影。

  如果想要获取 id 为 50 的电影,只需要吧 URL 最后的参数改成 50 即可,即 https://spa1.scrape.center/detail/50/,请求这个新的 URL 便能获取 id 为 50 电影对应的数据了。

  同样,响应结果的也是结构化的 JSON 数据,其字段也非常规整,直接爬取即可。

  详情页的数据提取逻辑分析完毕,继续考虑怎么和列表页关联起来,电影 id 从哪里来这些问题。回过头来看一下列表页的接口返回数据。

  可以看到列表页原本的返回数据中就带有 id 这个字段,所以只需要拿到列表页结果中的  id 来构造详情页的 Ajax 请求的 URL 就好。

  先定义一个详情页的爬取逻辑,代码如下:

DETAIL_URL = 'https://spa1.scrape.center/api/movie/{id}'

def scrape_detail(id):
url = DETAIL_URL.format(id=id)
return scrape_api(url)

  这里定义了一个 scrape_detail 方法,接受一个参数 id。这里的实现根据定义好的 DETAIL_URL 加 id 构造一个真实的详情页 Ajax 请求的 URL,再直接调用 scrape_api 方法传入这个 url 即可。

  最后,定义一个总的调用方法,对以上方法串联调用,代码如下:

TOTAL_PAGE = 10
def main():
for page in range(1, TOTAL_PAGE + 1):
index_data = scrape_index(page)
for item in index_data.get('result'):
id = item.get('id')
detail = 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 格式。至此所有电影的详情数据都爬取到了。

保存数据

  成功提取详情页信息之后,下一步就是保存数据。可以保存到MongoDB中。

  保存之前,确保有一个可以正常连接和使用的 MongoDB 数据库,以本地的 localhost 的 MongoDB 数据库为例来操作。

  将数据导入 MongoDB 需要用到 PuMongo 这个库。引入并配置:

MONGO_CONNECTION_STRING = 'mongodb://localhost:27017'
MONGO_DB_NAME = 'movies'
MONGO_COLLECTION_NAME = 'movies' client = pymongo.MongoClient(MONGO_CONNECTION_STRING)
db = client['movies']
collection = db['movies']
  • MONGO_CONNECTION_STRING:MongoDB的连接字符串,里面定义的是MongoDB的基本连接信息,这里是 host、port,还可以定义用户名、密码等内容。
  • MONGO_DB_NAME:MongoDB 数据库的名称。
  • MONGO_COLLECTION_NAME:MongoDB 的集合名称。

然后用MongoClient声明了一个连接对象client,并依次声明了存储数据的数据库和集合。

  定义了一个 save_data 方法,接收一个参数 data,也就是上面提取电影详情信息。这个方法里调用了 update_one 方法,其第一个参数为查询条件,根据name 进行查询:第二个参数是data对象本身,就是所有的数据,这里我们用 $set 操作符表示更新操作;第三个参数很关键,这里实际上是upsert参数,如果把它设置为 True,就可以实现存在即更新,不存在插人的功能,更新时会参照第一个参数设置的name 字段,所以这样可以防止数据库中出现同名的电影数据。

注意 实际上电影可能有同名现象,但此处场景下的爬取数据没有同名情况,当然这里更重要的是实现 MongoDB 的去重操作。

  改写 main 方法:

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)
save_data(detail_data)
logging.info('data saved successfully')

  增加了对 save_data 方法的调用,并添加一些日志信息。

 运行结果:

  连接数据库查看爬取结果:

Ajax分析与爬取实战的更多相关文章

  1. PYTHON 爬虫笔记九:利用Ajax+正则表达式+BeautifulSoup爬取今日头条街拍图集(实战项目二)

    利用Ajax+正则表达式+BeautifulSoup爬取今日头条街拍图集 目标站点分析 今日头条这类的网站制作,从数据形式,CSS样式都是通过数据接口的样式来决定的,所以它的抓取方法和其他网页的抓取方 ...

  2. Ajax数据的爬取(淘女郎为例)

    mmtao Ajax数据的爬取(淘女郎为例) 如有疑问,转到 Wiki 淘女郎模特抓取教程 网址:https://0x9.me/xrh6z 判断一个页面是不是 Ajax 加载的方法: 查看网页源代码, ...

  3. 爬虫七之分析Ajax请求并爬取今日头条

    爬取今日头条图片 这里只讨论出现的一些问题,代码在最下面github链接里. 首先,今日头条取消了"图集"这一选项,因此对于爬虫来说效率降低了很多: 在所有代码都完成后,也许是爬取 ...

  4. Java爬虫_资源网站爬取实战

    对 http://bestcbooks.com/  这个网站的书籍进行爬取 (爬取资源分享在结尾) 下面是通过一个URL获得其对应网页源码的方法 传入一个 url  返回其源码 (获得源码后,对源码进 ...

  5. 初识scrapy,美空网图片爬取实战

          这俩天研究了下scrapy爬虫框架,遂准备写个爬虫练练手.平时做的较多的事情是浏览图片,对,没错,就是那种艺术照,我骄傲的认为,多看美照一定能提高审美,并且成为一个优雅的程序员.O(∩_∩ ...

  6. python爬虫调用搜索引擎及图片爬取实战

    实战三-向搜索引擎提交搜索请求 关键点:利用搜索引擎提供的接口 百度的接口:wd="要搜索的内容" 360的接口:q="要搜索的内容" 所以我们只要把我们提交给 ...

  7. Ajax介绍及爬取哔哩哔哩番剧索引追番人数排行

    Ajax,是利用JavaScript在保证页面不被刷新,页面链接不改变的情况下与服务器交换数据并更新部分网页的技术.简单的说,Ajax使得网页无需刷新即可更新其内容.举个例子,我们用浏览器打开新浪微博 ...

  8. Python知乎热门话题数据的爬取实战

    import requestsfrom pyquery import PyQuery as pq url = 'https://www.zhihu.com/explore'headers = { 'u ...

  9. python爬虫实战---爬取大众点评评论

    python爬虫实战—爬取大众点评评论(加密字体) 1.首先打开一个店铺找到评论 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经 ...

  10. python爬取微信小程序(实战篇)

    python爬取微信小程序(实战篇) 本文链接:https://blog.csdn.net/HeyShHeyou/article/details/90452656 展开 一.背景介绍 近期有需求需要抓 ...

随机推荐

  1. [DApp] Moralis 生产阶段的服务安全设置 -锁定数据库

    Moralis 的基础设施数据库是使用的 MongoDB,其非常适合Dev阶段的快速开发. 如果进入生产环境,需要锁定数据库,防止任何用户可利用SDK向Mongo插入多余数据. 另外,Moralis ...

  2. Roslyn 通过 EmbedAllSources 将源代码嵌入到 PDB 符号文件中方便开发者调试

    本文来告诉大家如何在项目文件里面添加上 EmbedAllSources 属性,将自己的代码嵌入到 PDB 符号文件里面,让开发者们在调试的时候,可以看到库的源代码 是否记得 PDB 符号文件的作用?符 ...

  3. K8s集群中部署SpringCloud在线购物平台(三)

    五.SpringCloud概述 springcloud架构图 5.1 SpringCloud是什么? 官网: https://spring.io/projects/spring-cloud Sprin ...

  4. 构建RAG应用-day05: 如何评估 LLM 应用 评估并优化生成部分 评估并优化检索部分

    评估 LLM 应用 1.一般评估思路 首先,你会在一到三个样本的小样本中调整 Prompt ,尝试使其在这些样本上起效. 随后,当你对系统进行进一步测试时,可能会遇到一些棘手的例子,这些例子无法通过 ...

  5. SpringBoot获取Bean的工具类

    1.beanName 默认是类名首字母小写 下面的类:beanName = bean1 @Component public class Bean1 { public String getBean1() ...

  6. DB2查找最耗时SQL

    两种方法:db2top和snapshot for dynamic sql 1. db2top -d <dbname>

  7. RocketMQ 事件驱动:云时代的事件驱动有啥不同?

    前言: 从初代开源消息队列崛起,到 PC 互联网.移动互联网爆发式发展,再到如今 IoT.云计算.云原生引领了新的技术趋势,消息中间件的发展已经走过了 30 多个年头. 目前,消息中间件在国内许多行业 ...

  8. Gitee千Star优质项目解析: ng-form-element低开引擎解析

    好家伙, 在写项目的时候,我发现自己的平台的组件写的实在是太难看了,于是想去gitee上偷点东西,于是我们本期的受害者出现了 gitee项目地址 https://gitee.com/jjxliu306 ...

  9. 用 C 语言开发一门编程语言 — 交互式解释器

    目录 文章目录 目录 前言 环境 编译型 vs 解释型 实现交互式解释器 使用 GNU Readline 函数库 前言 通过开发一门类 Lisp 的编程语言来理解编程语言的设计思想,本实践来自著名的& ...

  10. 【PB案例学习笔记】-02 目录浏览器

    写在前面 这是PB案例学习笔记系列文章的第二篇,该系列文章适合具有一定PB基础的读者, 通过一个个由浅入深的编程实战案例学习,提高编程技巧,以保证小伙伴们能应付公司的各种开发需求. 文章中设计到的源码 ...