跳到主要内容

Linux 修行之路 · Blog

Linux修行之路 - 技术博客

分享Kubernetes、Linux、Python、网络安全等技术文章

文章数量169
技术分类9
查看分类
21

分布式爬虫概述

· 阅读需 4 分钟

分布式爬虫概述

分布式爬虫,是一种能够将爬虫效率发挥到极致的爬虫方法。

实现方式:scrapy + redis(完整说法是 scrapy 结合着 scrapy-redis 组件)

原生的 scrapy 框架是无法实现分布式的。

什么是是分布式?

  • 分布式就是搭建一个分布式的机群,然后让机群中的每一台电脑执行同一组程序,让其对同一组资源 进行联合且分布的数据爬取。

为什么原生的 scrapy 框架无法实现分布式?

  • 调度器无法被分布式机群共享
  • 管道无法分布式机群被共享

如何实现分布式?

  • 使用 scrapy-redis 组件即可

scrapy-redis 组件的作用是,可以给原生的 scrapy 框架提供共享的管道和调度器。

scrapy-redis 的安装:

pip install scrapy-redis

分布式爬虫的实现流程

修改爬虫文件

导包方式:

from scrapy_redis.spiders import RedisCrawlSpider

然后修改当前爬虫类的父类为 RedisCrawlSpider,将 start_url 替换成 redis_keys 的属性,属性值为任意字符串,比如:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider

class FbsSpider(RedisCrawlSpider):
name = 'fbs'
# allowed_domains = ['www.xxx.com']
# start_urls = ['http://www.xxx.com/']
redis_key = 'sunQueue' # 可以被共享的调度器队列的名称,可以随意取

redis_key = 'xxx' 表示的是可以被共享的调度器队列的名称,最终是需要将起始的 url 手动放置到 redis_key 表示的队列中。

剩下的,就跟正常解析数据一样了。

配置 settings.py 文件

指定调度器。增加一个去重容器类的配置,作用是使用 Redis 的 set 集合来存储请求的指纹数据,从而实现请求去重的持久化。

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

使用 scrapy-redis 组件自己的调度器。

SCHEDULER = "scrapy_redis.scheduler.Scheduler"

配置调度器是否要持久化。也就是当爬虫结束了,要不要清空 Redis 中请求队列和去重指纹的 set。如果是 True, 就表示要持久化存储,就不清空数据,否则清空数据。

SCHEDULER_PERSIST = True

指定管道。

ITEM_PIPELINES = {    'scrapy_redis.pipelines.RedisPipeline': 400}

特点:该种管道只可以将 item 写入 redis。

指定 redis。

REDIS_HOST = 'redis服务的ip地址'REDIS_PORT = 6379

配置 redis 的配置文件

Windows 中是 redis 安装目录下的 redis.window.conf 文件。

解除默认绑定,注释掉第 56 行的绑定 IP 的代码

#bind 127.0.0.1

关闭保护模式,将第 75 行的 protected-mode 设置成 no

protected-mode no

启动 redis 服务和客户端

进入 redis 安装路径,执行命令启动 redis。

执行 scrapy 工程

scrapy crawl fbs

注意,执行 scrapy 工程时,不要在配置文件中加入 LOG_LEVEL。我们需要分析这些日志信息。

工程启动后,程序会停留在 listening 位置,等待起始的 url 加入。

向 redis_key 表示的队列中添加起始 url

需要在 redis 的客户端执行如下指令(调度器队列是存在于 redis 中):

lpush sunQueue http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1

分布式爬虫实例

需求:使用分布式爬虫爬取阳光问政平台全站的问政标题和状态

网址 url:http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1

分析:分布式爬虫和普通爬虫的核心数据解析代码是一致的,只需要稍加修改,再进行一些配置,即可实现分布式。

爬虫源文件的写法:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy_redis.spiders import RedisCrawlSpider
from fbsPro.items import FbsproItem


class FbsSpider(RedisCrawlSpider):
name = 'fbs'
# allowed_domains = ['www.xxx.com']
# start_urls = ['http://www.xxx.com/']
redis_key = 'sunQueue' # 可以被共享的调度器队列的名称,可以随意取
# 稍后我们是需要将一个起始的url手动的添加到redis_key表示的队列中
rules = (
Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=True),
)

def parse_item(self, response):
li_list = response.xpath('//ul[@class="title-state-ul"]/li')
for li in li_list:
status = li.xpath('./span[2]/text()').extract_first().strip()
title = li.xpath('./span[3]/a/text()').extract_first().strip()
item = FbsproItem()
item['status'] = status
item['title'] = title
yield item

然后安装前面所说的,配置 settings.py 即可。

增量试爬虫概述

· 阅读需 4 分钟

对于我们前面的那些爬虫方法,如果我们之前爬取过某个网站,下次再启动工程,还是会从头爬取。即便我们之前爬取过这个网站的很多数据,但是我们还是会对这些爬取过的数据重复爬取。为了减少这种重复爬取的操作,让程序更加集中运行在我们没有爬取过的,新出现的网页中,从而提高爬取效率。

增量式爬虫的概念:监测网站数据更新的情况,以便于爬取到最新更新出来的数据。

实现增量式的核心是要去重

实战中去重的方式:记录表。

记录表需要记录的一定是爬取过的相关信息,能够唯一标识爬取过的任务。

爬取过的相关信息通常指的是详情页的 url。当然只要某一组数据,该组数据如果可以作为该部电影的唯一标识即可,刚好详情页的 url 往往就可以作为任务单元的唯一标识。只要可以表示任务单元唯一标识的数据我们统称为数据指纹

去重的方式对应的记录表应该使用什么数据结构呢?

  • python 中的 set 集合是不太合适的,因为 set 集合不方便进行持久化存储
  • redis 中的 set 则可以胜任我们的记录表,因为它可以持久化存储数据

