现在很多网站用的是动态网页加载技术,这时候用前面的request库和BS4库就不能解决问题了,需要用新的办法。
打开网页,按F12或者右键弹出菜单里选择“检查”,右侧会打开开发者工具。
这里有一排菜单,最左边的是Element,显示的是网页的源代码,如果在这里能直接找到所需要爬取的内容,就说明这是静态页面,可以用 request库和BeautifulSoup4库的工具爬取所需内容。如果这里找不到所需内容,那么就是动态页面。这时候往右面看Network菜单。
Network
的功能是:记录在当前页面上发生的所有请求。现在看上去好像空空如也的样子,这是因为Network
记录的是实时网络请求。现在网页都已经加载完成,所以不会有东西。
右侧勾选框Preserve log
,它的作用是“保留请求日志”。如果不点击这个,当发生页面跳转的时候,记录就会被清空。所以,我们在爬取一些会发生跳转的网页时,会点亮它。
然后刷新页面,这时候就会跳出来很多记录。
找到这个页面的第0个请求:search.html
,然后点击它,我们来查看它的Response
(官方翻译叫“响应”,你可以理解为服务器对浏览器这个请求的回应内容,即请求的结果)。
下面第1行,是对请求进行分类查看。我们最常用的是:ALL(查看全部)/XHR(仅查看XHR)/Doc(Document,第0个请求一般在这里),有时候也会看看:Img(仅查看图片)/Media(仅查看媒体文件)/Other(其他)。最后,JS和CSS,则是前端代码,负责发起请求和页面实现;Font是文字的字体;而理解WS和Manifest,需要网络编程的知识,倘若不是专门做这个,不需要了解。
在Network
中,有一类非常重要的请求叫做XHR
(当你把鼠标在XHR上悬停,你可以看到它的完整表述是XHR and Fetch)
AJAX技术在工作的时候,会创建一个XHR
(或是Fetch)对象,然后利用XHR
对象来实现,服务器和浏览器之间传输数据。在这里,XHR
和Fetch
并没有本质区别,只是Fetch
出现得比XHR
更晚一些,所以对一些开发人员来说会更好用,但作用都是一样的。
在XHR列表中,找到带有我们所需要数据的一个(这个要靠经验了,可以一个个点开看,也可以观察英文名)
点开XHR,看到如下列表
从左往右分别是:Headers
:标头(请求信息)、Preview
:预览、Response
:响应、Cookies
:Cookies、Timing
:时间。
点开Headers
General
里的Requests URL
就是我们应该去访问的链接。如果在浏览器中打开这个链接,就会得到一个类似字典的数据结构,它是JSON数据。
json
数据可以跨平台,跨语言工作。json
和XHR
之间的关系:XHR
用于传输数据,它能传输很多种数据,json
是被传输的一种数据格式。
我们用requests库的json方法可以解析这一数据。
# 引用requests库
import requests
# 调用get方法,下载这个字典
res = requests.get(url)
# 使用json()方法,将response对象,转为列表/字典
json = res.json()
#遍历字典,获取所需资料
for i in json:
for j in i:
print(j)
带参数请求
每个url
都由两部分组成。前半部分大多形如:https://xx.xx.xxx/xxx/xxx
后半部分,多形如:xx=xx&xx=xxx&xxxxx=xx&……
两部分使用?
来连接。举例豆瓣网址,前半部分就是:搜索:
后半部分则是:q=%E6%B5%B7%E8%BE%B9%E7%9A%84%E5%8D%A1%E5%A4%AB%E5%8D%A1
它们的中间使用了?
来隔开。
这前半部分是我们所请求的地址,它告诉服务器,我想访问这里。而后半部分,就是我们的请求所附带的参数,它会告诉服务器,我们想要什么样的数据。
这参数的结构,会和字典很像,有键有值,键值用=连接;每组键值之间,使用&来连接。
就像豆瓣。我们请求的地址是搜索: 而我们的请求所附带的参数是“海边的卡夫卡”:q=%E6%B5%B7%E8%BE%B9%E7%9A%84%E5%8D%A1%E5%A4%AB%E5%8D%A1
(那段你看不懂的代码,它是“海边的卡夫卡”使用utf-8编码的结果)。
requests
模块里的requests.get()
提供了一个参数叫params
,可以让我们用字典的形式,把参数传进去。
我们可以把Query String Parameters
里的内容,直接复制下来,封装为一个字典,传递给params
。只是有一点要特别注意:要给他们打引号,让它们变字符串。
通过改变params里的参数,可以找到自己想要的页面,如有的翻页会写成 "page= ",有的人名、作品名等等都能找到相应的参数,需要仔细分析
请求头 Request Headers
每一个请求,都会有一个Request Headers
,我们把它称作请求头。它里面会有一些关于该请求的基本信息,比如:这个请求是从什么设备什么浏览器上发出?这个请求是从哪个页面跳转而来?
如上图,user-agent
(中文:用户代理)会记录你电脑的信息和浏览器版本(如我的,就是windows10的64位操作系统,使用谷歌浏览器)。
origin
(中文:源头)和referer
(中文:引用来源)则记录了这个请求,最初的起源是来自哪个页面。它们的区别是referer
会比origin
携带的信息更多些。
如果我们想告知服务器,我们不是爬虫,而是一个正常的浏览器,就要去修改user-agent
。倘若不修改,那么这里的默认值就会是Python,会被服务器认出来。
有趣的是,像百度的爬虫,它的user-agent
就会是Baiduspider
,谷歌的也会是Googlebot
……如是种种。
Requests模块允许我们去修改Headers的值。
如此,只需要封装一个字典就好了。和写params非常相像。
而修改origin
或referer
也和此类似,一并作为字典写入headers就好。就像这样:
headers = {
'origin':'https://china.nba.com',
# 请求来源
'referer':'https://china.nba.com',
# 请求来源,携带的信息比“origin”更丰富
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
# 标记了请求从什么设备,什么浏览器上发出
}
最后分享一个案例,爬取NBA网站上现役球员的资料
import requests,openpyxl
wb=openpyxl.Workbook()
sheet=wb.active
sheet.title='NBA现役球员'
sheet['A1']='姓名'
sheet['B1']='国籍'
sheet['C1']='身高'
sheet['D1']='体重'
sheet['E1']='场上位置'
sheet['F1']='所属球队'
sheet['G1']='出道年份'
sheet['H1']='来源'
url = 'https://china.nba.com/static/data/league/playerlist.json'
headers = {
'origin':'https://china.nba.com',
# 请求来源,本案例中其实是不需要加这个参数的,只是为了演示
'referer':'https://china.nba.com',
# 请求来源,携带的信息比“origin”更丰富,本案例中其实是不需要加这个参数的,只是为了演示
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
# 标记了请求从什么设备,什么浏览器上发出
}
res = requests.get(url,headers=headers)
playerlist=res.json()
players=playerlist['payload']['players']
print(len(players))
for player in players:
#print('姓名:'+player['playerProfile']['displayName'])
#print('国籍:'+player['playerProfile']['country'])
#print('身高:'+player['playerProfile']['height'])
#print('体重:'+player['playerProfile']['weight'])
#print('位置:'+player['playerProfile']['position'])
#print('球队:'+player['teamProfile']['city']+player['teamProfile']['displayAbbr'])
#print('出道:'+player['playerProfile']['draftYear'])
#print('来源:'+player['playerProfile']['schoolType'])
#print()
name=player['playerProfile']['displayName']
country=player['playerProfile']['country']
high=player['playerProfile']['height']
weight=player['playerProfile']['weight']
position=player['playerProfile']['position']
team=player['teamProfile']['city']+player['teamProfile']['displayAbbr']
draftYear=player['playerProfile']['draftYear']
laiyuan=player['playerProfile']['schoolType']
sheet.append([name,country,high,weight,position,team,draftYear,laiyuan])
wb.save('NBA现役球员.xlsx')