Scrapy 案例一:通过 API 抓取天气预报数据

本文详细介绍了如何分析并获取一个天气网站的WebAPI数据,通过模拟浏览器行为抓取JSON格式的天气预报信息。内容包括定位WebAPI URL,分析返回的JavaScript代码,以及编写Scrapy爬虫程序进行数据提取。同时,展示了如何处理动态加载和防止缓存的策略,并提供了获取多个城市天气信息的方法。
摘要由CSDN通过智能技术生成

一、概述

在一些简单的网站中,可以发现,所有的数据都在网页代码中,然而在实际获取数据的过程中,我们可以发现,并不是所有的数据都在网页代码中 (大部分),对于通过 AJAX 方式更新数据的 Web 页面,通常会使用 Web API 的方式从服务端获取数据,然后通过 JS 代码将这些数据显示在 Web 页面的组件中。在这种情况下,我们是无法通过抓取 HTML 代码的方式获取这些数据,而要通过直接访问这些 Web API 的方式从服务端获取数据 (目前大多数情况来看这是常规操作)。

Web API 返回数据的形式有多种,但通常是以 JSON 格式的数据返回,当然有可能在返回的数据中插入其他的东西,例如 JavaScript 代码、HTML 代码、CSS 等。这就要具体问题具体分析了。对于这种情况,需要针对特定的 Web API 进行分析和获取,如下图所示:
在这里插入图片描述
本文案例主要是分析某天气网站的 Web API 数据格式,并模拟浏览器访问该网站的 Web API,通过该网站的 Web API 可以获取指定城市的天气预报数据。网址:aHR0cDovL3d3dy53ZWF0aGVyLmNvbS5jbi8=

二、案例分析

获取 Web API 数据不像抓取 Web 页面数据那么直接,获取 Web 页面时已经得知该页面的 URL,所以只需要传给 spider 该 URL ,就可以下载该页面的代码,然后通过 Xpath 或者其他技术获取特定的数据即可。而 Web API 尽管也需要通过 URL 访问,但这个 URL 肯定不会显示在 Web 页面上,所以需要我们自己通过各种技术去定位 Web APIURL。获取 URL 后,才可以去分析和抓取 Web API 数据。

我们进入该天气网站的首页,在页面右上方的文本输入框输入一个城市的名字,如 cq(用英文代替),然后选择列表中显示的第一项,效果如下图所示,最后按 Enter 键搜索指定城市的天气信息。
在这里插入图片描述
搜索出来的天气信息如下图所示,这个搜索页面的信息有的是在页面上,但有的并没有在页面上,例如,左侧的气温实况就无法直接从搜索页面获得,其实这些信息都是从 Web API 获取的,然后通过 JS 代码显示在 Web 页面上。
在这里插入图片描述
现在的任务就是要找到上图所示页面使用的 Web API 地址,然后通过 spider 访问这个地址来抓取数据。在浏览器页面右击,单击弹出菜单中的 检查 命令 (后续案例这种简单的操作将不再赘述),如下图所示:
在这里插入图片描述
这时在浏览器页面会显示如下图所示的调试页面:
在这里插入图片描述
打开调试页面后,在最上面有一排选项卡。单击 Network 选项卡,一开始在该选项卡中什么都没有,这是因为进入 Network 选项卡后,刷新页面才会显示数据。现在重新按 F5 或者是浏览器左上角的 重新加载此页 按钮重新刷新当前页面,会看到下图所示的效果:
在这里插入图片描述

Network 选项卡最下方的列表中会显示当前 Web 页面所有访问的 URL (包括 html、css、js、图像等 URL),这些 URL 有的是用同步的方式访问的,有的是用异步方式 (AJAX) 访问的。不管用哪种方式,通过 Web API 是采用 JSON、XML 等数据格式交互数据的,因为这样有利用 Web 页面通过 JavaScript 进行分析和处理。