数据指纹一般是经过加密处理的。数据量不是很大的数据指纹没有必要加密。如果数据的唯一标识标识的内容数据量比较大,可以使用 hash 函数将数据加密成 32 位的密文。

给数据指纹加密目的是为了节省空间。

接下来,我们就以 2345 电影网为例,看看如何实现增量式爬虫。

需求:使用增量式爬虫实现 2345 电影网的电影标题和电影细节描述的爬取。

网址 url:https://www.4567kan.com/index.php/vod/show/class/ 动作 /id/1.html```

在爬虫源文件中,大部分代码和普通的爬虫是一样的,只是多了一步将数据指纹存放到 redis 集合中的操作。这里的数据指纹是电影的 url。如果 url 在集合中,添加不成功,返回的是 0。这意味着,之前已经爬取过这部电影了,无需再次爬取。如果集合中没有这部电影的 url,则说明这部电影还没有爬取,就要爬取这个链接后,获取电影的详细信息。这就很简单了,一步简单判断即可实现:

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis
from zlsPro.items import ZlsproItem

class ZlsSpider(CrawlSpider):
name = 'zls'
# allowed_domains = ['www.4567kan.com/index.php/vod/show/class/动作/id/1.html']
start_urls = ['http://www.4567kan.com/index.php/vod/show/class/动作/id/1.html/']
# 建立redis连接
conn = Redis('127.0.0.1', 6379)

rules = (
Rule(LinkExtractor(allow=r'page/\d+\.html'), callback='parse_item', follow=True),
)

def parse_item(self, response):
li_list = response.xpath('//ul[@class="stui-vodlist clearfix"]/li')
for li in li_list:
url = 'https://www.4567kan.com' + li.xpath('./div/a/@href').extract_first()
title = li.xpath('./div/a/@title').extract_first()
ex = self.conn.sadd('movie_urls', url)
# ex==1插入成功,ex==0插入失败
if ex: # detail_url表示的电影没有存在于记录表中
item = ZlsproItem()
item['title'] = title
# 爬取电影数据:发起请求
print(f'有数据更新,电影《{title}》正在爬取中...')
yield scrapy.Request(url, callback=self.parse_detail, meta={'item': item})
else:
# 这个链接已经存在于记录表中,不需要爬取
print('这部电影已经爬过了,不需要再爬了...')
def parse_detail(self, response):
# 解析电影简介
desc = response.xpath('//span[@class="detail-content"]/text()').extract_first()
if not desc:
desc = response.xpath('//span[@class="detail-sketch"]/text()').extract_first()
item = response.meta['item']
item['desc'] = desc
yield item

在管道中,我们可以将数据以任意形式存储,这里将数据存储到 redis 中:

class ZlsproPipeline(object):
def process_item(self, item, spider):
# spider即爬虫对象,conn是redis连接
conn = spider.conn
conn.lpush('movieData', item)
return item

其他如 settings.py 中的配置和 items 的配置就不介绍了吧。

安装 python3 环境

· 阅读需 3 分钟

centos7 自带有 python,版本是python2.7

接下来我们手动安装python3,并且配置后可以并存使用。

1.首先,你要知道系统现在的python的位置在哪儿:

[root@root ~]# whereis python
python: /usr/bin/python2.7 /usr/bin/python /usr/lib/python2.7 /usr/lib64/python2.7 /etc/python /usr/include/python2.7 /usr/share/man/man1/python.1.gz
  • 可以知道我们的python在 /usr/bin目录中
[root@root ~]# cd /usr/bin/
[root@root bin]# ll python*
lrwxrwxrwx. 1 root root 7 2月 7 09:30 python -> python2
lrwxrwxrwx. 1 root root 9 2月 7 09:30 python2 -> python2.7
-rwxr-xr-x. 1 root root 7136 8月 4 2017 python2.7
  • 可以看到,python指向的是python2,python2指向的是python2.7,因此我们可以装个python3,然后将python指向python3,然后python2指向python2.7,那么两个版本的python就能共存了。

2.因为我们要安装python3,所以要先安装相关包,用于下载编译python3:

yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make 运行了以上命令以后,就安装了编译python3所用到的相关依赖

3.默认的,centos7也没有安装pip,不知道是不是因为我安装软件的时候选择的是最小安装的模式。

#运行这个命令添加epel扩展源
yum -y install epel-release
#安装 pip
yum install python-pip

4.用pip装wget

pip install wget

5.用wget下载python3的源码包

wget https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tar.xz

6.编译python3源码包

#解压
xz -d Python-3.6.4.tar.xz
tar -xf Python-3.6.4.tar
#进入解压后的目录,依次执行下面命令进行手动编译
cd Python-3.6.4
./configure prefix=/usr/local/python3
make && make install
# 如果出现can't decompress data; zlib not available这个错误,则需要安装相关库#安装依赖zlib、zlib-devel
yum install zlib zlib
yum install zlib zlib-devel

如果最后没提示出错,就代表正确安装了,在/usr/local/目录下就会有python3目录

7.添加软链接

#将原来的链接备份mv /usr/bin/python /usr/bin/python.bak 
#添加python3的软链接
ln -s /usr/local/python3/bin/python3.6 /usr/bin/python
#测试是否安装成功了
python -V

8.更改yum配置,因为其要用到python2才能执行,否则会导致yum不能正常使用

vi /usr/bin/yum
把#! /usr/bin/python修改为#! /usr/bin/python2

vi /usr/libexec/urlgrabber-ext-down
把#! /usr/bin/python 修改为#! /usr/bin/python2

启动 python2: python2

启动 python3: python

pip3 的环境配置

ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3