数据藏在接口里
3 道练习题·预计 45 分钟·做对一题解锁下一段
前言
前几章抓的,都是直接写在 HTML 里的数据。可是现在的网站,很多内容并不在一开始发回来的 HTML 里。那它们是怎么来的呢?网页先打开一个空架子,再由网页里的 JavaScript 去另一个地址把数据要过来,填进页面。
遇到这种情况,requests 拿到的就只有那个空架子。很多童鞋第一次遇到时,都会一头雾水:浏览器里明明看得到,程序怎么就是取不到呢?
先别急着发愁,这往往是好事。为什么这么说呢?因为数据既然是从「另一个地址」要过来的,那个地址发回来的多半是 JSON,比 HTML 干净得多。只要找到它,直接请求它,连解析 HTML 的功夫都省了。这种专门发数据的地址,叫接口(API)。
所以这一章,咱们就去找这些藏在背后的接口:先用开发者工具的「网络」面板,看看网页都要了哪些数据,再用 requests 直接去要。
学这一章之前,需要先学完 爬虫课第 6 章 ,会翻页,也会控制请求的节奏。另外,还要记得第 2 章讲过的 response.json() 。
目录
这一章会学到:
- 一、网页上有,requests 里没有——JavaScript 后来填的数据
- 二、在开发者工具里找接口——「网络」面板
- 三、直接请求接口——拿回来就是数据
- 四、分页的接口——顺着
next一直取 - 五、常见坑
- 六、自己查一次文档——把嵌套的数据打印得好读一点
- 七、小结与回看
学完这一章,你能找到网页背后的数据接口,用几个请求就把整个书店的数据完整取下来。
一、网页上有,requests 里没有
先来看一个例子。在浏览器里打开两点书店的 新书速递 ,能看到 8 本新书。那么,用学过的办法,能不能把它们取下来呢?试一试:
import requests
from bs4 import BeautifulSoup
html = requests.get('https://learn-py.org/lab/bookstore/new/').text
soup = BeautifulSoup(html, 'html.parser')
print(len(soup.select('li.new-book')))
print(soup.select_one('#new-books').get_text())输出的结果:
0
正在加载……结果一本都没取到,书单的位置上只有一句「正在加载……」。浏览器里明明看得到,这是怎么回事呢?
第 3 章说过,开发者工具「元素」面板里看到的,是运行完 JavaScript 以后的网页。而 requests 拿到的,是网站最初发回来的 HTML。这一页的书单,就是 JavaScript 后来才填进去的,所以 requests 拿到的 HTML 里还没有它。
那么,JavaScript 又是从哪儿拿到书单的呢?其实答案就写在 HTML 里。把 <script> 那一段找出来,打印前 60 个字符看看:
import requests
from bs4 import BeautifulSoup
html = requests.get('https://learn-py.org/lab/bookstore/new/').text
soup = BeautifulSoup(html, 'html.parser')
print(soup.select_one('script').get_text().strip()[:60])输出的结果:
fetch('/lab/bookstore/api/new.json')
.then((res) => res.js这里的 fetch() ,是 JavaScript 里发请求的函数。也就是说,网页打开以后,它又去请求了 /lab/bookstore/api/new.json ,拿到 JSON,再把书单填进页面。
在这个例子里,一眼就能看出来。可真实网站的 JavaScript 往往有成千上万行,还被压缩成了一团,很难这样找。那该怎么办呢?更通用的办法,是去开发者工具的「网络」面板里看,下一节就来试试。
二、在开发者工具里找接口
为什么说「网络」面板(Network)更通用呢?因为它会记下网页打开过程中发出的每一个请求:HTML、图片、样式,还有 JavaScript 要数据的请求。不管 JavaScript 写成什么样,只要它去要了数据,这里就有记录。
下面就在新书速递页上试一试:
- 打开开发者工具,切到「网络」面板。
- 刷新网页。面板里会列出一串请求。
- 在面板上方的筛选按钮里点「Fetch/XHR」,只看 JavaScript 发出的请求。列表里剩下一个
new.json。 - 点开它。在右边的「标头」(Headers)里,能看到完整的地址。在「预览」(Preview)或「响应」(Response)里,就是它发回来的数据:一个
books列表,每本书有id、title、price。
这个例子筛完只剩一个请求,很好找。可真实的网站,一刷新就是一大串请求,该怎么找呢?有几个诀窍:
- 先筛 Fetch/XHR ,大部分数据请求都在这里。
- 不知道是哪个,就一个一个点开「预览」看,哪个里面有你要的数据,就是它。
- 在网页上翻页、搜索、点「加载更多」的时候,盯着「网络」面板,看看又多出了哪些请求,往往就能找到翻页的接口和参数。
找到接口以后,先别急着写代码,在浏览器里直接打开它的地址看看,比如 new.json 。要是能直接看到 JSON,说明它不需要别的条件,用 requests.get() 就能拿到。
除了新书的接口,两点书店还有两个接口,后面几节会用到:
| 接口 | 发回什么 |
|---|---|
/lab/bookstore/api/page-1.json 到 page-6.json | 每一页的 20 本书,以及下一页接口的地址 |
/lab/bookstore/api/books/编号.json | 一本书的全部信息 |
其中第二个,第 2 章其实已经用过了。
接口都找到了,下一节就用 requests 直接去要数据。
三、直接请求接口
接口的地址找到了,剩下的就简单了:直接请求它,再用 .json() 把发回来的 JSON 变成 Python 的数据:
import requests
data = requests.get('https://learn-py.org/lab/bookstore/api/new.json').json()
print(type(data), list(data.keys()))
for book in data['books']:
print(book['id'], book['title'], book['price'])输出的结果:
<class 'dict'> ['books']
10 留白的秘密 63.6
27 一步步做小工具 58.6
33 运河千年 71.4
72 迟到的来信 42.2
84 字体的力量 82.5
103 海洋的故事 38.5
30 图标的秘密 47.3
57 小熊找朋友 44.1新书的编号、书名和价格,一下子就全拿到了。和前几章从 HTML 里抠数据比一比,省事在哪儿呢?
- 不用 BeautifulSoup,也不用写选择器。
- 价格本来就是数字
63.6,不用去掉¥再转换。 - 网站改了页面的样子,只要接口不变,代码就不用改。
再看输出的第一行。拿到接口的数据,第一件事是先看清它的结构:最外层是什么类型,有哪些键,列表里的每一项又长什么样。type() 和 keys() 就是用来做这个的。从输出可以看到,最外层是一个字典,里面只有一个键 books 。
还有一点要留意:接口发回来的数据,和网页上显示的不一定完全一样。比如新书速递页上,价格显示成 ¥63.60 ,接口里却是 63.6 。再比如,网页上只显示书名和价格,详情接口里却有页数、出版年份和简介。看到接口里有、网页上没有的字段,不妨也拿下来,说不定以后用得上。
下面先做一道练习,自己请求新书接口,找出最便宜的是哪本。
新书速递页的书单来自接口 https://learn-py.org/lab/bookstore/api/new.json 。请直接请求它,打印一共几本新书,以及最便宜的是哪本、多少钱(保留两位小数)。
运行后的输出应该是:
8 本新书
最便宜的是 海洋的故事,38.50 元推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai