当前位置: 首页 > news >正文

【13】Ajax爬取案例实战

目录

 一、准备工作

二、爬取目标

三、初步探索:如何判断网页是经js渲染过的?        

四、爬取列表页

4.1 分析Ajax接口逻辑

4.2 观察响应的数据

4.3 代码实现

(1)导入库

(2)定义一个通用的爬取方法

(3)定义一个爬取列表页的方法

 五、爬取详情页

5.1分析详情页

5.2 如何将详情页与列表页关联起来

5.3 代码实现

六、main总调用

七、完整代码(简化版)


        上一节我们学习了 Ajax 的基本原理和分析方法,这一课时我们结合实际案例,学习 Ajax 分析和爬取页面的具体实现。

上期文章: 【12】Ajax的原理和解析-CSDN博客

 一、准备工作

        安装好 Python 3(最低为 3.6 版本),并能成功运行 Python 3 程序。

        了解 Python HTTP 请求库 requests 的基本用法。

        了解 Ajax 的基础知识和分析 Ajax 的基本方法。

二、爬取目标

以一个动态渲染网站为例来试验一下 Ajax 的爬取。其链接为:Scrape | Movie,页面如图所示。

         这个页面看似和我们上一课时的案例一模一样,但其实不是,它的后台实现逻辑和数据加载方式与上一课时完全不同,只不过最后呈现的样式是一样的!!

本课时我们需要完成的目标有:

        分析页面数据的加载逻辑;

        用 requests 实现 Ajax 数据的爬取;

        将每部电影的数据保存成一个 JSON 数据文件;

三、初步探索:如何判断网页是经js渲染过的?        

        尝试用之前的 requests 来直接提取页面,看看会得到怎样的结果。用最简单的代码实现一下 requests 获取首页源码的过程,代码如下:

import requests



url = 'https://spa1.scrape.center/page/1'

html = requests.get(url).text

print(html)

 运行结果如下:

可以看到我们只爬取到了这么一点 HTML 内容,只是一个空壳,没有数据,也就是说在 HTML 中我们只能在源码中看到引用了一些 JavaScript 和 CSS 文件,并没有观察任何有关电影数据的信息。

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

        在一般情况下,这些数据都是通过 Ajax 来加载的, JavaScript 在后台调用这些 Ajax 数据接口,得到数据之后,再把数据进行解析并渲染呈现出来,得到最终的页面。所以说,要想爬取这个页面,我们可以通过直接爬取 Ajax 接口获取数据。

        在上一课时中,我们已经了解了用 Ajax 分析的基本方法。下面我们就来分析下 Ajax 接口的逻辑并实现数据爬取吧。

四、爬取列表页

4.1 分析Ajax接口逻辑

        首先我们来分析下列表页的 Ajax 接口逻辑,打开浏览器开发者工具,切换到 Network 面板,勾选上 「Preserve Log」并切换到 「XHR」选项卡,重新刷新页面,然后点击第 2 页、第 3 页、第 4 页的按钮,这时候可以看到页面上的数据发生了变化,同时在开发者工具下方会监听到几个 Ajax 请求,如图所示:

        由于我们切换了 10页,所以这里正好也出现了 10个 Ajax 请求,我们可以任选一个点击查看其请求详情,观察其请求的 URL、参数以及响应内容是怎样的,如图所示:

这里我们点开第 2 个结果,观察到其 Ajax 接口请求的 URL 地址为:https://spa1.scrape.center/api/movie/?limit=10&offset=10,这里有两个参数,一个是 limit,其值为 10,一个是 offset,它的值也是 10。 

        通过观察多个 Ajax 接口的参数,我们可以发现这么一个规律:limit 的值一直为 10,这就正好对应着每页 10 条数据;offset 的值在依次变大,页面每加 1 页,offset 就加 10,这就代表着页面的数据偏移量,比如第 2 页的 offset 值为 10 代表跳过 10 条数据,返回从第 11 条数据开始的结果,再加上 limit 的限制,就代表返回第 11~20 条数据的结果。

4.2 观察响应的数据

接着我们再观察下响应的数据,切换到 Preview 选项卡,结果如图所示。 

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

        这样的话,我们只需要把所有页面的 Ajax 接口构造出来,那么所有的列表页数据我们都可以轻松获取到了。

4.3 代码实现

(1)导入库

我们先定义一些准备工作,导入一些所需的库并定义一些配置,代码如下:

import requests

import logging

logging.basicConfig(level=logging.INFO,

                    format='%(asctime)s - %(levelname)s: %(message)s')

INDEX_URL = 'https://dynamic1.scrape.center/api/movie/?limit={limit}&offset={offset}'

(2)定义一个通用的爬取方法

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 字符串

(3)定义一个爬取列表页的方法

LIMIT = 10

def scrape_index(page):

    url = INDEX_URL.format(limit=LIMIT, offset=LIMIT * (page - 1))

    return scrape_api(url)

        定义了一个 scrape_index 方法,用来接收参数 page,page 代表列表页的页码。构造了一个 URL,通过字符串的 format 方法,传入 limit 和 offset 的值。这里的 limit 直接使用了全局变量 LIMIT 的值,offset 则是动态计算的,计算方法是页码数减 1 再乘以 limit,比如第 1 页的 offset 值就是 0,第 2 页的 offset 值就是 10,以此类推。构造好 URL 之后,直接调用 scrape_api 方法并返回结果即可。

