增量式爬虫详细讲解,附案例分析

爬虫之增量式爬虫

一:什么是增量式爬虫

爬虫策略:

  1. 广度优先

    比如我们平时通过分页爬取的方式

  2. 深度优先

    对于深度优先来说,必须使用增量爬虫

增量的含义就是不断的增加,它通过我们提供的一个入口,不断的去爬取数据,从而达到使数据不断增加的目的。

在我们平时的爬取过程中,会遇到一些问题:

  1. 页面内容发生变更

    1. 有些数据需要我们持久的慢慢的进行爬取

如果我们的爬虫页面发生了变化,也能够持续稳定的将变化的数据更新到数据库中,同时又能保证爬虫再执行的过程中,数据量也在不停的增加,这样的爬虫就叫增量爬虫。

增量爬虫的核心:去重,如果不进行去重会进入死链状态,详细可参考下面的案例

增量爬虫如何去重:

  1. 在请求发起之前,判断此url是否爬取过
  2. 在解析内容之后,判断该内容是否被爬取过
  3. 在保存数据时,判断将要写入的内容是否存在于存储介质中

二:案例演示

案例目标:猫眼电影-影人抓取,爬取尽可能多的演员信息

案例介绍:此案例的爬虫策略和之前博客中发布的爬虫案例的都不同,这个案例并没有分类页(列表页),没有一个完整的爬虫入口,只能够请求一个或多个演员,然后找到相关影人,从而尽可能多的获取。

  1. 编写调度器即定义一个队列进行存取
from lxml import etree

def main():
	# 取出队列中的url,进行请求
	url = scheduler.get()
	# 定义downloader下载器处理取出的请求
	response = downloader(url)
	html = etree.HTML(response)
	
if __name__ == "__main__":
	# 初始化任务池,尽可能多的放进一些url,这样搜索面才会大
	start_urls = [
		"https://maoyan.com/films/celebrity/789",
        "https://maoyan.com/films/celebrity/29480",
        "https://maoyan.com/films/celebrity/29481",
        "https://maoyan.com/films/celebrity/28625",
        "https://maoyan.com/films/celebrity/5392",
	]
	# 定义一个调度器队列
	scheduler = Queue()
	# 将任务池中的url放入到调度器的队列中
	for url in start_urls:
		scheduler.put(url)
	# 定义一个main函数用来执行其它组件的功能
	main()
  1. 编写下载器即downloader函数

作用:将从调度器拿到的url进行请求,并将请求后的响应返回给爬虫组件spider

def downloader(url):
    response = requests.get(url)
    return response.text
  1. 编写爬虫组件spider函数

作用:处理下载器发送过来的响应,获取数据

def spider(html):

    # 创建一个item字典
    item = {}

    # 获取影人中文名字
    zh_name = html.xpath('//p[@class="china-name cele-name"]/text()')[0]
    # 获取影人英文名字
    en_name = html.xpath('//p[@class="eng-name cele-name"]/text()')[0]
    # 获取影人职业
    profession = html.xpath('//span[@class="profession"]/text()')[0]
    # 获取影人生日
    birthday = html.xpath('//span[@class="birthday"]/text()')[0]
    # 获取影人身高
    height = html.xpath('//span[@class="height"]/text()')[0]
    # 获取影人作品
    display = html.xpath('//p[@class="movie-name"]/text()')
    
    # 保存数据至item
    item["zh_name"] = zh_name
    item["en_name"] = en_name
    item["profession"] = profession
    item["birthday"] = birthday
    item["height"] = height
    item["display"] = display

这里出现了一个问题:如果影人没有英文名或是缺少了其它一些信息,那么我们就会得到一个空列表,之后用索引取值时就会报错,为了解决这个问题,我们再编写一个提取数据的函数,使得如果得到一个空列表则返回一个None。

编写extract_first提取函数

def extract_first(text):
    if text:
        return text[0]
    return None

修改spider函数

def spider(html):

    # 创建一个item字典
    item = {}

# -------------------------------------------------------------------------------------
    # 获取影人中文名字
    zh_name = extract_first(html.xpath('//p[@class="china-name cele-name"]/text()'))
    # 获取影人英文名字
    en_name = extract_first(html.xpath('//p[@class="eng-name cele-name"]/text()'))
    # 获取影人职业
    profession = extract_first(html.xpath('//span[@class="profession"]/text()'))
    # 获取影人生日
    birthday = extract_first(html.xpath('//span[@class="birthday"]/text()'))
    # 获取影人身高
    height = extract_first(html.xpath('//span[@class="height"]/text()'))
    # 获取影人作品
    display = html.xpath('//p[@class="movie-name"]/text()')
# -------------------------------------------------------------------------------------

    # 保存数据至item
    item["zh_name"] = zh_name
    item["en_name"] = en_name
    item["profession"] = profession
    item["birthday"] = birthday
    item["height"] = height
    item["display"] = display

    return item

  1. 编写管道组件pipeline函数

作用:将获取到的数据保存到mongodb数据库中

def pipeline(item,url):
    # 链接mongodb
    client = pymongo.Client(host="127.0.0.1",port=27017)
    # 进入数据库
    db = client["first_text"]
    # 进入集合
    col = db["actor"]
    # 插入数据,需进行判重
    # 判重思路:一般都是通过url进行判重,在这里我们可以将主键id替换为我们
加密过的请求url,然后进行判重,如果此url已请求过则不再添加它的返回值
    item["_id"] = encryption(url)
    col.update({"_id":url},{"$set":item},True)

