分布式爬虫很难吗?用Python写一个小白也能听懂的分布式知乎爬虫

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:

oLT1QEwrG3qKhB674rStBORSD2Cb07GuPUzZMKCP


前言

很早就有采集知乎用户数据的想法,要实现这个想法,需要写一个网络爬虫(Web Spider)。因为在学习 python,正好 python 写爬虫也是极好的选择,于是就写了一个基于 python 的网络爬虫。

几个月前写了爬虫的初版,后来因为一些原因,暂时搁置了下来,最近重新拾起这个想法。首先优化了代码的结构,然后在学弟的提醒下,从多线程改成了多进程,一台机器上运行一个爬虫程序,会启动几百个子进程加速抓取。

但是一台机器的性能是有极限的,所以后来我使用 MongoDB 和 Redis 搭建了一个主从结构的分布式爬取系统,来进一步加快抓取的速度。

然后我就去好几个服务器厂商申请免费的试用,比如百度云、腾讯云、Ucloud…… 加上自己的笔记本,断断续续抓取了一个多周,才采集到300万知乎用户数据。中间还跑坏了运行网站的云主机,还好 自动备份 起作用,数据没有丢失,但那又是另外一个故事了……

废话不多说,下面我介绍一下如何写一个简单的分布式知乎爬虫。

抓取知乎用户的个人信息

给大家推荐一个学习交流的地方,想要学习Python的小伙伴可以一起来学习,719+139+688,入坑需谨慎,对Python没啥兴趣的就不要来凑热闹啦。我们要抓取知乎用户数据,首先要知道在哪个页面可以抓取到用户的数据。知乎用户的个人信息在哪里呢,当然是在用户的主页啦,我们以轮子哥为例 ~

22xYWGpZ17SDG9KWH84QY0nTkU6i=fQqhJITQh71


红框里的便我们要抓取的用户关键信息(的一部分)。

最上面是我们的目标URL:https://www.zhihu.com/people/excited-vczh/answers

观察一下这个URL的组成:

http://www.zhihu.com + /people + /excited-vczh + /answer

可以发现只有 excited-vczh 这部分是会变化的,它代表着知乎用户的唯一ID,在知乎的数据格式中,它的键名叫做 urlToken

所以我们可以用拼接字符串的形式,得到我们待抓取页面的URL:

url = '%s/people/%s/answers'%(host,urlToken)

页面URL有了,而且从上图我们可以发现 不登录 也可以访问用户主页,这说明我们可以不用考虑模拟登陆的问题,可以自由的获取用户主页面源码。

那么我们如何从用户主页的源码中获取用户的数据呢?一开始我以为需要挨个匹配页面中对应的部分,但我查看源码的时候发现知乎把用户数据集集中放到了源码的一个地方,那就是 id="data" 的 div 的 data-state 属性的值中,看下图:

XhNrjl14CEJlU9fkdpYGjSXUOgKmfIxrf=oh1Eb6


从上图我们可以发现,date-state 的属性值中藏有用户的信息,比如我们可以依次找到用户的教育经历(educations)、简介(headline)、参与的 Live 数量(participatedLiveCount)、关注的收藏夹数量(followingFavlistsCount)、被收藏的次数(favoritedCount)、关注他的用户数(followerCount)、关注的话题数量(followingTopicCount)、用户描述(description)等信息。通过观察我们也可以发现,数据应该是以 JSON 格式存储。

知道了用户数据都藏在 date-state 中,我们 用 BeautifulSoup 把该属性的值取出来,然后作为 JSON 格式读取,再把数据集中存储用户数据的部分提取出来即可,看代码:

# 解析htmls = BS(html,'html.parser')# 获得该用户藏在主页面中的json格式数据集data = s.find('div',attrs={'id':'data'})['data-state']

data = json.loads(data)

data = data['entities']['users'][urlToken]

如此,我们便得到了某一个用户的个人信息。

抓取知乎用户的关注者列表

刚刚我们讨论到可以通过抓取用户主页面源码来获取个人信息,而用户主页面可以通过拼接字符串的形式得到 URL,其中拼接的关键是 如何获取用户唯一ID —— urlToken

我采用的方法是 抓取用户的关注者列表

每个用户都会有关注者列表,比如轮子哥的:

mvTlXLCKd=Y94FZKLq1J2gNOUr0Efkx7zHs=7Tzy

请点击此处输入图片描述

OQ1O3SkxVEsnUouiAKowXpwzXNz1sspfgzcUFRSx


和获取个人信息同样的方法,我们可以在该页面源码的 date-state 属性值中找到关注他的用户(一部分):

DmTfXe1MweXyeoUZ9wNnLn91YvQuMvQPSocAU1Uq

请点击此处输入图片描述

名为 ids 的键值中存储有当前列表页的所有用户的 urlToken,默认列表的每一页显示20个用户,所以我们写一个循环便可以获取当前页该用户的所有关注者的 urlToken

# 解析当前页的 html   url = '%s/people/%s/followers?page=%d'%(host,urlToken,page)

html = c.get_html(url)

