Scrapy 案例一:通过 API 抓取天气预报数据
Posted Amo Xiang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scrapy 案例一:通过 API 抓取天气预报数据相关的知识,希望对你有一定的参考价值。
目录
一、概述
在一些简单的网站中,可以发现,所有的数据都在网页代码中,然而在实际获取数据的过程中,我们可以发现,并不是所有的数据都在网页代码中 (大部分),对于通过 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 API
的 URL
。获取 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
中是否有 JSON
或 XML
格式的数据。在 Network
选项卡的上方有一个 XHR
过滤器,如下图所示,单击 XHR
过滤器,会过滤掉其他非 XHR
格式的 URL
。
补充说明:XHR 是 XMLHttpRequest 的缩写,专门指通过 XMLHttpRequest 对象发送出去的数据,通常是 JSON 格式和 XML 格式的数据。XMLHttpRequest 也是 AJAX 技术的基础。
不过很可惜,切换到 XHR
过滤器后,下方列表中什么都没有,这就意味着 Web API
的数据格式不是 JSON
或 XML
,至少不是纯正的 JSON
格式或 XML
格式。现在使用另一种方法来搜索 Web API URL
。这种方式一半靠猜,另一半靠运气。可以假设 Web API
的 URL
与当前页面使用相同的域名,所以在 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 页面,看着像是一个静态的页面,其实不一定是静态页面,也可能是服务端故弄玄虚,这个静态页面很可能是一个路由,实际上是对应的一个动态的服务端程序。这么做的好处至少有如下两点:
- 容易被搜索引擎搜索到,因为像 Google、Baidu 等搜索引擎,更容易搜索像 html 一样的静态资源。
- 可以隐藏服务端使用的技术,如果 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 基础的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!
好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,请点赞
、评论
、收藏
一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了关注
我哦!
以上是关于Scrapy 案例一:通过 API 抓取天气预报数据的主要内容,如果未能解决你的问题,请参考以下文章
Python爬虫编程思想(156):使用Scrapy抓取天气预报数据
Python爬虫编程思想(156):使用Scrapy抓取天气预报数据