数据分析入门——不用爬虫带你抓数据
插在前面的,今天准备开始日更,打开网易云音乐,竟然很贴心的推荐了《写作文勿扰专用》的歌单,好贴心哦:
前面讲爬虫的时候提到过,现在很多架构都是前后端分离了,很多数据是有API的,我们可以利用前端知识顺藤摸瓜找到这些API,拿到更加结构化的数据。
下面,以知乎为例,随便打开知乎一个有文章列表的页面:
以思维学习这个专题为例
我们尝试抓取这里面384个内容。
寻找线索
首先,我们打开浏览器的检查工具,在网络——XHR中找到里面的请求进行分析:
从字面意思我们也能判断个大概,如果不放心,可以一个个点开看右边的预览内容。很幸运,items就是我们需要的这个列表,返回的是一个json结果,里面的结构非常清晰。
右键我们可以得到它的URL:
https://www.zhihu.com/api/v4/favlists/20351986/items?offset=20&limit=20&include=data%5B*%5D.created%2Ccontent.comment_count%2Csuggest_edit%2Cis_normal%2Cthumbnail_extra_info%2Cthumbnail%2Cdescription%2Ccontent%2Cvoteup_count%2Ccreated%2Cupdated%2Cupvoted_followees%2Cvoting%2Creview_info%2Cis_labeled%2Clabel_info%2Crelationship.is_authorized%2Cvoting%2Cis_author%2Cis_thanked%2Cis_nothelp%2Cis_recognized%3Bdata%5B*%5D.author.badge%5B%3F(type%3Dbest_answerer)%5D.topics
这里要对http的get请求的表达方式有个基本的认识:
- “?”号开始都是参数,参数名和参数值中间是等号链接
- 多个参数之间用“&”符号链接
- “%”开头的是一些特殊字符经过URL编码处理的结果,我们可以对它进行解码,还原它的本来面目。浏览器也提供了直接的结果,就在刚才的XHR里面,点击右边的标头,最下面的查询字符串参数部分:
通过浏览器的工具,我们
显而易见,这个URL非常的长,里面的参数非常的多,可读性也很差,为了方便起见,我根据“&”符号进行了分割,更加便于阅读。
我们看到里面最长的一个参数include,通过字面意思我们推测这个里面大概是在告诉接口我需要返回的数据要包含那些信息。
这里我在插入一个工具——postman,进行api分析或测试的必备神器,我们可以把上面那个复杂的链接贴进去看看效果:
自动帮我们把参数分离了出来,是不是很方便。
继续说include,如果里面确实是关于返回结果的定义,那么我们可以尝试不做这些现实,大概率接口也会给我一个默认的结构:
可以对比上一张截图,data部分的结构没有区别,我们关心的title、url、updated_time这些关键信息都有,所以我们可以放心的把include这个参数扔掉了。
url分析清楚了,接下来就可以开始写程序了。
编码
为了培养大家一个好的习惯,我们可以对这个URL的参数做一定的格式化,我通常习惯设计一个python的dict来代替,因为dict也可以作为python的Request包的get方法的标准参数,所以我们先做一个基本的定义:
class ZhiHu(object):
base_url = "https://www.zhihu.com/api/v4/favlists/20351986/items"
params = {
'offset': 20,
'limit': 20,
'include': ''
}
这样我们就用一个类定义的方式描述了这样一个请求。如果我们想一起请求回来更多的数据,我们希望limit的值是一个可变的值,那么我们可以设计一个方法来实现:
class ZhiHu(object):
base_url = "https://www.zhihu.com/api/v4/favlists/20351986/items"
params = {
'offset': 20,
'limit': 20,
'include': ''
}
def __init__(self, offset=20, limit=20, include=''):
self.params = {
'offset': offset,
'limit': limit,
'include': include
}
看一下效果:
接下来我们需要调取接口的数据了,主要用到requests库,送上完整代码:
import requests
import urllib3
class ZhiHu(object):
base_url = "https://www.zhihu.com/api/v4/favlists/20351986/items"
params = {
'offset': 20,
'limit': 20,
'include': ''
}
def __init__(self, offset=20, limit=20):
self.params = {
'offset': offset,
'limit': limit,
# 'include': include
}
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
headers = {
'Content-Type': 'application/json',
'Referer': 'https://www.zhihu.com/collection/20351986',
'Host': 'www.zhihu.com',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15',
'Accept-Language': 'zh-cn',
'Connection': 'keep-alive',
'Accept': '*/*'
}
#%%
z = ZhiHu(offset='20', limit='10')
z.params
response = requests.get(url=z.base_url, params=z.params, headers=headers, verify=False)
rd = response.json()
筛选数据
最后一步,我们从API拿到的结果中把我们想要的信息提取出来。比如我们想拿到文章的标题和对应的链接两个信息,这个时候我们可以对返回的json数据进行一点简单分析:
PS:看postman的结果也是一样的
路径有点深,在content的question下面,写一个小循环把里面的内容掏出来吧:
因为数据里面不是每一条数据都有question的,所以用了一个偷懒的处理方式——异常处理。从结果来看,我们成功的拿到了接口的数据,后面你想继续爬内容还是把链接存起来就随便了,看你心情。
两个注意事项:
- 因为是https请求,直接请求会有证书认证的警告,最简单粗暴的方式就像我一样用urllib3.disable_warnings()处理一下。
-
headers必须恰当地进行定义,如果不做headers有时候我们会得到400错误,headers的定义内容可以参考浏览器的工具,依然是XHR的标头:
-
返回类型。不同的API返回类型不同,有的是text,有的是json。直接返回json的是非常nice的接口,就像我们今天遇到的这个一样,可以直接用response.json()获得dict格式的返回值,如果是text的json内容我们还需要手工进行json.loads()处理才行。那么如何确定返回类型呢?依然是从XHR的标头里面:
- 一定不要采用的参数——“Accept-Encoding: gzip, deflate, br”。gzip会对返回结果进行压缩,我们无法直接通过response的content、text或者json()获得返回的结果。