s = BS(html,'html.parser')# 获得当前页的所有关注用户data = s.find('div',attrs={'id':'data'})['data-state']

data = json.loads(data)

items = data['people']['followersByUser'][urlToken]['ids']for item in items:    if item!=None and item!=False and item!=True and item!='知乎用户'.decode('utf8'):

       node = item.encode('utf8')

       follower_list.append(node)

再写一个循环遍历关注者列表的所有页,便可以获取用户的所有关注者的 urlToken。

有了每个用户在知乎的唯一ID,我们便可以通过拼接这个ID得到每个用户的主页面URL,进一步获取到每个用户的个人信息。

我选择抓取的是用户的关注者列表,即关注这个用户的所有用户(follower)的列表,其实你也可以选择抓取用户的关注列表(following)。我希望抓取更多知乎非典型用户(潜水用户),于是选择了抓取关注者列表。当时抓取的时候有这样的担心,万一这样抓不到主流用户怎么办?毕竟很多知乎大V虽然关注者很多,但是主动关注的人相对都很少,而且关注的很可能也是大V。但事实证明,主流用户基本都抓取到了,看来基数提上来后,总有缝隙出现。

反爬虫机制

频繁抓取会被知乎封IP,也就是常说的反爬虫手段之一,不过俗话说“道高一尺,魔高一丈”,既然有反爬虫手段,那么就一定有反反爬虫手段,咳,我自己起的名……

言归正传,如果知乎封了你的IP,那么怎么办呢?很简单,换一个IP。这样的思想催生了 代理IP池 的诞生。所谓代理IP池,是一个代理IP的集合,使用代理IP可以伪装你的访问请求,让服务器以为你来自不同的机器。

于是我的 应对知乎反爬虫机制的策略 就很简单了:全力抓取知乎页面 --> 被知乎封IP --> 换代理IP --> 继续抓 --> 知乎继续封 --> 继续换 IP..... (手动斜眼)

使用 代理IP池,你可以选择用付费的服务,也可以选择自己写一个,或者选择用现成的轮子。我选择用七夜写的 qiyeboy/IPProxyPool 搭建代理池服务,部署好之后,修改了一下代码让它只保存https协议的代理IP,因为 使用http协议的IP访问知乎会被拒绝

搭建好代理池服务后,我们便可以随时在代码中获取以及使用代理 IP 来伪装我们的访问请求啦!

(其实反爬手段有很多,代理池只是其中一种)

简单的分布式架构

多线程/多进程只是最大限度的利用了单台机器的性能,如果要利用多台机器的性能,便需要分布式的支持。

如何搭建一个简单的分布式爬虫?

我采用了 主从结构,即一台主机负责调度、管理待抓取节点,多台从机负责具体的抓取工作。

具体到这个知乎爬虫来说,主机上搭建了两个数据库MongoDB 和 Redis。MongoDB 负责存储抓取到的知乎用户数据,Redis 负责维护待抓取节点集合。从机上可以运行两个不同的爬虫程序,一个是抓取用户关注者列表的爬虫(list_crawler),一个是抓取用户个人资料的爬虫(info_crawler),他们可以配合使用,但是互不影响。

我们重点讲讲主机上维护的集合,主机的 Redis 数据库中一共维护了5个集合:

  • waiting:待抓取节点集合

  • info_success:个人信息抓取成功节点集合

  • info_failed:个人信息抓取失败节点集合

  • list_success:关注列表抓取成功节点集合

  • list_failed:关注列表抓取失败节点集合

这里插一句,之所以采用集合(set),而不采用队列(queue),是因为集合天然的带有唯一性,也就是说可以加入集合的节点一定是集合中没有出现过的节点,这里在5个集合中流通的节点其实是 urlToken

(其实集合可以缩减为3个,省去失败集合,失败则重新投入原来的集合,但我为了测速所以保留了5个集合的结构)

他们的关系是:

Pr=mjKBUQToU5jllm=LcpMiPIQzWVpSs5lEU1I8E

请点击此处输入图片描述

举个具体的栗子:从一个 urlToken 在 waiting 集合中出现开始,经过一段时间,它被 info_crawler 爬虫程序从 waiting 集合中随机获取到,然后在 info_crawler 爬虫程序中抓取个人信息,如果抓取成功将个人信息存储到主机的 MongoDB 中,将该 urlToken 放到 info_success 集合中;如果抓取失败则将该 urlToken 放置到 info_failed 集合中。下一个阶段,经过一段时间后,list_crawler 爬虫程序将从 info_success 集合中随机获取到该 urlToken,然后尝试抓取该 urlToken 代表用户的关注者列表,如果关注者列表抓取成功,则将抓取到的所有关注者放入到 waiting 集合中,将该 urlToken 放到 list_success 集合中;如果抓取失败,将该 urlToken 放置到 list_failed 集合中。

如此,主机维护的数据库,配合从机的 info_crawler 和 list_crawler 爬虫程序,便可以循环起来:info_crawler 不断从 waiting 集合中获取节点,抓取个人信息,存入数据库;list_crawler 不断的补充 waiting 集合

