【Python基础】增量爬虫怎么实现

2021-02-05 10:57发布

2条回答
给你三个亿
2楼 · 2021-02-25 15:00

一:什么是增量式爬虫

爬虫策略:

  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()12345678910111213141516171819202122232425
  1. 编写下载器即downloader函数

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

def downloader(url):
    response = requests.get(url)
    return response.text123
  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"] = display12345678910111213141516171819202122232425

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

编写extract_first提取函数

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

修改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 item123456789101112131415161718192021222324252627282930
  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)123456789101112

编写加密函数

def encryption(url):
    # 初始化md5对象
    md5 = hashlib.md5()
    # 加密
    md5.update(url.encode("utf-8"))
    # 返回加密后的数据
    return md5.hexdigest()1234567
  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)        
1234567891011121314151617181920212223

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

  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 == 012345678
  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)123456789101112131415161718192021222324252627

至此增量式爬虫编写完毕,在此提醒:运行文件的时候可别忘了打开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()


人情世故
3楼 · 2021-03-10 15:49

1)统一更新法:爬虫以相同的频率访问所有网页,不考虑网页的改变频率;2)个体更新法:爬虫根据个体网页的改变频率来重新访问各页面;3)基于分类的更新法:爬虫根据网页改变频率将其分为更新较快网页子集和更新较慢网页子集两类,然后以不同的频率访问这两类网页。

相关问题推荐

  • 回答 3

    换行。比如,print hello\nworld效果就是helloworld\n就是一个换行符。\是转义的意思,'\n'是换行,'\t'是tab,'\\'是,\ 是在编写程序中句子太长百,人为换行后加上\但print出来是一整行。...

  • 回答 42

    十种常见排序算法一般分为以下几种:(1)非线性时间比较类排序:a. 交换类排序(快速排序、冒泡排序)b. 插入类排序(简单插入排序、希尔排序)c. 选择类排序(简单选择排序、堆排序)d. 归并排序(二路归并排序、多路归并排序)(2)线性时间非比较类排序:...

  • 回答 70
    已采纳

    前景很好,中国正在产业升级,工业机器人和人工智能方面都会是强烈的热点,而且正好是在3~5年以后的时间。难度,肯定高,要求你有创新的思维能力,高数中的微积分、数列等等必须得非常好,软件编程(基础的应用最广泛的语言:C/C++)必须得很好,微电子(数字电...

  • 回答 28

    迭代器与生成器的区别:(1)生成器:生成器本质上就是一个函数,它记住了上一次返回时在函数体中的位置。对生成器函数的第二次(或第n次)调用,跳转到函数上一次挂起的位置。而且记录了程序执行的上下文。生成器不仅记住了它的数据状态,生成器还记住了程序...

  • 回答 9

    python中title( )属于python中字符串函数,返回’标题化‘的字符串,就是单词的开头为大写,其余为小写

  • 回答 6

    第一种解释:代码中的cnt是count的简称,一种电脑计算机内部的数学函数的名字,在Excel办公软件中计算参数列表中的数字项的个数;在数据库( sq| server或者access )中可以用来统计符合条件的数据条数。函数COUNT在计数时,将把数值型的数字计算进去;但是...

  • 回答 1

    head是方法,所以需要取小括号,即dataset.head()显示的则是前5行。data[:, :-1]和data[:, -1]。另外,如果想通过位置取数据,请使用iloc,即dataset.iloc[:, :-1]和dataset.iloc[:, -1],前者表示的是取所有行,但不包括最后一列的数据,结果是个DataFrame。...

  • Python入门简单吗2021-09-23 13:21
    回答 45

    挺简单的,其实课程内容没有我们想象的那么难、像我之前同学,完全零基础,培训了半年,直接出来就工作了,人家还在北京大公司上班,一个月15k,实力老厉害了

  • 回答 4

    Python针对众多的类型,提供了众多的内建函数来处理(内建是相对于导入import来说的,后面学习到包package时,将会介绍),这些内建函数功用在于其往往可对多种类型对象进行类似的操作,即多种类型对象的共有的操作;如果某种操作只对特殊的某一类对象可行,Pyt...

  • 回答 8

     相当于 ... 这里不是注释

  • 回答 4

    还有FIXME

  • 回答 3

    python的两个库:xlrd和xlutils。 xlrd打开excel,但是打开的excel并不能直接写入数据,需要用xlutils主要是复制一份出来,实现后续的写入功能。

  • 回答 8

    单行注释:Python中的单行注释一般是以#开头的,#右边的文字都会被当做解释说明的内容,不会被当做执行的程序。为了保证代码的可读性,一般会在#后面加一两个空格然后在编写解释内容。示例:#  单行注释print(hello world)注释可以放在代码上面也可以放在代...

  • 回答 2

    主要是按行读取,然后就是写出判断逻辑来勘测行是否为注视行,空行,编码行其他的:import linecachefile=open('3_2.txt','r')linecount=len(file.readlines())linecache.getline('3_2.txt',linecount)这样做的过程中发现一个问题,...

  • 回答 4

    或许是里面有没被注释的代码

  • 回答 26

    自学的话要看个人情况,可以先在B站找一下视频看一下

没有解决我的问题,去提问