编写加密函数

def encryption(url):
    # 初始化md5对象
    md5 = hashlib.md5()
    # 加密
    md5.update(url.encode("utf-8"))
    # 返回加密后的数据
    return md5.hexdigest()
  1. 获取相关影人的url,并将获取到的url放入到队列中,不断进行请求,直到队列为空为止

    对main函数进行修改

def main():
# ---------------------------------------------------------------------------------
	# 使用死循环一直从队列中取出URL,在队列为空时退出循环
	while True:
		if scheduler.empty():
			break
# ----------------------------------------------------------------------------------

        # 取出队列中的url,进行请求
        url = scheduler.get()
        # 定义downloader下载器处理取出的请求
        response = downloader(url)
        html = etree.HTML(response)
        # 定义spider爬虫组件接受下载器的请求并处理数据
        item = spider(html)
        # 定义pipeline管道对数据进行保存
        pipeline(item,url)
        # 获取相关影人的url
        related_url_list = html.xpath('//div[@class="rel-item"]/a/@href')
        for related_url in related_url_list:
            full_related_url = "https://maoyan.com" + related_url
            scheduler.put(full_related_url)
        

​ 这里又出现了一个问题,假设某演员的相关影人中有他的朋友,当我们进入到他朋友的页面中后,如果他朋友的相关影人也有他那么就会不断的进行请求,就会形成一个死循环,所以我们要进行队列的去重操作。

  1. 定义去重函数url_seen,相当于Scrapy-Redis的去重器

去重器原理:将每一次取出的url放入到redis数据库中集合中,以放入时返回的值为判断条件,如果返回值为0则说明有重复的url,我们就不再进行请求,如果返回值为1,说明这个url没有被请求过,我们就可以对这个url发起请求

def url_seen(url):
"""去重函数"""

    # 链接Redis数据库
    r = redis.Redis(host="127.0.0.1",port=6379)
    # 判重
    res = r.sadd("maoyan:actor_url",encryption(url))
    return res == 0
  1. 修改main函数,对队列中的url进行去重处理
def main():
    # 使用死循环一直从队列中取出URL,在队列为空时退出循环
    while True:
        if scheduler.empty():
            break
        # 取出队列中的url,进行请求
        url = scheduler.get()
        
# 在此处判断,即每一次从队列中取出url时,先去判断此url是否已经存在
# -----------------------------------------------------------     
        if not url_seen(url):
# -----------------------------------------------------------

            # 定义downloader下载器处理取出的请求
            response = downloader(url)
            html = etree.HTML(response)
            # 定义spider爬虫组件接受下载器的请求并处理数据
            item = spider(html)
            # 定义pipeline管道对数据进行保存
            pipeline(item,url)
            # 获取相关影人的url
            related_url_list = html.xpath('//div[@class="rel-item"]/a/@href')
            for related_url in related_url_list:
                full_related_url = "https://maoyan.com" + related_url
                # 将获取到的url放入到队列中
                scheduler.put(full_related_url)

至此增量式爬虫编写完毕,在此提醒:运行文件的时候可别忘了打开mongodb和redis数据库的服务器,不然会报请求被拒绝的错误哦!

下面附完整无注释代码:

import redis
import hashlib
import pymongo
import requests

from lxml import etree
from queue import Queue

def extract_first(text):
    if text:
        return text[0]
    return None


def encryption(url):
    md5 = hashlib.md5()
    md5.update(url.encode("utf-8"))
    return md5.hexdigest()

def pipeline(item,url):
    client = pymongo.MongoClient(host="127.0.0.1",port=27017)
    db = client["first_text"]
    col = db["actor"]
    item["_id"] = encryption(url)
    col.update_many({"_id":item["_id"]},{"$set":item},True)


def spider(html):
    item = {}

    zh_name = extract_first(html.xpath('//p[@class="china-name cele-name"]/text()'))
    en_name = extract_first(html.xpath('//p[@class="eng-name cele-name"]/text()'))
    profession = extract_first(html.xpath('//span[@class="profession"]/text()'))
    birthday = extract_first(html.xpath('//span[@class="birthday"]/text()'))
    height = extract_first(html.xpath('//span[@class="height"]/text()'))
    display = html.xpath('//p[@class="movie-name"]/text()')

    item["zh_name"] = zh_name
    item["en_name"] = en_name
    item["profession"] = profession
    item["birthday"] = birthday
    item["height"] = height
    item["display"] = display

    return item


def downloader(url):
    response = requests.get(url)
    return response.text


def url_seen(url):
    r = redis.Redis(host="127.0.0.1",port=6379)
    res = r.sadd("maoyan:actor_url",encryption(url))
    return res == 0

def main():
    while True:
        if scheduler.empty():
            break
        url = scheduler.get()
        if not url_seen(url):
            response = downloader(url)
            html = etree.HTML(response)
            item = spider(html)
            pipeline(item,url)
            related_url_list = html.xpath('//div[@class="rel-item"]/a/@href')
            for related_url in related_url_list:
                full_related_url = "https://maoyan.com" + related_url
                scheduler.put(full_related_url)


if __name__ == "__main__":
    start_urls = [
        "https://maoyan.com/films/celebrity/789",
        "https://maoyan.com/films/celebrity/29480",
        "https://maoyan.com/films/celebrity/29481",
        "https://maoyan.com/films/celebrity/28625",
        "https://maoyan.com/films/celebrity/5392",
    ]
    scheduler = Queue()
    for url in start_urls:
        scheduler.put(url)
    main()

  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值