如果 URL 较少,我们可以采取最原始的方式,逐个 URL 寻找,当 URL 多的时候,采用此种方式就太慢了,而且容易漏掉重要信息。所以可凭经验使用各种技巧进行搜索。首先要确定的是这些 URL 中是否有 JSONXML 格式的数据。在 Network 选项卡的上方有一个 XHR 过滤器,如下图所示,单击 XHR 过滤器,会过滤掉其他非 XHR 格式的 URL
在这里插入图片描述
补充说明:XHR 是 XMLHttpRequest 的缩写,专门指通过 XMLHttpRequest 对象发送出去的数据,通常是 JSON 格式和 XML 格式的数据。XMLHttpRequest 也是 AJAX 技术的基础。

不过很可惜,切换到 XHR 过滤器后,下方列表中什么都没有,这就意味着 Web API 的数据格式不是 JSONXML ,至少不是纯正的 JSON 格式或 XML 格式。现在使用另一种方法来搜索 Web API URL。这种方式一半靠猜,另一半靠运气。可以假设 Web APIURL 与当前页面使用相同的域名,所以在 Network 选项卡左上角的过滤器文本框中输入当前页面的域名。这时会过滤出所有域名是与当前页面相同的 URL。不过 URL 还是太多了 (其实向下翻动发现 URL 并不多,大多数都是一些图片资源的链接)。

接下来继续尝试,可以猜测,当前页面的 URL 中包含了一串数字,这个数字可能是某个城市的标识,所以我们可以在 Network 选项卡左上角的过滤器文本框中输入 101040100,这时下面列表中显示的 URL 明显变少了,如下图所示:
在这里插入图片描述

现在可以逐个查看每一个过滤出来的 URL 了。找到了几个看似相似 JSON 格式的数据,其中有一个 URL 返回的数据就是页面上的天气信息,如下图所示:

在这里插入图片描述

查找方式补充1:其实这里还可以这样尝试,当前页面是动态加载的数据,所以除了 XHR 我们还可以切换到 JS 进行查看,如下图所示:

在这里插入图片描述

查找方式补充2:当前页面是通过 AJAX 的方式访问的 Web API,所以访问 Web API 的时间肯定会在主页面和大多数图像、css 等资源之后,Network 选项卡支持按访问时间过滤,我们可以在之前的所示的 Network 选项卡中 URL 列表的上方,选择一段稍微靠后的时间段,例如:600ms 到 650ms,这时下面列表中显示的 URL 更加少了,如下图所示:

在这里插入图片描述

这个 JSON 格式的数据有些特别,并不是纯的 JSON 格式,而是一段 JavaScript 代码,将一个 JavaScript 对象赋给了一个变量,这就是为什么 XHR 过滤器没过滤出这个 URL 的原因,因为这根本就不是 JSON 格式的数据,而是一段 JavaScript 代码,当然,效果与 JSON 格式数据是相同的。其实这也是一种简单的反爬虫技术,这种技术可以在一定程度上防止爬虫找到和分析 JSON 格式的数据,不过这种反爬技术相当简陋,对于稍微有一点经验的爬虫程序员,这种反爬虫技术毫无意义。

现在已经基本了解当前页面是如何从服务端获取天气信息的。首先会通过这个 URL 访问 Web API,然后在 Web 端执行 Web API 返回的数据 (因为是一段 JavaScript 代码),接下来就会通过保存天气信息的 JavaScript 变量 dataSK 访问相应的天气信息,并将这些信息显示在页面上。完整的 Web API URL

aHR0cDovL2QxLndlYXRoZXIuY29tLmNuL3NrXzJkLzEwMTA0MDEwMC5odG1sP189MTY2Mjk5NTgzMTE0Ng==

这个 URL 从表面上看是一个 html 页面,看着像是一个静态的页面,其实不一定是静态页面,也可能是服务端故弄玄虚,这个静态页面很可能是一个路由,实际上是对应的一个动态的服务端程序。这么做的好处至少有如下两点:

  1. 容易被搜索引擎搜索到,因为像 Google、Baidu 等搜索引擎,更容易搜索像 html 一样的静态资源。
  2. 可以隐藏服务端使用的技术,如果 URL 的扩展名直接使用 php 或其他服务端程序的扩展名以及其他特征,那么很容易猜到服务端使用的是什么技术,如果用路由映射成静态页面,那么服务端可能会采用任何技术实现。

