你造吗,今天是个大喜的日子!来到这儿,就意味着你爬虫已经入门啦!
在这个重要又喜悦的日子里,我们就干三件事:回顾前路、项目实操、展望未来。
回顾前路,是为了复习0-6关所学的知识。项目实操,是通过写一个爬虫程序把所学的知识用起来。展望未来,是预告一下我们之后会遇到的风景。
马上开始吧~
回顾前路
在前面,我们按关卡学了好多好多知识。而这么多的内容,我们用【项目实现】和【知识地图】两张图就能说清。
【项目实现】: 任何完成项目的过程,都是由以下三步构成的。
先需要明确自己的目标是什么,然后分析一下如何实现这个目标,最后就可以去写代码了。
当然,这不是一个线性的过程,而可能出现“代码实现”碰壁后然后折返“分析过程”,再“代码实现”的情形。
接下来是【知识地图】:前面6关所讲的爬虫原理,在本质上,是一个我们所操作的对象在不断转换的过程。
总体上来说,从Response对象开始,我们就分成了两条路径,一条路径是数据放在HTML里,所以我们用BeautifulSoup库去解析数据和提取数据;另一条,数据作为Json存储起来,所以我们用response.json()方法去解析,然后提取、存储数据。
你需要根据具体的情况,来确定自己应该选择哪一条路径。
也可以参考图片上的注释,帮助自己去回忆不同对象的方法和属性。不过,老师还是希望你能把这个图记在心里。
好啦,0-6关的内容就梳理完成啦~
接下来是项目实操,这是今天的重头戏。
项目实操
我们会按照刚刚提过的项目三步骤来,首先是明确目标。
0. 明确目标
为了让大家能把前面所学全都复习一遍,老师为大家准备了一个项目:爬取知乎大v张佳玮的文章“标题”、“摘要”、“链接”,并存储到本地文件。
之所以选这个项目,是为了让大家能把前面所学全都复习一遍。
知道目标后,就可以【分析过程】了。
1. 分析过程
张佳玮的知乎文章URL在这里:https://www.zhihu.com/people/zhang-jia-wei/posts?page=1。(为了尽可能让你看到的内容和我一致,我选择了按点赞数给文章排序)
这是我做这个项目时,网站显示的内容。
点击右键——检查——Network,选All(而非XHR),然后刷新网页,点进去第0个请求:posts_by_votes,点Preview`。
发现有文章标题,看来数据是放在HTML里。
那么,走的应该是【知识地图】里上面那条路径:
那好,就可以去观察一下网页源代码了,我们点回Elements。
发现文章标题对应的就是元素里面的文本“大概,他们就是世上活得最明白的人吧。”还可以看到在标签里面,还有属性target="_blank",和data-za-detail-view-element_name=“Title”。
对于target="_blank"的属性,我们按下command+f(windows电脑是ctrl+f)去搜索target:
结果发现这个target属性并不能帮我们精准地定位到文章标题,所以排除使用这个属性。
而对于另一个属性data-za-detail-view-element_name=“Title”,你看这个属性命名如此freestyle,应该是知乎的开发人员自定义命名的,并不是html语法里标准的属性名。所以,我们不推荐使用。
好,让我们再定睛一看:
你会发现,这个<a标签>的上一个层级是<h2标签>,并且有class=“ContentItem-title”。仍然用command+f搜索“ContentItem-title”,发现这个属性可以帮我们精准定位目标数据,可以用。
到这里,我们的思路也差不多清晰起来。
获取数据——用requests库;解析数据——用BeautifulSoup库;提取数据——用BeautifulSoup里的find_all(),翻页的话观察第一页,到最后一页的网址特征,写循环;存储数据——用csv和openpyxl都可以。基本思路就应该是这样。
好,这就结束了分析过程,接下来就是敲代码啦。
2.代码实现
别一上来就整个大的,我们先试着拿一页的文章标题,看看能不能成功。
请阅读下面的代码,然后点击运行:
import requests
from bs4 import BeautifulSoup
#引入requests和bs
url='https://www.zhihu.com/people/zhang-jia-wei/posts/posts_by_votes?page=1'
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#使用headers是一种默认的习惯,默认你已经掌握啦~
res=requests.get(url,headers=headers)
#用resquest模块发起请求,将响应的结果赋值给变量res。
print(res.status_code)
#检查状态码
发现状态码显示200,表示请求成功,放心了,可以继续往下。
请阅读下面的代码,然后点击运行:
import requests
from bs4 import BeautifulSoup
#引入request和bs
url='https://www.zhihu.com/people/zhang-jia-wei/posts/posts_by_votes?page=1'
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#使用headers是一种默认的习惯,默认你已经掌握啦~
res=requests.get(url,headers=headers)
#发起请求,将响应的结果赋值给变量res。
print(res.status_code)
#检查状态码
bstitle=BeautifulSoup(res.text,'html.parser')
#用bs进行解析
title=bstitle.find_all(class_='ContentItem-title')
#提取我们想要的标签和里面的内容
print(title)
#打印title
见鬼了,为啥只有两个文章标题?
看起来是碰壁了,但遇上问题别慌,先来做个排查,使用 res.text 打印一下网页源代码。请阅读下面的代码,然后点击运行。
import requests
from bs4 import BeautifulSoup
#引入request和bs
url='https://www.zhihu.com/people/zhang-jia-wei/posts/posts_by_votes?page=1'
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#使用headers是一种默认的习惯,默认你已经掌握啦~
res=requests.get(url,headers=headers)
#发起请求,将响应的结果赋值给变量res。
print(res.status_code)
#检查状态码
print(res.text)
#打印网页源代码
打出来之后,我们把第一页的最后一篇文章的标题“出走半生,关山万里,归来仍是少女心气”在这个网页源代码里面搜索,呵呵,是搜不到的。🙃
再回过头看看:
原来,在HTML里面只放了两篇文章的数据,刚刚一不留神,就没注意。
这时,我们需要重新思考,完整的数据是放在哪里的?哎呀,其实我们现在已经回到分析过程了。
1.重新分析
我们打开Network,点开XHR,同时刷新页面,看到出现了很多个请求。
浏览一下,看到两个带articles的请求,感觉有戏。点开首个articles看看preview,一层层点开,看到“title:记住就是一切”,猜测这是一个文章标题。
在网页里面用command+f(windows电脑用ctrl+f)搜索一下“记住就是一切”,发现搜不到,奇怪。
那就看看跟首个articles请求长得很像的另一个articles的请求好啦,仍然看preview,看到title: “国产航母下水……让我想到李鸿章和北洋舰队”,仍然在网页里搜一下:
哎鸭!果然在这里。看来这个articles的请求里面存的是第一页的文章标题。这下妥了,我们知道向哪个url获取数据了。
那首个带articles的请求是什么?其实这是知乎的网站设计,当你刷新第一页的时候,默认你也请求了第二页的文章数据,这样你加载就会比较流畅。
通过同样的方法,我们还可以发现文章的链接、文章的摘要也是一样放在articles的请求里的,这里就不赘述了。
现在,理论上我们可以拿到第一页的文章数据了,那如果要拿到之后所有页面的数据,还不够吧。
好,我们去观察第1页对第2页的请求,和第2页里对第3页请求的参数区别,是在headers里面的query string parameters里面。
然后发现除了offset都一样,offset代表起始值,limit表示加载的限制数,通过循环我们是可以爬到所有页数的内容了。
我们的大致思路也就出来了。
好,分析过程搞定后,就又是写代码啦~
2.代码实现
我们可以参考上面的知识地图,一步一步来写,但老师认为,走到现在的你,应该是不用完全依赖这个图了。
接下来,想请你来写代码。不过我们不可能一下子写完,所以先请你用requests.get()获取数据,然后检查请求是否成功。
我们会用requests.get()来获取数据,但我们要先找到数据所在的具体请求的链接(Request URL);
封装headers,请在Headers面板中搜索user-agent,找到user-agent的信息;
封装params,请去Query String Parameters中复制信息;
然后再使用requests.get()发送请求,并使用response.status_code来检查请求的状态。
import requests
#引入requests
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#封装headers
url='https://www.zhihu.com/api/v4/members/zhang-jia-wei/articles?'
#写入网址
params={
'include':'data[*].comment_count,suggest_edit,is_normal,thumbnail_extra_info,thumbnail,can_comment,comment_permission,admin_closed_comment,content,voteup_count,created,updated,upvoted_followees,voting,review_info,is_labeled,label_info;data[*].author.badge[?(type=best_answerer)].topics',
'offset':'10',
'limit':'20',
'sort_by':'voteups',
}
#封装参数
res=requests.get(url,headers=headers,params=params)
#发送请求,并把响应内容赋值到变量res里面
print(res.status_code)
#确认请求成功
终端显示200,请求成功,妥了。下一步是解析json数据。
请你思考一下,然后点击阅读代码,再点击运行。
import requests
#引入requests
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#封装headers
url='https://www.zhihu.com/api/v4/members/zhang-jia-wei/articles?'
#写入网址
params={
'include':'data[*].comment_count,suggest_edit,is_normal,thumbnail_extra_info,thumbnail,can_comment,comment_permission,admin_closed_comment,content,voteup_count,created,updated,upvoted_followees,voting,review_info,is_labeled,label_info;data[*].author.badge[?(type=best_answerer)].topics',
'offset':'10',
'limit':'20',
'sort_by':'voteups',
}
#封装参数
res=requests.get(url,headers=headers,params=params)
#发送请求,并把响应内容赋值到变量res里面
print(res.status_code)
#确认请求成功
articles=res.json()
#用json()方法去解析response对象,并赋值到变量articles上面。
print(articles)
#打印这个json文件
发现没问题,接下来就是要根据json数据结构来提取我们想要的文章标题数据了。
我们在preview里面一层层看看这个json文件,到底是怎么一个结构。下面三张图依次是从外到内的数据展示。
最外层是一个很大的字典,里面有两大元素,data:和paging:,这两大元素又是键值对应的字典形式,data这个键所对应的值是一个列表,里面有10元素,每个元素又是字典形式。
这样看其实还不够直观,为了更清晰地定位到目标数据,我们把这三张图合二为一:
最外层是个字典,在键为data的值里面,又有一个列表,而列表的每个元素又是一个字典,在第0个元素的值里面,又有一个字典,这里面终于有了我们要拿的title: “国产航母下水……让我想到李鸿章和北洋舰队”。
那就可以一层层地取数据了,我们又要接着来写代码了,(≧▽≦)/,我们只要先拿第一页的所有文章的标题即可。
import requests
#引入requests
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#封装headers
url='https://www.zhihu.com/api/v4/members/zhang-jia-wei/articles?'
#写入网址
params={
'include':'data[*].comment_count,suggest_edit,is_normal,thumbnail_extra_info,thumbnail,can_comment,comment_permission,admin_closed_comment,content,voteup_count,created,updated,upvoted_followees,voting,review_info,is_labeled,label_info;data[*].author.badge[?(type=best_answerer)].topics',
'offset':'10',
'limit':'20',
'sort_by':'voteups',
}
#封装参数
res=requests.get(url,headers=headers,params=params)
#发送请求,并把响应内容赋值到变量res里面
print(res.status_code)
#确认这个response对象状态正确
articles=res.json()
#用response.json()方法去解析response对象,并赋值到变量articles上面,此时的articles是一个
print(articles)
#打印这个json文件
以下,是老师的参考答案:
import requests
#引入requests
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#封装headers
url='https://www.zhihu.com/api/v4/members/zhang-jia-wei/articles?'
#写入网址
params={
'include':'data[*].comment_count,suggest_edit,is_normal,thumbnail_extra_info,thumbnail,can_comment,comment_permission,admin_closed_comment,content,voteup_count,created,updated,upvoted_followees,voting,review_info,is_labeled,label_info;data[*].author.badge[?(type=best_answerer)].topics',
'offset':'10',
'limit':'20',
'sort_by':'voteups',
}
#封装参数
res=requests.get(url,headers=headers,params=params)
#发送请求,并把响应内容赋值到变量res里面
print(res.status_code)
#确认这个response对象状态正确
articles=res.json()
#用json()方法去解析response对象,并赋值到变量articles上面,此时的articles是一个
print(articles)
#打印这个json文件
data=articles['data']
#取出键为data的值。
for i in data:
print(i['title'])
#遍历列表,拿到的是列表里的每一个元素,这些元素都是字典,再通过键把值取出来
通过同样的方法,我们也可以定位到文章链接和文章摘要的数据。
import requests
#引入requests
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
#封装headers
url='https://www.zhihu.com/api/v4/members/zhang-jia-wei/articles?'
#写入网址
params={
'include':'data[*].comment_count,suggest_edit,is_normal,thumbnail_extra_info,thumbnail,can_comment,comment_permission,admin_closed_comment,content,voteup_count,created,updated,upvoted_followees,voting,review_info,is_labeled,label_info;data[*].author.badge[?(type=best_answerer)].topics',
'offset':'10',
'limit':'20',
'sort_by':'voteups',
}
#封装参数
res=requests.get(url,headers=headers,params=params)
#发送请求,并把响应内容赋值到变量res里面
print(res.status_code)
#确认这个Response对象状态正确
articles=res.json()
#用response.json()方法去解析数据,并赋值到变量articles上面,此时的articles是一个
print(articles)
#打印这个json文件
data=articles['data']
#取出键为data的值。
for i in data:
print(i['title'])
print(i['url'])
print(i['excerpt'])
#遍历列表,拿到的是列表里的每一个元素,这些元素都是字典,再通过键把值取出来
那一页的数据,我们就拿到了,接下来是要去拿所有页面的数据。
我们在上一步已经分析过了,第一页和第二页的请求的参数区别在于offset,由此可以写一个循环。
那么如何结束这个循环呢?
这里老师有一个经验之谈,我们来看看第一页和最后一页请求的参数区别:
对比一下,你会发第一页的is_end是显示false,最后一页的is_end是显示true,这个元素可以帮我们结束循环。
至于那个totals: 919元素,我算了一下页码和每页的文章数,判断这是文章的总数,也同样可以作为结束循环的条件。两个元素都可以用,在这里我们用is_end元素。
理论上,我们就可以拿到这些数据了。但是在这里,我们并不建议全部爬取46页数据。大家想想,参加课程的同学有很多,每个人都去爬这46页的话,如此高频访问会给对方的服务器造成相当大的压力,而我们并不真正需要这么多的数据,只是为了学习爬虫。
所以,我们的循环会设置为爬两页数据就停止。代码如下:
import requests
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
url='https://www.zhihu.com/api/v4/members/zhang-jia-wei/articles?'
articlelist=[]
#建立一个空列表,以待写入数据
offset=0
#设置offset的起始值为0
while True:
params={
'include':'data[*].comment_count,suggest_edit,is_normal,thumbnail_extra_info,thumbnail,can_comment,comment_permission,admin_closed_comment,content,voteup_count,created,updated,upvoted_followees,voting,review_info,is_labeled,label_info;data[*].author.badge[?(type=best_answerer)].topics',
'offset':str(offset),
'limit':'20',
'sort_by':'voteups',
}
#封装参数
res=requests.get(url,headers=headers,params=params)
#发送请求,并把响应内容赋值到变量res里面
articles=res.json()
# print(articles)
data=articles['data']
#定位数据
for i in data:
list1=[i['title'],i['url'],i['excerpt']]
#把数据封装成列表
articlelist.append(list1)
offset=offset+20
#在while循环内部,offset的值每次增加20
if offset>40:
break
#如果offset大于40,即爬了两页,就停止
#if articles['paging']['is_end'] == True:
#如果键is_end所对应的值是True,就结束while循环。
#break
print(articlelist)
#打印看看
好啦,你读完这段代码就该你去写啦:
获取、解析、提取数据都完成了。好,接下来就是存储数据了。
在我的想象中,最终的数据最好存成下面这样简明清晰的表格:
csv和openpyxl都可以做到,但在这里我们会选取csv,因为上一关的案例是用的openpyxl,那我们今天就来试试csv。
上一关,我们学的csv最主要的功能就是可以把列表按行写入。
反推回来,也就是我们如果可以把数据写成list=[title,url,excerpt]的样子,那就可以直接写入啦。
刚刚我们爬取到的数据也是这样的一个列表,那么我们就知道如何写代码了。
import requests
import csv
#引用csv。
csv_file=open('articles.csv','w',newline='',encoding='utf-8')
#调用open()函数打开csv文件,传入参数:文件名“articles.csv”、写入模式“w”、newline=''。
writer = csv.writer(csv_file)
# 用csv.writer()函数创建一个writer对象。
list2=['标题','链接','摘要']
#创建一个列表
writer.writerow(list2)
#调用writer对象的writerow()方法,可以在csv文件里写入一行文字 “标题”和“链接”和"摘要"。
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
url='https://www.zhihu.com/api/v4/members/zhang-jia-wei/articles?'
offset=0
#设置offset的起始值为0
while True:
params={
'include':'data[*].comment_count,suggest_edit,is_normal,thumbnail_extra_info,thumbnail,can_comment,comment_permission,admin_closed_comment,content,voteup_count,created,updated,upvoted_followees,voting,review_info,is_labeled,label_info;data[*].author.badge[?(type=best_answerer)].topics',
'offset':str(offset),
'limit':'20',
'sort_by':'voteups',
}
#封装参数
res=requests.get(url,headers=headers,params=params)
#发送请求,并把响应内容赋值到变量res里面
articles=res.json()
print(articles)
data=articles['data']
#定位数据
for i in data:
list1=[i['title'],i['url'],i['excerpt']]
#把目标数据封装成一个列表
writer.writerow(list1)
#调用writerow()方法,把列表list1的内容写入
offset=offset+20
#在while循环内部,offset的值每次增加20
if offset > 40:
break
csv_file.close()
#写入完成后,关闭文件就大功告成
print('okay')
我们的目标:续写代码,请把获取到的两页的文章标题、链接、摘要这些数据,使用csv保存到本地。
csv写入文件的步骤如下:
import csv
csv_file=open('demo.csv','w',newline='')
writer = csv.writer(csv_file)
writer.writerow(['电影','豆瓣评分'])
csv_file.close()
import requests
import csv
#引用csv。
csv_file=open('articles.csv','w',newline='',encoding='utf-8')
#调用open()函数打开csv文件,传入参数:文件名“articles.csv”、写入模式“w”、newline=''。
writer = csv.writer(csv_file)
# 用csv.writer()函数创建一个writer对象。
list2=['标题','链接','摘要']
#创建一个列表
writer.writerow(list2)
#调用writer对象的writerow()方法,可以在csv文件里写入一行文字 “标题”和“链接”和"摘要"。
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
url='https://www.zhihu.com/api/v4/members/zhang-jia-wei/articles?'
offset=0
#设置offset的起始值为0
while True:
params={
'include':'data[*].comment_count,suggest_edit,is_normal,thumbnail_extra_info,thumbnail,can_comment,comment_permission,admin_closed_comment,content,voteup_count,created,updated,upvoted_followees,voting,review_info,is_labeled,label_info;data[*].author.badge[?(type=best_answerer)].topics',
'offset':str(offset),
'limit':'20',
'sort_by':'voteups',
}
#封装参数
res=requests.get(url,headers=headers,params=params)
#发送请求,并把响应内容赋值到变量res里面
articles=res.json()
print(articles)
data=articles['data']
#定位数据
for i in data:
list1=[i['title'],i['url'],i['excerpt']]
#把目标数据封装成一个列表
writer.writerow(list1)
#调用writerow()方法,把列表list1的内容写入
offset=offset+20
#在while循环内部,offset的值每次增加20
if offset > 40:
break
csv_file.close()
#写入完成后,关闭文件就大功告成
print('okay')
展望未来
来到这里,你的爬虫就已经入门了!!!
我能看到大家在这段时间里认真上课,努力写代码的样子,也能看到大家对代码抓耳挠腮,百思不得其解的样子,还能看到你每一个豁然开朗、代码跑通后开心的样子。
接下来的关卡,你会学习更厉害的爬虫技能,以面对更多更复杂的情况。
第8关是运用cookies让浏览器记住我们,第9关是用selenium控制浏览器,第10关是让爬虫程序能定时向我们汇报结果,第11-14关是运用协程和scrapy框架来帮我们提速,并且可以爬取海量的数据,第15关是复习。
你仍然会遇到很多的问题,但我知道,现在的你已经有更强的能力、和更大的信心去解决更难更多的问题了 !
来源:CSDN
作者:BR_Chen
链接:https://blog.csdn.net/Theo93/article/details/104199649