本文由博主最初发布于华为开发者社区,原地址:https://portal.huaweicloud.com/blogs/46c0ffaad0ee11e7b8317ca23e93a891,再次感谢华为对文章的认可与鼓励 😂
爬虫是时下十分热门的一种程序,谷歌、百度等搜索引擎以及今日头条、即刻等热门应用均建立在爬虫程序的基础上,构成互联网巨大流量的入口。那么现代的爬虫是如何工作,我们自己又如何借助华为云服务搭建自己的爬虫呢?下面我们以爬取华为开发者社区所有的博客为例,利用时下热门的 PySpider 框架快速搭建一个基本的爬虫服务。
爬虫的基本原理
如果把互联网看作是各个站点相互引用、串联形成的一张网,那爬虫顾名思义就是在这张网上穿梭的蜘蛛(Spider)。一个页面作为互联网中的一个节点,很可能会包含有指向其他页面的链接,可以理解为节点之间的连线。爬虫通过遍历节点之间的连线,确定页面之间的网络结构,抓取所需要的信息。
在开始之前,我们应该清楚一些基本的概念:
WWW (World Wide Web, 万维网) 是一个由互相连接的超文本页面组成的系统
每个页面有对应的 URL(Uniform Resource Locator, 网址) 所标记
页面通过 HTTP (Hypertext Transfer Protocol, 超文本传输协议) 传输
网页使用 HTML(HyperText Markup Language, 超文本标记语言) 表示其页面结构
那么具体而言,爬虫程序的运行过程是:
寻找包含所需信息的页面 URL
通过 HTTP 获取页面
从 HTML 中解析出信息
从中发现更多包含所需信息的 URL,跳转到步骤2
在实现层面上,爬虫程序运行之初,需要提供给它一个 URL 列表,引导爬虫从这些页面开始访问。当爬虫访问这些 URL 时,会识别出页面中的所需的 URL,并将这些 URL 加入到待访问的 URL 列表中。URL 列表中的地址会以一系列策略递归地被爬虫所访问,爬虫会记录这些地址所对应的页面以及一些页面信息。
PySpider 简介
PySpider 是一个基于 Python 开发的强大爬虫系统,它具有以下特点:
用户使用 Python 编写脚本以控制爬虫的工作流程
包含基于 WebUI 的可视化脚本编辑器、任务监视器、项目管理器及结果预览工具
可以使用 MySQL、MongoDB、Redis、SQLite、ElasticSearch、PostgreSQL 等工具结合 SQLAlchemy 作为存储后端
可以使用 RabbitMQ、Beanstalk、Redis 和 Kombu 作为消息队列
支持任务优先级、任务重试、周期任务、根据页面寿命自动重爬等高级功能
分布式架构,支持 js 解析,支持 Python2 与 Python 3
总结一下,利用 PySpider,可以方便地搭建出专业的爬虫框架。
实战操作
1. 配置华为云服务
作为一个专业稳定的爬虫服务,而不是简单批量下载页面的工具,我们往往需要搭建一个服务器,来让它自动地、定期地去工作,这时一个稳定可信赖的云服务就至关重要了,我们这里就以业界领先的华为云服务为例,搭建爬虫的基础运行环境。
我们首先需要在 https://console.huaweicloud.com/ecm/?locale=zh-cn#/ecs/createVm 申请一个台云服务器。
我的服务器配置为
这里我选择了个人更偏好的 CentOS 7.3 系统,下面就以 CentOS 7.3 系统为例,继续接下来的搭建。
2. 环境准备
在安装 PySpider 前,我们可以做一些环境准备,来提升服务的稳定性,这也是在搭建服务时的好习惯。
首先更新 yum,这步会比较漫长
yum update -y
安装 EPEL(Extra Packages for Enterprise Linux),用以安装下面需要的包
yum install epel-release
安装依赖库
yum install python-pip python-devel libxml2-devel python-lxml libxslt-devel openssl-devel -y
升级 pip
pip install --upgrade pip
至此,运行环境及必需的依赖已升级至最新版本。下面我们根据喜好进行一些可选的配置,首先安装 MariaDB 作为爬虫的后端数据库。
yum install mariadb-server mariadb -y
启动 MariaDB 服务
systemctl start mariadb
如果有需要,可以设置数据库 root 用户的密码(your_password 应替换为你的密码),当然也可以不设置:
mysqladmin -u root password "your_password"
这时可以使用如下命令来检查 MariaDB 是否配置成功(如果未设置密码,直接使用 mysql
即可):
mysql -u root -p
然后输入刚才设置的密码,如果没有问题,应该可以看到以 MariaDB [(none)]>
的提示。
此时输入 SHOW DATABASES;
语句,查看所有数据库,如果有类似下面的输出,则配置正常。
MariaDB [(none)]> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test |
+--------------------+
4 rows in set (0.00 sec)
输入 exit
退出,进入下一步。
接下来,安装 Redis,作为消息队列使用。
yum install redis -y
在启动 Redis 之前,需要做一些设置,让 Redis 作为一个服务运行。首先创建一份配置文件:
mkdir -p /etc/redis
cp /etc/redis.conf /etc/redis/redis.conf
修改 /etc/redis/redis.conf ,将其中的 daemonize
项修改为 yes
:
daemonize yes
配置文件路径作为参数,启动 Redis 服务:
redis-server /etc/redis/redis.conf
3. 部署PySpider
在安装 PySpider 之前,需要先安装 mysql-connector 及 redis 两个依赖,而最新的 mysql-connector 2.2 有 protobuf 的依赖,protobuf 笔者目前还没找到合适的安装方法,所以这里退而求其次,安装不依赖 protobuf 的 mysql-connector 2.1.6
pip install mysql-connector==2.1.6 redis
安装 PySpider
pip install pyspider
创建 PySpider 配置目录
mkdir /etc/pyspider
在 /etc/pyspider
目录下创建 pyspider.conf.json 文件,以下可作为参考:
{
"taskdb": "mysql+taskdb://root:your_password@localhost:3306/taskdb",
"projectdb": "mysql+projectdb://root:your_password@localhost:3306/projectdb",
"resultdb": "mysql+resultdb://root:your_password@localhost:3306/resultdb",
"message_queue": "redis://localhost:6379/db",
"webui": {
"username": "sunny",
"password": "your_user_password",
"need-auth": true
}
}
其中 taskdb
, projectdb
和 resultdb
分别为任务、项目及结果的数据库连接地址,这里我们用一个 MariaDB 服务存储这三类数据,上面的 root
为我们的 MariaDB 用户名,后面 your_password
应替换为之前设置的数据库密码。 message_queue
后应填写消息队列服务的地址,这里我们是将 Redis 做为消息队列使用。 webui
项中的 username
及 password
是用来配置访问 WebUI 时的用户名和密码,也可以不设置用户名和密码,只需将 need-auth
改成 false
即可。
接下来创建一个工作文件夹,并启动爬虫
mkdir ~/pyspider
cd ~/pyspider
pyspider -c /etc/pyspider/pyspider.conf.json
此时访问 http://你的服务器IP地址:5000
,应该可以看到 PySpider 的 Dashboard。
如果访问失败,很可能是防火墙组织了发往5000端口的数据包,这时我们需要打开5000端口
iptables -A INPUT -p tcp --dport 5000 -j ACCEPT
iptables --flush
此时重新启动爬虫,就可以访问 Dashboard 了,我们也可以结合 nohup
让爬虫服务运行在后台:
nohup pyspider -c /etc/pyspider/pyspider.conf.json &
至此 PySpider 服务的部署就完成了。
4. 爬取华为开发者社区
在搭建好服务之后,我们的目标是建立一个华为开发者社区所有文章的数据库,也就是要保存社区中所有的博客文章以及相关的数据。既然要对这些文章下手,那第一件事就是需要找到一个合适的文章列表。一个理想的列表应该具有这些特征:
尽可能多地包含指向文章页面的链接
可以通过跳转到下一页获取到所有文章的 URL
列表根据时间由新到旧排序,方便获取到最新的文章
很幸运,我们发现社区博客页面的首页就有这样一个我们所期待的列表
https://portal.huaweicloud.com/blogs
接下来我们正式开始上手 PySpider。首先输入之前设置的用户名和密码进入 Dashboard,然后点击 Create 新建一个 Project,命名为 huawei_developer
,初始 URL 填写我们刚刚找到的列表页面https://portal.huaweicloud.com/blogs,类型选择 Script
。
右侧是我们写爬虫代码的编辑区,在 PySpider 中,on_start
是整个爬虫的入口
@every(minutes=24 * 60)
def on_start(self):
self.crawl('https://portal.huaweicloud.com/blogs', callback=self.index_page)
self.crawl
会获取页面,并调用 callback
来分析响应,@every
装饰器表示 on_start
会每天运行一次确保不会漏掉新的文章。
点击绿色的 Run 按钮,切换到 follow 页面,下面会出现一个链接,就是我们这里首先要爬取的第一个页面,我们这里点击绿色三角按钮
在首页上,我们需要提取两种链接:一是文章的链接,比如https://portal.huaweicloud.com/blogs/b16fc680d01811e7b8317ca23e93a891
;二是列表翻到下一页的链接。
我们可以看到,在爬取首页后,默认设置的爬虫在页面上找出了127条链接。而其中我们想要的页面链接实际上只存在于文章列表的区域,下面我们将使用类似于 CSS 选择器的工具来过滤出我们想要的元素。
CSS 选择器是 CSS 用来选择 HTML 中元素的工具,CSS 通过这种简单的语法指定特定的元素并应用一些样式。由于包含信息的元素往往具有不同的样式,所以这里使用 CSS 选择器来过滤元素非常合适。关于 CSS 选择器的更多信息可以参考:
https://www.w3schools.com/cssref/css_selectors.asp
这里我们可以通过 PyQuery 内建的 response.doc
对象使用 CSS 选择器。
PySpider 提供了一个叫做 css selector helper 的工具,利用它我们可以更方便地通过点击元素得到它对应的 css 选择器。切换到 web 页面并点击 enable css selector helper 就可以使用它。
当光标移到元素上时,对应元素会变成橙色高亮状态。点击元素,一条CSS 选择器信息会出现在页面顶部,你可以进一步编辑来定位需要的元素,然后把 CSS 选择器加在你的代码里。
我们点击一条带链接的文章标题,然后将选择器加在代码里:
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('a.common-blog-title').items():
self.crawl(each.attr.href, callback=self.detail_page)
在提取到文章链接的同时,我们也要考虑翻页操作。这里可以用同样的思路得到指向下一页的链接。值得注意的是,最后一页的页面上没有对应的翻页控件,因此需要做一个额外的判断,在有翻页按钮的情况下才向后翻页。此外,我们解析文章列表页的函数是 index_page
本身,所以翻页的回调函数应设为 self.index_page
,修改后的代码为:
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('a.common-blog-title').items():
self.crawl(each.attr.href, callback=self.detail_page)
next = response.doc('.ucd-pager-next > a')
if next:
self.crawl(next.attr.href, callback=self.index_page)
再次点击 run,然后进入 follow 页,选择一个文章链接,点击三角进入详情。
我们可以加一些键值来提取和保存更多的信息:
@config(priority=2)
def detail_page(self, response):
return {
"url": response.url,
"title": response.doc('.cloud-blog-detail-title').text(),
"author": response.doc('.sub-content-username').text(),
"release_time": response.doc('.article-write-time').text()[4:],
"view_time": response.doc('.common-blog-eye').next().text(),
"comment_time": response.doc('.common-blog-bubbling').next().text(),
"tags": [i.text() for i in response.doc('.blog-menu-footer > a').items()],
"summary": response.doc('.cloud-blog-detail-summary-tag').text(),
"content": response.doc('#blogContent').html()
}
在原先只保存 url
和 title
的基础上,我们把文章相关的作者、发布时间、浏览次数、评论数、标签、摘要和正文都从页面上提取出来,具体的获取细节和上面类似,这里就不展开了。点击 run 可以看到提取的效果。
从预览来看,结果还是很符合我们的预期的。完成代码编辑和测试后,别忘了点右上角的 save,然后回到 Dashboard,将项目对的 status 改成 DEBUG
或者 RUNNING
,然后点击 Run,爬虫就开始每天按计划进行爬取并更新数据啦。
任务完成后点击 Results 可以看到爬虫保存的数据,可以导出并做后续的加工处理。由于我们使用了 MariaDB 保存结果,实际上后续的数据处理过程也可以直接连接数据库获取数据。
总结
至此,我们的 PySpider 爬虫实战入门项目就完成了。华为开发者社区是一个开放的平台,因此数据获取也并没有受到太多阻碍,然而在实际的运用中,会遇到很多具有反爬虫策略的网站。道高一尺,魔高一丈,我们用上 ip 代理、模拟登陆、验证码识别、无头浏览器等等技术手段和网站维护人员斗智斗勇,还是有办法突破他们设置的封锁的。
这篇博文只是一个系列的开篇,后续我会为大家总结应对反爬虫策略的一些方法,以及数据分析和处理的技巧,欢迎大家一起交流。