这样我们就完成了列表页的爬取,每次请求都会得到一页 10 部的电影数据。

        由于这时爬取到的数据已经是 JSON 类型了,所以我们不用像之前一样去解析 HTML 代码来提取数据,爬到的数据就是我们想要的结构化数据,因此解析这一步这里我们就可以直接省略啦。

到此为止,我们就能成功爬取列表页并提取出电影列表信息了。

 五、爬取详情页

5.1分析详情页

        这时候我们已经可以拿到每一页的电影数据了,但是实际上这些数据还缺少一些我们想要的信息,如剧情简介等,所以我们需要进一步进入到详情页来获取这些内容。

        稍加观察我们就可以发现,Ajax 请求的 URL https://spa1.scrape.center/detail/2后面有一个参数是可变的,这个参数就是电影的 id,这里是 2,对应《这个杀手不太冷》这部电影。所以如果我们想要获取 id 为 50 的电影,只需要把 URL 最后的参数改成 50 即可,即 https://spa1.scrape.center/detail/50/,请求这个新的 URL 我们就能获取 id 为 50 的电影所对应的数据了。

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

5.2 如何将详情页与列表页关联起来

        分析了详情页的数据提取逻辑,那么怎么把它和列表页关联起来呢?这个 id 又是从哪里来呢?我们回过头来再看看列表页的接口返回数据,如图所示:

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

5.3 代码实现


DETAIL_URL = 'https://spa1.scrape.center/detail/{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 即可。 

六、main总调用

接着,我们定义一个总的调用方法,将以上的方法串联调用起来,代码如下:

TOTAL_PAGE = 10



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)

        这里我们定义了一个 main 方法,首先遍历获取页码 page,然后把 page 当成参数传递给 scrape_index 方法,得到列表页的数据。接着我们遍历所有列表页的结果,获取每部电影的 id,然后把 id 当作参数传递给 scrape_detail 方法,来爬取每部电影的详情数据,赋值为 detail_data,输出即可。

七、完整代码(简化版)

ref: 爬虫基础-Ajax爬取实战_request采集ajkx-CSDN博客  

import requests
import json
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s : %(message)s')

BASE_URL = 'https://spa1.scrape.center/api/movie?limit={limit}&offset={offset}'
INDEX_URl = 'https://spa1.scrape.center/api/movie/{id}/'


'''通用爬取方法'''
def scrape_method(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.json()
        else:
            logging.error(f"请求{url}的状态码:{requests.status_codes}")
    except requests.RequestException as e:
        logging.error(e)


'''爬取每一页的url'''
def scrape_baseUrl(limitA,offsetA):
    url = BASE_URL.format(limit=limitA,offset=offsetA)
    return scrape_method(url)


'''爬取每一个电影'''
def scrape_indexRul(id):
    url = INDEX_URl.format(id = id)
    return scrape_method(url)


def main():
    limitNum = 10
    # 一共11页offset依次传入0 10 20 .... 100
    for i in range(0,100,10):
        jsonObject = scrape_baseUrl(limitNum,i)
        logging.info(jsonObject.get('results'))
        # 此时results是dict类型,需要转换为json对象,再存入json文件
        for item in jsonObject.get('results'):
            id = item.get('id')
            indexData = scrape_indexRul(id)
            # 以追加的方式将每一部电影的所有属性都存入到bb.json文件中
            with open('bb.json', 'a', encoding='utf-8') as file:
                file.write(json.dumps(indexData, indent=2, ensure_ascii=False))
                file.write('\n')


if __name__ == '__main__':
    main()

运行结果:

 

json文件:

 

受不了了。。。自己写的运行不出来。。。

 

相关文章:

  • 通过Docker快速搭建VoceChat | 开源轻量自托管聊天工具
  • 基于Spring Boot的网上商城系统的设计与实现(LW+源码+讲解)
  • 213.SpringSecurity:授权,授权实战,OAuth2,SpringSecurity中OAuth2认证服务器、资源服务器搭建,JWT
  • Oracle 19C 备份
  • vue3中<script setup>语法糖是什么意思。为什么叫语法糖,为什么叫糖,它甜吗
  • vue2前端日志数据存储(indexedD)自动清理3天前的数据
  • 数据结构初阶-二叉树链式
  • el-input表单校验只能输入数字格式的数据
  • 火山引擎云上实战: DeepSeek R1 大模型(全尺寸)
  • 把手搭建vue前后端管理系统-TAB标签通过pinia来进行管理(二十六)
  • [特殊字符] 校园外卖跑腿平台源码技术解析与实战搭建指南
  • 鸡生蛋还是蛋生鸡? 基于python的CCM因果关系计算
  • ROS2的发展历史、核心架构和应用场景
  • 【机器学习】使用Python Spark MLlib进行预测模型训练
  • ChatDBA VS DeepSeek:快速诊断 OceanBase 集群新租户数据同步异常
  • GPU架构与通信互联技术介绍
  • 如何使用Tailwind CSS创建一个组合了很多样式的类名,实现样式复用
  • 【概念】Node.js,Express.js MongoDB Mongoose Express-Validator Async Handler
  • [ComfyUI] SDXL Prompt Styler 自定义节点的作用解析
  • 【前端扫盲】node.js npm nvm都是什么以及他们之间的关系
  • 黄宾虹诞辰160周年|一次宾翁精品的大集结
  • 美国参议院投票通过戴维·珀杜出任美国驻华大使
  • 纪念|海上金石学的兴盛与王昶《金石萃编》
  • 北京公园使用指南
  • 澎湃思想周报丨数字时代的育儿;凛冬已至好莱坞
  • 合肥一季度GDP为3003.88亿元,同比增长6.6%