前几天在看崔庆才老师的教程,用Scrapy抓知乎用户信息,里面用到了递归。之前我写的爬虫都是将已知的固定数据的网址存到list中,然后遍历list中的网址。这次针对简书,我们使用递归来试一下。
什么是递归程序(或函数)调用自身的编程技巧称为递归( recursion)。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归的优点
1、降低问题难度
2、大大地减少了程序的代码量
3、递归的能力在于用有限的语句来定义对象的无限集合
本案例
如果看图麻烦,可先看大邓录制的视频讲解,请点击链接
关注即可获得项目源码
本案例要抓简书高质量用户的信息,如昵称、id、文章数、文集数、文字数、获得的喜欢等。我们只抓一个用户ta所关注的人,至于这个ta的粉丝,我们不抓。相对而言,ta所关注的人比ta的粉丝要质量高端大气上档次些。先从初始用户,如邓旭东HIT所关注的用户列表开始,获得邓旭东所关注的A、B、C等25个用户。
再依次抓这25个用户所关注的用户
循环前两步
下面我们先分析网址结构
网址url分析
我们发现,当我向下滚动关注列表时,网页下方会加载,且在图左下角会多出
following?page=3和following?page=4两个xhr类型文件。
将鼠标放置于following?page=3文件上方,悬浮弹出一个网址
http://www.jianshu.com/users/1562c7f16a04/following?page=3
我们鼠标点击一下following?page=3 ,点击Headers,发现request url就是
http://www.jianshu.com/users/1562c7f16a04/following?page=3
那么问题来了,page这个数字如何构建呢?
我们发现 用户的关注人数除以10,然后取整数再加1就是page的最大数。
比如邓旭东关注了25个人,那么就有page=1,page=2,page=3三个page
现在我们已经构建好了
网址有了,下面开始分析要抓的数据存放在网页源码的什么位置呢?
网页分析
首先我们要先定位多个关注用户的标签,如下图。我们要找到
- 且该ul标签的属性是
user-list。
接下来我们要定位每个用户的数据位置。属性值为info的div标签如下如图
网页解析
用户列表数据的ul标签我们使用BeautifulSoup进行定位。返回用户列表ul标签及其子孙标签
url = 'http://www.jianshu.com/users/1562c7f16a04/following?page=1'
res = requests.get(url)
bsObj = BeautifulSoup(resp, 'lxml')
user_List_Container = bsObj.findAll('ul', {'class': 'user-list'})[0]
user_List = user_List_Container.contents
user_List = [str(user) for user in user_List if user != '\n']
单个用户数据的div标签我们使用re正则表达匹配出其中的数据。
import re
# 用户ID与name的匹配规律
IdName_pattern = re.compile('(.*?)')
#用户的关注、粉丝、文章、文集的匹配规律
meta1_pattern = re.compile('关注 (\d+)粉丝 (\d+)文章 (\d+)文集 (\d+)')
# 用户写的字数,获得的喜欢的匹配模式
meta2_pattern = re.compile('写了 (\d+) 字,获得了 (\d+) 个喜欢')
for user in user_List:
Id, Name = re.findall(IdName_pattern, user)[0]
followingNum, followerNum, articleNum, articleGroupNum = re.findall(meta1_pattern, user)[0]
wordNum, likeNum = re.findall(self.meta2_pattern, user)[0]
我们把上面两部分构造成三个函数,
#发起请求
def creat_request(self, userid, page):
url = self.user_url.format(userid=userid, page=page)
resp = requests.get(url, headers=self.headers).text
return resp
#解析用户列表
def parse_response(self, resp):
bsObj = BeautifulSoup(resp, 'lxml')
user_List_Container = bsObj.findAll('ul', {'class': 'user-list'})[0]
user_List = user_List_Container.contents
user_List = [str(user) for user in user_List if user != '\n']
return user_List
#解析单个用户的信息
def parser_user_info(self, user):
Id, Name = re.findall(self.IdName_pattern, user)[0]
try:
followingNum, followerNum, articleNum, articleGroupNum = re.findall(self.meta1_pattern, user)[0]
except:
followingNum, followerNum, articleNum, articleGroupNum = ('','','','')
try:
wordNum, likeNum = re.findall(self.meta2_pattern, user)[0]
except:
wordNum, likeNum = ('','')
Content = (Id, Name, followingNum, followerNum, articleNum, articleGroupNum, wordNum, likeNum)
writer.writerow(Content)
print(Content)
return Content
下面开始构造今天的任务主体,最高潮部分。最难的地方在于对递归的解决,好在总算实现了,可能有点小问题,但是能爬的数据已经挺多的了。首先,我们要将已经访问的用户的id和following放入ID_container(是一个集合)容器中,以便后面再遇到这个id时,可以验证是否重复访问该id。
其次,用户列表解析 ,用户数据解析。将解析出的用户id和following添加到deque队列中去,待爬去
最后,对队列进行for循环,如果该队列中的元素不在ID_container中,执行函数自身。
def get_userlist(self,userid,following):
ID_container.add((userid,following))
if following != '':
num = int(following) / 10
Page = math.ceil(num) #ceil(1.8)返回2,ceil(27.8)返回28
for page in range(1, Page + 1, 1):
resp = self.creat_request(userid,page)
user_List = self.parse_response(resp)
for user in user_List:
content = self.parser_user_info(user)
Deque.append((content[0],content[2]))
time.sleep(1)
for deq in Deque:
if deq not in ID_container:
self.get_userlist(deq[0],deq[1])
print('hello world')
关注公众号:大邓带你玩转python即可获得项目源码。