主机和从机的关系如下图:

BuvrpbKaX6jZ=9JM0lG7TGh7o1EUInNIq=0YLlki


主机是一台外网/局域网可以访问的“服务器”,从机可以是PC/笔记本/Mac/服务器,这个架构可以部署在外网也可以部署在内网。

X7oKk70LD4bKEmXN4cX1BD583JXhSP2M4AQunNBC


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2天前
|
数据采集 存储 JSON
从零到一构建网络爬虫帝国:HTTP协议+Python requests库深度解析
在网络数据的海洋中,网络爬虫遵循HTTP协议,穿梭于互联网各处,收集宝贵信息。本文将从零开始,使用Python的requests库,深入解析HTTP协议,助你构建自己的网络爬虫帝国。首先介绍HTTP协议基础,包括请求与响应结构;然后详细介绍requests库的安装与使用,演示如何发送GET和POST请求并处理响应;最后概述爬虫构建流程及挑战,帮助你逐步掌握核心技术,畅游数据海洋。
17 3
|
2天前
|
数据采集 API 开发者
🚀告别网络爬虫小白!urllib与requests联手,Python网络请求实战全攻略
在网络的广阔世界里,Python凭借其简洁的语法和强大的库支持,成为开发网络爬虫的首选语言。本文将通过实战案例,带你探索urllib和requests两大神器的魅力。urllib作为Python内置库,虽API稍显繁琐,但有助于理解HTTP请求本质;requests则简化了请求流程,使开发者更专注于业务逻辑。从基本的网页内容抓取到处理Cookies与Session,我们将逐一剖析,助你从爬虫新手成长为高手。
16 1
|
11天前
|
数据采集 JavaScript 前端开发
构建你的首个Python网络爬虫
【9月更文挑战第8天】本文将引导你从零开始,一步步构建属于自己的Python网络爬虫。我们将通过实际的代码示例和详细的步骤解释,让你理解网络爬虫的工作原理,并学会如何使用Python编写简单的网络爬虫。无论你是编程新手还是有一定基础的开发者,这篇文章都将为你打开网络数据获取的新世界。
|
11天前
|
数据采集 机器学习/深度学习 搜索推荐
Python爬虫技术基础与应用场景详解
本文介绍了爬虫技术的基本概念、原理及应用场景,包括数据收集、价格监测、竞品分析和搜索引擎优化等。通过一个实战案例展示了如何使用Python爬取电商网站的商品信息。强调了在使用爬虫技术时需遵守法律法规和道德规范,确保数据抓取的合法性和合规性。
|
14天前
|
数据采集 JavaScript 前端开发
打造你的Python爬虫:从基础到进阶
【9月更文挑战第5天】在数字信息泛滥的时代,掌握一项技能能让我们更好地筛选和利用这些资源。本文将带你了解如何用Python构建一个基本的网页爬虫,进而拓展到更复杂的数据抓取任务。无论你是编程新手还是有一定经验的开发者,跟随这篇文章的步伐,你将能够实现自动化获取网络数据的目标。准备好了吗?让我们一起潜入代码的世界,解锁新的可能!
WK
|
18天前
|
数据采集 XML 安全
常用的Python网络爬虫库有哪些?
Python网络爬虫库种类丰富,各具特色。`requests` 和 `urllib` 简化了 HTTP 请求,`urllib3` 提供了线程安全的连接池,`httplib2` 则具备全面的客户端接口。异步库 `aiohttp` 可大幅提升数据抓取效率。
WK
36 1
|
19天前
|
数据采集 JavaScript 前端开发
构建简易Python爬虫:抓取网页数据入门指南
【8月更文挑战第31天】在数字信息的时代,数据抓取成为获取网络资源的重要手段。本文将引导你通过Python编写一个简单的网页爬虫,从零基础到实现数据抓取的全过程。我们将一起探索如何利用Python的requests库进行网络请求,使用BeautifulSoup库解析HTML文档,并最终提取出有价值的数据。无论你是编程新手还是有一定基础的开发者,这篇文章都将为你打开数据抓取的大门。
|
19天前
|
数据采集 存储 JavaScript
Python 爬虫实战:从入门到精通
【8月更文挑战第31天】 本文将带你走进 Python 爬虫的世界,从基础的请求和解析开始,逐步深入到反爬策略的应对和数据存储。我们将通过实际案例,一步步构建一个功能完整的爬虫项目。无论你是编程新手还是有一定经验的开发者,都能在这篇文章中找到适合自己的学习路径。让我们一起探索数据的海洋,揭开网络信息的神秘面纱。
|
20天前
|
数据采集 存储 JavaScript
Python 爬虫实战:从入门到精通
【8月更文挑战第31天】 本文将带你走进 Python 爬虫的世界,从基础的请求和解析开始,逐步深入到反爬策略的应对和数据存储。我们将通过实际案例,一步步构建一个功能完整的爬虫项目。无论你是编程新手还是有一定经验的开发者,都能在这篇文章中找到适合自己的学习路径。让我们一起探索数据的海洋,揭开网络信息的神秘面纱。