这个 URL 还有一个特别之处,就是后面跟一个数字,多次刷新当前网页,每次得到的 URL 返回的数据基本是一样的,但 URL 最后的数字每次都不一样,其实这个很容易猜到,这个数字是随机产生的,为了防止浏览器使用缓存。因为这个 URL 需要实时返回天气信息,而浏览器会对同一个 URL 在一定时间内第二次及以后的访问使用本地的缓存,如果是这样,就无法实时获取天气信息了,所以客户端在每次访问这个 Web API 时,自动在 URL 后面加一个随机的数字,这样浏览器将永远不会对这个 URL 使用缓存了。在使用爬虫访问这个 Web API 时,如果能保证不使用本地缓存,也可以不加这个数字。(总结:了解开发知识、做过开发更利于我们分析网页,编写 spider 则会更加轻松)

上面的这个 Web API URL 是针对具体城市的。101040100 表示 cq 的编码,这是一个标准的程序编码,为了方便,本例只获取某个具体城市的天气信息,如果要获取 qg 所有程序的天气信息,只需为 spider 提供一个城市 code 列表即可。(后续我也会演示)

现在做最后一个尝试,就是直接使用浏览器访问这个 URL(因为是 get 请求),不过可惜,得到了如下图所示的结果:
在这里插入图片描述
出现这个错误的原因并不是这个 URL 不存在,而是服务端禁止通过这种方式访问。现在切换到 Headers 选项卡,会看到该 URL 确实是通过 HTTP GET 请求访问的,那么为什么服务端会禁止访问该 URL 呢?

在这里插入图片描述

原因只有一个,就是在访问这个 URL 时,通过 HTTP 请求头向服务端发送了其他的信息,而直接通过浏览器访问这个 URL 时并没有向服务端提供这些信息。通常来讲,这些信息是通过 Cookie 向服务端发送的,所以在使用 spider 模拟浏览器访问 Web API 时,还要向服务端发送这些 Cookie 信息。除了 Cookie 信息外,服务端可能还要求其他的 HTTP 请求头,如 Host 等,所以在模拟浏览器访问 Web API 时,最好完整地将浏览器向服务端发送的数据都给服务端发过去 (我们也可以挨个调试)

Headers 标签页下方找到 Request Headers 部分,这一部分是 Web API 向服务端发送的所有 HTTP 请求头信息,如下图所示:
在这里插入图片描述

单击 view source 链接,会看到完整的代码 (不要复制第一行), 然后将这些代码复制到一个名为 headers.txt 的文本文件中,将该文本文件放到 spiders 目录中。之所以将 HTTP 请求头信息放在 headers.txt 文本文件中,是为了以后更新信息方便。爬虫程序会从 headers.txt 文件中读取 HTTP 请求头信息。
在这里插入图片描述
现在到了最后一步,就是编写 spider 获取 Web API 返回的数据,不过在编写程序之前,需要先分析一下 Web API 返回的数据格式。根据前面的描述,Web API 返回的是一段如下所示的 JavaScript 代码,当然,没必要执行这段 JavaScript 代码,而只需将需要的信息提取出来即可。

var dataSK = {
    "nameen": "chongqing",
    "cityname": "重庆",
    "city": "101040100",
    "temp": "29",
    "tempf": "84",
    "WD": "西风",
    "wde": "W",
    "WS": "1级",
    "wse": "2km\/h",
    "SD": "53%",
    "sd": "53%",
    "qy": "978",
    "njd": "10km",
    "time": "23:05",
    "rain": "0",
    "rain24h": "0",
    "aqi": "82",
    "aqi_pm25": "82",
    "weather": "多云",
    "weathere": "Cloudy",
    "weathercode": "d01",
    "limitnumber": "",
    "date": "09月12日(星期一)"
}

