Python 环境加载中…

数据藏在接口里

3 道练习题·预计 45 分钟·做对一题解锁下一段

完整通读本章 →

教学 01 / 08· 已读

前言

前几章抓的,都是直接写在 HTML 里的数据。可是现在的网站,很多内容并不在一开始发回来的 HTML 里。那它们是怎么来的呢?网页先打开一个空架子,再由网页里的 JavaScript 去另一个地址把数据要过来,填进页面。

遇到这种情况,requests 拿到的就只有那个空架子。很多童鞋第一次遇到时,都会一头雾水:浏览器里明明看得到,程序怎么就是取不到呢?

先别急着发愁,这往往是好事。为什么这么说呢?因为数据既然是从「另一个地址」要过来的,那个地址发回来的多半是 JSON,比 HTML 干净得多。只要找到它,直接请求它,连解析 HTML 的功夫都省了。这种专门发数据的地址,叫接口(API)。

所以这一章,咱们就去找这些藏在背后的接口:先用开发者工具的「网络」面板,看看网页都要了哪些数据,再用 requests 直接去要。

学这一章之前,需要先学完 爬虫课第 6 章 ,会翻页,也会控制请求的节奏。另外,还要记得第 2 章讲过的 response.json() 。

目录

这一章会学到:

  • 一、网页上有,requests 里没有——JavaScript 后来填的数据
  • 二、在开发者工具里找接口——「网络」面板
  • 三、直接请求接口——拿回来就是数据
  • 四、分页的接口——顺着 next 一直取
  • 五、常见坑
  • 六、自己查一次文档——把嵌套的数据打印得好读一点
  • 七、小结与回看

学完这一章,你能找到网页背后的数据接口,用几个请求就把整个书店的数据完整取下来。

教学 02 / 08· 已读

一、网页上有,requests 里没有

先来看一个例子。在浏览器里打开两点书店的 新书速递 ,能看到 8 本新书。那么,用学过的办法,能不能把它们取下来呢?试一试:

Python· 可在网页运行
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 个字符看看:

Python· 可在网页运行
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 往往有成千上万行,还被压缩成了一团,很难这样找。那该怎么办呢?更通用的办法,是去开发者工具的「网络」面板里看,下一节就来试试。

教学 03 / 08· 已读

二、在开发者工具里找接口

为什么说「网络」面板(Network)更通用呢?因为它会记下网页打开过程中发出的每一个请求:HTML、图片、样式,还有 JavaScript 要数据的请求。不管 JavaScript 写成什么样,只要它去要了数据,这里就有记录。

下面就在新书速递页上试一试:

  1. 打开开发者工具,切到「网络」面板。
  2. 刷新网页。面板里会列出一串请求。
  3. 在面板上方的筛选按钮里点「Fetch/XHR」,只看 JavaScript 发出的请求。列表里剩下一个 new.json 。
  4. 点开它。在右边的「标头」(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 直接去要数据。

教学 04 / 08

三、直接请求接口

接口的地址找到了,剩下的就简单了:直接请求它,再用 .json() 把发回来的 JSON 变成 Python 的数据:

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 。再比如,网页上只显示书名和价格,详情接口里却有页数、出版年份和简介。看到接口里有、网页上没有的字段,不妨也拿下来,说不定以后用得上。

下面先做一道练习,自己请求新书接口,找出最便宜的是哪本。

练习 1 / 3直接请求新书接口题目有问题?

新书速递页的书单来自接口 https://learn-py.org/lab/bookstore/api/new.json 。请直接请求它,打印一共几本新书,以及最便宜的是哪本、多少钱(保留两位小数)。

运行后的输出应该是:

文本
8 本新书
最便宜的是 海洋的故事,38.50 元
main.py
可编辑
做对当前题解锁下一段 ·03

推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai

学习目录