这段 JavaScript 代码非常简单,只是为一个变量赋值的操作,其实只需要将等号 = 后面的内容提取出来即可(一个简单的字符串截取操作)。

三、编码实现

完整的项目结构,如下图所示:
在这里插入图片描述
爬虫示例代码如下:

import scrapy
import json
import re
from WeatherSpider.items import WeatherspiderItem


# 从headers.txt 文件中读取 HTTP 请求头信息
def get_headers(file_name):
    # 用于保存请求头信息的字典
    header_dict = {}
    f = open(file_name, 'r')  # 打开 headers.txt文件
    headers_text = f.read()  # 读取 headers.txt文件的所有内容
    headers = re.split('\n', headers_text)  # 将headers.txt文件的内容用换行符分隔成多行
    for header in headers:
        # 将每一行用: 分成两部分,前一部分是请求字段,后一部分是请求值
        result = re.split(r': ', header, maxsplit=1)
        header_dict[result[0]] = result[1]  # 保存当前请求字段和请求值
    f.close()  # 关闭 headers.txt文件
    return header_dict


# 用于抓取Web API的爬虫类
class WeatherSpider(scrapy.Spider):
    name = 'weather'  # 爬虫名称

    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']

    # 爬虫运行时会自动调用start_requests方法向服务端发送请求
    def start_requests(self):
        # 读取headers.txt文件中的内容
        headers = get_headers(r'./WeatherSpider/spiders/headers.txt')
        print(headers)
        # 定义要访问的 URL 通过 headers 命名参数指定HTTP请求头信息
        yield scrapy.Request(url='网址', headers=headers)

    # 当成功抓取 Web API数据后调用该方法
    def parse(self, response, **kwargs):
        # 截取等号后面的内容
        result = response.text[response.text.find('{'):]
        # 将截取的内容转换为 json 对象
        json_dict = json.loads(result)
        # print(json_dict.items())
        # 要返回的 item
        weather_item = WeatherspiderItem()
        # print(result)
        # 动态向item中添加字段
        for key, value in json_dict.items():
            # 动态向 weather_item 中添加 field类型的属性
            weather_item.fields[key] = scrapy.Field()
            weather_item[key] = value
        yield weather_item

上面的代码涉及一个 WeatherspiderItem 类,parse 方法会返回 WeatherspiderItem 类的实例,该实例用于描述天气信息,WeatherspiderItem 对象中的成员与 Web API 返回格式数据的字段相同。由于 WeatherspiderItem 对象是动态向其添加成员的,所以 WeatherspiderItem 类并不需要编写实际的代码。

四、获取多个 city 的天气信息(简单实现)

找到城市编码的接口,解析出重庆市所有区县的编码,如下:
在这里插入图片描述
修改 weather.py 中的代码为:

import time

import scrapy
import json
import re
from WeatherSpider.items import WeatherspiderItem


# 从headers.txt 文件中读取 HTTP 请求头信息
def get_headers(file_name):
    # 用于保存请求头信息的字典
    header_dict = {}
    f = open(file_name, 'r')  # 打开 headers.txt文件
    headers_text = f.read()  # 读取 headers.txt文件的所有内容
    headers = re.split('\n', headers_text)  # 将headers.txt文件的内容用换行符分隔成多行
    for header in headers:
        # 将每一行用: 分成两部分,前一部分是请求字段,后一部分是请求值
        result = re.split(r': ', header, maxsplit=1)
        header_dict[result[0]] = result[1]  # 保存当前请求字段和请求值
    f.close()  # 关闭 headers.txt文件
    return header_dict


# 用于抓取Web API的爬虫类
class WeatherSpider(scrapy.Spider):
    name = 'weather'  # 爬虫名称

    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
    url_template = "模板"

    # 爬虫运行时会自动调用start_requests方法向服务端发送请求
    def start_requests(self):
        yield scrapy.Request(url="所有城市编码接口", callback=self.parse_code)

    def parse_code(self, response, **kwargs):
        # 读取headers.txt文件中的内容
        headers = get_headers(r'./WeatherSpider/spiders/headers.txt')
        result = response.text[response.text.find('{'):]
        json_dict = json.loads(result)
        # 获取重庆所有区县的编码
        cq_codes = json_dict.get("重庆").get("重庆")
        for _ in cq_codes.items():
            area_id = _[1].get("AREAID")
            yield scrapy.Request(url=self.url_template.format(area_id, int(round(time.time() * 1000))),
                                 callback=self.parse, headers=headers)

    # 当成功抓取 Web API数据后调用该方法
    def parse(self, response, **kwargs):
        # 截取等号后面的内容
        result = response.text[response.text.find('{'):]
        # 将截取的内容转换为 json 对象
        json_dict = json.loads(result)
        print(json_dict)
        # print(json_dict.items())
        # 要返回的 item
        weather_item = WeatherspiderItem()
        # print(result)
        # 动态向item中添加字段
        for key, value in json_dict.items():
            # 动态向 weather_item 中添加 field类型的属性
            weather_item.fields[key] = scrapy.Field()
            weather_item[key] = value
        yield weather_item

至此今天的案例就到此结束了,笔者在这里声明,笔者写文章只是为了学习交流,以及让更多学习 Python 基础的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!


在这里插入图片描述

    好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
    如果我的博客对你有帮助、如果你喜欢我的博客内容,请 点赞评论收藏 一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
 编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了 关注 我哦!

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
1. requests的基本使用: - 安装:使用pip命令安装requests库:`pip install requests` - 导入:在Python代码中导入requests库:`import requests` - 发送HTTP请求:使用requests库发送HTTP请求,例如发送GET请求:`response = requests.get(url)`,发送POST请求:`response = requests.post(url, data=params)` - 处理响应:可以通过response对象获取响应状态码、头部信息、内容等:`status_code = response.status_code`,`headers = response.headers`,`content = response.content` - 其他功能:requests还提供了处理Cookie、设置请求头、发送文件、处理重定向、处理代理等功能,可以根据具体需求使用。 2. Requests的使用案例: - 网页爬取使用requests库获取网页内容,例如爬取新闻标题、图片等。 - API请求:使用requests库发送API请求,获取数据并进行处理。 - 文件下载:使用requests库下载文件,例如下载图片、视频等。 - 表单提交:使用requests库模拟表单提交,例如登录、注册等操作。 3. Scrapy的基本使用: - 安装:使用pip命令安装Scrapy框架:`pip install scrapy` - 创建项目:使用命令行工具创建Scrapy项目:`scrapy startproject project_name` - 定义Spider:在项目中定义Spider,编写抓取和解析规则,指定起始URL等。 - 编写Item和Pipeline:定义Item类来存储抓取数据,编写Pipeline类来处理Item数据。 - 运行爬虫使用命令行工具运行爬虫:`scrapy crawl spider_name` - 处理数据Scrapy提供了处理数据的机制,可以使用XPath或CSS选择器来提取和处理抓取数据。 4. Scrapy使用案例: - 网页爬虫使用Scrapy框架抓取网站的数据,例如爬取商品信息、新闻内容等。 - 数据抓取与存储:使用Scrapy框架抓取数据并存储到数据库或文件中,例如抓取电影信息、音乐评论等。 - 动态网页爬取Scrapy配合Selenium或Splash等工具,可以抓取动态网页中的数据,例如抓取JavaScript渲染的页面。 - 分布式爬虫使用Scrapy框架搭建分布式爬虫系统,提高爬取效率和稳定性。 Requests是一个用于发送HTTP请求的库,适合快速简单的网络请求和数据获取。而Scrapy是一个功能强大的Python网络爬虫框架,适合构建复杂的、高效的和可扩展的网络爬虫。根据具体需求和项目复杂度,可以选择合适的工具来完成任务。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Amo Xiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值